diff --git a/.ci/Debian12/Dockerfile b/.ci/Debian12/Dockerfile index 14c6f018c..4da910dc0 100644 --- a/.ci/Debian12/Dockerfile +++ b/.ci/Debian12/Dockerfile @@ -3,11 +3,13 @@ FROM debian:12 RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ build-essential \ + ca-certificates \ ccache \ cmake \ file \ g++ \ git \ + libcurl4-openssl-dev \ libgl-dev \ liblzma-dev \ libmariadb-dev-compat \ diff --git a/.ci/Debian13/Dockerfile b/.ci/Debian13/Dockerfile index b597307c5..94f711398 100644 --- a/.ci/Debian13/Dockerfile +++ b/.ci/Debian13/Dockerfile @@ -9,6 +9,7 @@ RUN apt-get update && \ file \ g++ \ git \ + libcurl4-openssl-dev \ libgl-dev \ liblzma-dev \ libmariadb-dev-compat \ diff --git a/.ci/Fedora43/Dockerfile b/.ci/Fedora43/Dockerfile index 463da5a51..8aa345651 100644 --- a/.ci/Fedora43/Dockerfile +++ b/.ci/Fedora43/Dockerfile @@ -1,10 +1,12 @@ FROM fedora:43 RUN dnf install -y \ + ca-certificates \ ccache \ cmake \ gcc-c++ \ git \ + libcurl-devel \ mariadb-devel \ ninja-build \ openssl-devel \ diff --git a/.ci/Fedora44/Dockerfile b/.ci/Fedora44/Dockerfile index 62238e760..15204fa1a 100644 --- a/.ci/Fedora44/Dockerfile +++ b/.ci/Fedora44/Dockerfile @@ -1,10 +1,12 @@ FROM fedora:44 RUN dnf install -y \ + ca-certificates \ ccache \ cmake \ gcc-c++ \ git \ + libcurl-devel \ mariadb-devel \ ninja-build \ openssl-devel \ diff --git a/.ci/Ubuntu24.04/Dockerfile b/.ci/Ubuntu24.04/Dockerfile index b9fb2b701..43497dd3b 100644 --- a/.ci/Ubuntu24.04/Dockerfile +++ b/.ci/Ubuntu24.04/Dockerfile @@ -3,11 +3,13 @@ FROM ubuntu:24.04 RUN apt-get update && \ DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ build-essential \ + ca-certificates \ ccache \ cmake \ file \ g++ \ git \ + libcurl4-openssl-dev \ libgl-dev \ liblzma-dev \ libmariadb-dev-compat \ diff --git a/.ci/Ubuntu26.04/Dockerfile b/.ci/Ubuntu26.04/Dockerfile index cc6528858..4dc861ae9 100644 --- a/.ci/Ubuntu26.04/Dockerfile +++ b/.ci/Ubuntu26.04/Dockerfile @@ -10,6 +10,7 @@ RUN apt-get update && \ g++ \ gcovr \ git \ + libcurl4-openssl-dev \ libgl-dev \ liblzma-dev \ libmariadb-dev-compat \ diff --git a/.ci/compile.sh b/.ci/compile.sh index 6789b9817..9be8ba741 100755 --- a/.ci/compile.sh +++ b/.ci/compile.sh @@ -200,6 +200,20 @@ if [[ $USE_VCPKG ]]; then flags+=("-DUSE_VCPKG=1") flags+=("-DVCPKG_INSTALL_OPTIONS=--x-abi-tools-use-exact-versions") fi +if [[ ${WITH_CRASH_REPORTING:-} == "ON" ]]; then + # Release CI opts into the sentry-native SDK (default is OFF so from-source, distro and + # offline package builds never fetch it). Debug builds stay SDK-free so crashes from + # developer builds never reach Sentry. + flags+=("-DWITH_CRASH_REPORTING=ON") +fi +if [[ ${BUILDTYPE:-Release} == "Release" && -n ${COCKATRICE_SENTRY_DSN:-} ]]; then + # Release-only: Debug builds stay DSN-free so crashes from developer builds + # never reach Sentry. Empty (local builds, forks without the secret, and non-tag + # CI builds, which never receive the secret) keeps today's mock fallback via the + # compiled-in default. CI passes the value from the SENTRY_DSN secret on tag + # Release builds only, so pre-release test artifacts never report to Sentry. + flags+=("-DCOCKATRICE_SENTRY_DSN=$COCKATRICE_SENTRY_DSN") +fi # Add cmake --build flags buildflags=(--config "$BUILDTYPE") @@ -297,16 +311,24 @@ if [[ $RUNNER_OS == macOS ]]; then if [[ $MAKE_PACKAGE ]]; then # Workaround https://github.com/actions/runner-images/issues/7522 - # have hdiutil repeat the command 10 times in hope of success + # have hdiutil repeat the command 10 times in hope of success. + # The wrapper has to exit non-zero once it gives up: CPack reads that status to + # decide whether the disk image was created, so a zero exit turns a failed hdiutil + # into a much later, misleading "Problem copying the package" error that hides + # hdiutil's own message. hdiutil_script="/tmp/hdiutil.sh" # shellcheck disable=SC2016 echo '#!/bin/bash + attempts=10 i=0 - while ! hdiutil "$@"; do - if (( ++i >= 10 )); then - echo "Error: hdiutil failed $i times!" >&2 - break + while true; do + hdiutil "$@" && exit 0 + rc=$? + if (( ++i >= attempts )); then + echo "::error::hdiutil $1 failed $i times (last status $rc), giving up" >&2 + exit "$rc" fi + echo "hdiutil $1 attempt $i/$attempts failed (status $rc), retrying" >&2 sleep 1 done' >"$hdiutil_script" chmod +x "$hdiutil_script" @@ -445,6 +467,206 @@ if [[ $MAKE_COVERAGE ]]; then echo "::endgroup::" fi +if [[ $RUNNER_OS == Windows && ! $MAKE_NO_CLIENT ]]; then + echo "::group::Probe the installer's holder lookup" + # The working directory is still the build dir here: the package steps below are what + # leave and re-enter it, and that is what made a $BUILD_DIR-prefixed path name + # build/build. Both paths are relative to the dir the probe was built into. + probe="./installer-holder-probe.exe" + if [[ ! -f $probe ]]; then + echo "::error file=$0::installer-holder-probe.exe was not built" + exit 1 + fi + # The probe exists to name a holder that a process-and-module scan cannot see: one + # that has a file open without having loaded it. Holding a temporary DLL with + # FileShare.None is exactly that case, so this asserts the reason the probe was + # added instead of only that the Restart Manager answers at all. It needs no NSIS + # and no packaging, so it runs on every Windows build that gets this far. + # The holder is a process of its own: the probe never names the process that started it, + # which is what keeps an in-place uninstaller from being named as a holder of the + # directory it is emptying. The same DLL held by the script that starts the probe + # therefore has to come back as nothing, and that is the second case below. + probe_check="./holder-probe-check.ps1" + cat >"$probe_check" <<'POWERSHELL' +$ErrorActionPreference = "Stop" +$dir = Join-Path ([IO.Path]::GetTempPath()) "cockatrice-holder-probe-$PID" +Remove-Item -Recurse -Force $dir -ErrorAction SilentlyContinue +New-Item -ItemType Directory -Force $dir | Out-Null +$probe = (Resolve-Path -LiteralPath $env:PROBE_EXE).Path + +function Invoke-Probe([string] $guardDir) { + $start = New-Object System.Diagnostics.ProcessStartInfo + $start.FileName = $probe + $start.UseShellExecute = $false + $start.RedirectStandardOutput = $true + $start.EnvironmentVariables["COCKATRICE_GUARD_DIR"] = $guardDir + $process = [System.Diagnostics.Process]::Start($start) + $read = $process.StandardOutput.ReadToEndAsync() + if (-not $process.WaitForExit(20000)) { + try { $process.Kill() } catch { } + throw "the holder probe did not finish within 20 s for $guardDir" + } + $output = $read.Result + $code = $process.ExitCode + $process.Dispose() + return @{ Code = $code; Output = $output } +} + +function Wait-UntilLocked([string] $path) { + $deadline = (Get-Date).AddSeconds(15) + while ((Get-Date) -lt $deadline) { + try { + $open = [IO.File]::Open($path, "Open", "ReadWrite", "None") + $open.Dispose() + } catch { + return + } + Start-Sleep -Milliseconds 100 + } + throw "the holder never locked $path" +} + +try { + $file = Join-Path $dir "locked.dll" + [IO.File]::WriteAllText($file, "x") + + # The holder is a process of its own, not this script: the probe never names the process + # that started it (the case below asserts that), so a file held here would be left out on + # purpose and the check would pass for the wrong reason. + $holderScript = Join-Path $dir "hold.ps1" + Set-Content -LiteralPath $holderScript -Value @' +param([string] $Path) +$handle = [IO.File]::Open($Path, "Open", "ReadWrite", "None") +Start-Sleep -Seconds 120 +'@ + $powershell = Join-Path $env:SystemRoot "System32\WindowsPowerShell\v1.0\powershell.exe" + $holder = Start-Process -FilePath $powershell -PassThru -ArgumentList @( + "-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "`"$holderScript`"", "-Path", "`"$file`"") + try { + Wait-UntilLocked $file + $locked = Invoke-Probe $dir + if ($locked.Code -ne 1) { throw "expected exit 1 for a locked file, got $($locked.Code) : $($locked.Output)" } + if ($locked.Output -notmatch "pid\s+$($holder.Id)\b") { + throw "the lookup did not name the process holding the file: $($locked.Output)" + } + } finally { + try { $holder.Kill() } catch { } + try { $holder.WaitForExit(5000) | Out-Null } catch { } + } + + # The probe is started by the installer, or by an uninstaller that runs in place from the + # directory it is emptying, so the asking process holds files there itself. Naming it would + # ask the user to close the program that is asking - a question neither the close nor the + # end offer can answer, so the guard repeated it until the update was cancelled. A holder + # that is only the asking process has to come back as nothing. + $handle = [IO.File]::Open($file, "Open", "ReadWrite", "None") + try { + $self = Invoke-Probe $dir + } finally { + $handle.Dispose() + } + if ($self.Code -ne 0) { throw "expected exit 0 when the asking process is the only holder, got $($self.Code) : $($self.Output)" } + if ($self.Output -match "pid\s+$PID\b") { throw "the lookup named the process that started it: $($self.Output)" } + + # A payload executable that refuses to be replaced is running, whether or not this probe + # may name it: the Restart Manager does not report every mapping, and the image path of a + # process under another account cannot be read from here, so both process lookups can + # come back empty while the program is still there. Held here by the asking process, so + # that it cannot be named - the answer has to be "could not name it", never "nothing is + # holding it", which is what sent an update into an uninstall that could only fail + # halfway. Exit 3 is the code the guard answers by asking the user to close it and retry. + $image = Join-Path $dir "oracle.exe" + [IO.File]::WriteAllText($image, "x") + $handle = [IO.File]::Open($image, "Open", "ReadWrite", "None") + try { + $unnameable = Invoke-Probe $dir + } finally { + $handle.Dispose() + } + if ($unnameable.Code -ne 3) { + throw "expected exit 3 for a payload executable that is in use but cannot be named, got $($unnameable.Code) : $($unnameable.Output)" + } + if ($unnameable.Output -notmatch "oracle\.exe is in use") { + throw "the answer did not name the executable it could not replace: $($unnameable.Output)" + } + + $empty = Join-Path $dir "empty" + New-Item -ItemType Directory -Force $empty | Out-Null + $nothing = Invoke-Probe $empty + if ($nothing.Code -ne 0) { throw "expected exit 0 for a directory with nothing open, got $($nothing.Code) : $($nothing.Output)" } + # A process running from the directory is a holder even when it has no file open: an + # instance holds its payload as mapped images, not as handles, and that is the state + # the guard used to miss - it passed, the purge failed, and the install ended in a + # warning with no offer to close and try again. The image is a copy of a native + # Windows program, so it starts without its usual directory next to it, and it carries + # one of the payload's own names because that is what the lookup names. + $copy = Join-Path $dir "cockatrice.exe" + Copy-Item -LiteralPath (Join-Path $env:SystemRoot "System32\cmd.exe") -Destination $copy + $running = Start-Process -FilePath $copy -ArgumentList "/c", "ping -n 20 127.0.0.1 >nul" -PassThru + try { + Start-Sleep -Milliseconds 1500 + $byImage = Invoke-Probe $dir + } finally { + try { $running.Kill() } catch { } + # Wait for the image to be released, or the cleanup below leaves the copy behind. + try { $running.WaitForExit(5000) | Out-Null } catch { } + } + if ($byImage.Code -ne 1) { throw "expected exit 1 for a process running from the directory, got $($byImage.Code) : $($byImage.Output)" } + if ($byImage.Output -notmatch "cockatrice\.exe \(pid\s+$($running.Id)") { + throw "the lookup did not name the process running from the directory: $($byImage.Output)" + } + + # A program in a folder below the directory is not an instance of the installation, even + # when it carries a payload program's name: the install directory can be a folder the + # user keeps other applications in, each in its own subfolder, and naming one of those + # asked for a close the probe never sends and an end it refuses to perform, so the guard + # could only abort the update. A program that really does hold a payload file is still + # named, by the Restart Manager lookup exercised above. + $nested = Join-Path $dir "other" + New-Item -ItemType Directory -Force $nested | Out-Null + $nestedImage = Join-Path $nested "cockatrice.exe" + Copy-Item -LiteralPath (Join-Path $env:SystemRoot "System32\cmd.exe") -Destination $nestedImage + $nestedRunning = Start-Process -FilePath $nestedImage -ArgumentList "/c", "ping -n 20 127.0.0.1 >nul" -PassThru + try { + Start-Sleep -Milliseconds 1500 + $inSubfolder = Invoke-Probe $dir + } finally { + try { $nestedRunning.Kill() } catch { } + try { $nestedRunning.WaitForExit(5000) | Out-Null } catch { } + } + if ($inSubfolder.Code -ne 0) { + throw "expected exit 0 for a program in a folder below the directory, got $($inSubfolder.Code) : $($inSubfolder.Output)" + } + + # A program the user keeps in the directory itself is not a payload program either: the + # guard ends only what the payload installs, and naming anything else asks for an answer + # that cannot free a single file. + $foreign = Join-Path $dir "tool.exe" + Copy-Item -LiteralPath (Join-Path $env:SystemRoot "System32\cmd.exe") -Destination $foreign + $foreignRunning = Start-Process -FilePath $foreign -ArgumentList "/c", "ping -n 20 127.0.0.1 >nul" -PassThru + try { + Start-Sleep -Milliseconds 1500 + $ignored = Invoke-Probe $dir + } finally { + try { $foreignRunning.Kill() } catch { } + try { $foreignRunning.WaitForExit(5000) | Out-Null } catch { } + } + if ($ignored.Code -ne 0) { + throw "expected exit 0 for a program the payload does not install, got $($ignored.Code) : $($ignored.Output)" + } + Write-Host "holder lookup named pid $($holder.Id) for a file another process holds, named nothing for a file only the asking process holds, reported a payload executable it could not name as in use instead of free, named nothing for an empty directory, named pid $($running.Id) for a payload program running from the directory, and named nothing for a payload program in a folder below it or for a program the payload does not install" +} finally { + Remove-Item -Recurse -Force $dir -ErrorAction SilentlyContinue +} +POWERSHELL + if ! PROBE_EXE="$probe" powershell -NoProfile -File "$probe_check"; then + rm -f "$probe_check" + exit 1 + fi + rm -f "$probe_check" + echo "::endgroup::" +fi + if [[ $MAKE_INSTALL ]]; then echo "::group::Install" cmake --build . --target install --config "$BUILDTYPE" diff --git a/.ci/docker.sh b/.ci/docker.sh index 412ad6a1d..64ea6baa7 100644 --- a/.ci/docker.sh +++ b/.ci/docker.sh @@ -175,6 +175,12 @@ function RUN () args+=(--mount "type=bind,source=$GITHUB_OUTPUT,target=/gh_output") args+=(--env "GITHUB_OUTPUT=/gh_output") fi + # The container does not inherit host env vars: without this, WITH_CRASH_REPORTING=ON set in + # the workflow never reaches compile.sh inside the container, so the Linux packages silently + # build with crash reporting OFF while the libcurl packages in the Dockerfiles go unused. + if [[ ${WITH_CRASH_REPORTING:-} ]]; then + args+=(--env "WITH_CRASH_REPORTING=${WITH_CRASH_REPORTING}") + fi # shellcheck disable=2086 docker run "${args[@]}" $RUN_ARGS "$IMAGE_NAME" bash "$BUILD_SCRIPT" $RUN_OPTS "$@" return $? diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 75fbc59f1..75494dc3e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -60,8 +60,10 @@ jobs: run: | sudo apt-get update sudo apt-get install -y --no-install-recommends \ + ca-certificates \ cmake \ g++ \ + libcurl4-openssl-dev \ libgl-dev \ liblzma-dev \ libmariadb-dev-compat \ diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index beedaeaf2..df054ea26 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -46,6 +46,11 @@ jobs: outputs: tag: ${{ steps.configure.outputs.tag }} sha: ${{ steps.configure.outputs.sha }} + # MAJ.MIN.PATCH[-beta[.N]] of a tag build; empty otherwise. Mirrors what + # cmake/getversion.cmake derives as PROJECT_VERSION, which the client bakes as its + # Sentry release (cmake/CrashReporting.cmake) — symbol uploads must use the same + # string so stack traces de-symbolicate against this build's binaries. + sentry_release: ${{ steps.configure.outputs.sentry_release }} steps: - name: "Configure" @@ -56,6 +61,9 @@ jobs: run: | if [[ "$GITHUB_REF_TYPE" == 'tag' ]]; then # release echo "tag=$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT" + # Tag format is YYYY-MM-DD-{Release|Development}-MAJ.MIN.PATCH[-beta[.N]]; + # PROJECT_VERSION is the tail after the type (see cmake/getversion.cmake). + echo "sentry_release=$(sed -E 's/^[0-9]{4}-[0-9]{2}-[0-9]{2}-(Release|Development)-//' <<< "$GITHUB_REF_NAME")" >> "$GITHUB_OUTPUT" fi echo "sha=$RESOLVED_SHA" >> "$GITHUB_OUTPUT" @@ -93,6 +101,36 @@ jobs: --title "$release_name" \ --notes-file "$body_path" + vcpkg-baseline: + name: vcpkg baseline + runs-on: ubuntu-slim + steps: + - name: "Checkout" + uses: actions/checkout@v7 + + # vcpkg-tool reads versions/baseline.json out of the commit named by + # `builtin-baseline`, through the `vcpkg` submodule's git history. A submodule + # pointer that lags the manifest - e.g. a stale `vcpkg/` worktree captured by + # `git add` in a PR - makes every vcpkg leg fail in configure with "failed to + # `git show` versions/baseline.json". That reads like a baseline or network + # problem, when the pointer is what has to move. Check the two agree here in + # seconds instead of on five build legs. + - name: "Check the submodule pointer against the manifest baseline" + shell: bash + run: | + pointer="$(git rev-parse HEAD:vcpkg 2>/dev/null || true)" + baseline="$(sed -nE 's/.*"builtin-baseline"[[:space:]]*:[[:space:]]*"([0-9a-f]{40})".*/\1/p' vcpkg.json)" + if [[ -z "$pointer" || -z "$baseline" ]]; then + echo "::error file=vcpkg.json::could not read the vcpkg submodule pointer or the builtin-baseline" + exit 1 + fi + if [[ "$pointer" != "$baseline" ]]; then + echo "::error file=vcpkg.json::vcpkg submodule is at $pointer but builtin-baseline is $baseline" + echo "Move them together: git -C vcpkg checkout $baseline && git add vcpkg" + exit 1 + fi + echo "vcpkg submodule and builtin-baseline agree on $baseline" + build-linux: strategy: fail-fast: false @@ -225,7 +263,7 @@ jobs: - name: "Upload coverage artifact" if: matrix.coverage == 'yes' - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: coverage-report path: coverage/ @@ -239,6 +277,12 @@ jobs: SUFFIX: '-${{ matrix.distro }}${{ matrix.version }}' package: '${{ matrix.package }}' server_only: '${{ matrix.server_only }}' + WITH_CRASH_REPORTING: 'ON' + # Tag builds only: artifacts from master pushes and PR branches must not report to + # Sentry. Forks get no secrets on pull_request, so they keep the mock fallback either way. + # configure.outputs.tag is set for ANY tag, so beta (Development) tags get the DSN + # and the symbol upload below just like full releases. + COCKATRICE_SENTRY_DSN: ${{ needs.configure.outputs.tag != null && secrets.SENTRY_DSN || '' }} run: | source .ci/docker.sh args=() @@ -247,9 +291,58 @@ jobs: args+=(--ccache "$CCACHE_SIZE") args+=(--cmake-generator "$CMAKE_GENERATOR") args+=(--suffix "$SUFFIX") + # The container inherits no environment, so the DSN rides in explicitly. -e without + # =value passes it through (possibly empty); compile.sh only consumes it for Release + # configures, and empty keeps the mock fallback. + export RUN_ARGS="${RUN_ARGS:-} -e COCKATRICE_SENTRY_DSN" RUN --server --release --package "$package" "${args[@]}" + - name: "Install sentry-cli" + if: matrix.package != 'skip' && needs.configure.outputs.tag != null + # Downloads from Sentry's release registry: a Sentry outage or a broken install under + # Git Bash must not take down the release assets below. + continue-on-error: true + shell: bash + env: + SENTRY_CLI_VERSION: '3.8.0' + run: | + export INSTALL_DIR="$GITHUB_WORKSPACE/.sentry-cli" + curl -sL https://sentry.io/get-cli/ | bash + echo "$GITHUB_WORKSPACE/.sentry-cli" >> "$GITHUB_PATH" + + - name: "Upload debug symbols to Sentry" + # Like the DSN above, this runs for every tag — beta (Development) tags included. + if: matrix.package != 'skip' && needs.configure.outputs.tag != null + # A Sentry outage or rotated token must not take down the release assets below. + continue-on-error: true + shell: bash + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + run: | + paths=() + # The binaries only, not the directories: a directory makes sentry-cli walk the + # whole build tree, where it finds (and uploads) object files and other fragments + # that can never match a stack frame. The packaged copy is split and stripped by + # cpack, but keeps this binary's GNU build-id, which is how Sentry matches it. + for app in cockatrice oracle servatrice; do + binary="build/$app/$app" + [[ -f $binary ]] && paths+=("$binary") + done + if [[ ${#paths[@]} == 0 ]]; then + # Fail loudly instead of skipping: see the Windows/macOS leg below — this step + # is the only way symbols reach Sentry and `continue-on-error` keeps the job + # green either way, so a silent skip looks exactly like a successful upload. + echo "::error::No debug symbol files found; nothing would be uploaded" + exit 1 + fi + # Project slug on Sentry; must match the project the baked DSN points at. + # No --release here: `debug-files upload` has no such option (sentry-cli errors on + # the unknown flag and uploads nothing), and debug information files are matched to + # events by debug ID via the event's debug_meta, not by release. The release grouping + # comes from the client event itself (COCKATRICE_SENTRY_RELEASE). + sentry-cli debug-files upload --org cockatrice --project cockatrice-desktop "${paths[@]}" + # Delete used cache to emulate a ccache update. See https://github.com/actions/cache/issues/342 - name: "Delete remote compiler cache (ccache)" if: github.ref == 'refs/heads/master' && steps.restore_ccache.outputs.cache-hit @@ -403,7 +496,7 @@ jobs: vcpkg_upload: 1 # only one leg per vcpkg_scope hands its archives over name: ${{ matrix.os }} ${{ matrix.target }}${{ matrix.soc == 'Intel' && ' Intel' || '' }}${{ matrix.type == 'Debug' && ' Debug' || '' }} - needs: configure + needs: [configure, vcpkg-baseline] runs-on: ${{ matrix.runner }} timeout-minutes: 100 env: @@ -521,11 +614,17 @@ jobs: # job. A scope nobody has written to yet therefore leaves the directory missing # and fails the job, which is exactly the state every scope starts in. Make the # directory up front so an empty scope is a cache miss instead of an error. + # always() covers the cache step itself failing: the action's post save still runs + # then, and it should report the real error instead of ENOENT. It cannot rescue a + # failure before the cache step, since a post step only runs for an action whose + # main step did. The fallback keeps this guard from failing when the cache step was + # skipped and left no path output. - name: "Create vcpkg cache directory" + if: always() shell: bash env: CACHE_DIR: '${{ steps.vcpkg-cache.outputs.path }}' - run: mkdir -p "$CACHE_DIR" + run: mkdir -p "${CACHE_DIR:-$GITHUB_WORKSPACE/.vcpkg-cache}" # Uses environment variables, see compile.sh for more details - name: "Build Cockatrice" @@ -535,6 +634,11 @@ jobs: BUILDTYPE: '${{ matrix.type }}' CCACHE_EVICTION_AGE: ${{ matrix.ccache_eviction_age }} CMAKE_GENERATOR: ${{ matrix.cmake_generator }} + # Tag builds only: artifacts from master pushes and PR branches must not report to + # Sentry. Forks get no secrets on pull_request, so they keep the mock fallback either way. + # configure.outputs.tag is set for ANY tag, so beta (Development) tags get the DSN + # and the symbol upload below just like full releases. + COCKATRICE_SENTRY_DSN: ${{ needs.configure.outputs.tag != null && secrets.SENTRY_DSN || '' }} DEVELOPER_DIR: '/Applications/Xcode_${{ matrix.xcode }}.app/Contents/Developer' GITHUB_TOKEN: ${{ github.token }} # needed for vcpkg dependency graph updates, see VCPKG_FEATURE_FLAGS MACOS_CERTIFICATE: ${{ secrets.PROD_MACOS_CERTIFICATE }} @@ -558,8 +662,125 @@ jobs: VCPKG_DISABLE_METRICS: 1 VCPKG_FEATURE_FLAGS: dependencygraph VCPKG_TARGET_MACOS_VERSION: ${{ matrix.vcpkg_deployment_target }} + # Release legs compile the sentry-native SDK; Debug legs stay SDK-free. + WITH_CRASH_REPORTING: ${{ matrix.type == 'Release' && 'ON' || '' }} run: .ci/compile.sh --server --test --vcpkg + - name: "Install sentry-cli" + if: needs.configure.outputs.tag != null && matrix.make_package == '1' + # Same as the Linux leg above: the install downloads from Sentry's registry, so a + # failure here must not block the release upload below. + continue-on-error: true + shell: bash + env: + SENTRY_CLI_VERSION: '3.8.0' + run: | + export INSTALL_DIR="$GITHUB_WORKSPACE/.sentry-cli" + curl -sL https://sentry.io/get-cli/ | bash + echo "$GITHUB_WORKSPACE/.sentry-cli" >> "$GITHUB_PATH" + + - name: "Upload debug symbols to Sentry" + # Like the DSN above, this runs for every tag — beta (Development) tags included. + if: needs.configure.outputs.tag != null && matrix.make_package == '1' + # A Sentry outage or rotated token must not take down the release assets below. + continue-on-error: true + shell: bash + env: + SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} + run: | + paths=() + if [[ '${{ matrix.os }}' == 'Windows' ]]; then + # Top-level CMakeLists.txt links /DEBUG even in release, so the linker PDBs + # land next to the binaries. Ninja is single-config, so that is + # build// and NOT build//Release/ — the same globs the symbol + # artifact step below uses. + # + # The executables are uploaded as well, in the same invocation. On 64-bit + # Windows the Call Frame Information (.pdata/.xdata) lives in the PE and is + # not copied into the PDB, so a PDB on its own leaves Sentry reporting + # "Stack Unwinding: Missing" for the image. sentry-cli also needs to see the + # PE and its PDB together: only then does it associate the PE's code + # identifier with the PDB's debug identifier. + shopt -s nullglob + for target in cockatrice oracle servatrice; do + paths+=("build/$target"/*.exe "build/$target"/*.pdb) + done + shopt -u nullglob + + # Qt's own runtime DLLs, taken from required-runtime.txt — the manifest + # .ci/compile.sh asserts the installer against — so the set uploaded is + # the set an install actually carries. The PE is the only carrier of + # unwind information for a 64-bit image, and sending it in the same + # invocation as a PDB is what lets sentry-cli associate the PE's code + # identifier with the PDB's debug identifier. Qt ships no PDBs with the + # SDK, so Qt frames resolve through the export table: names, no lines. + qt_dir= + qt_core_dir= + # Existence guards matter: the step runs under `bash -eo pipefail`, so a + # redirection-less sed over a missing file would abort the step outright + # and continue-on-error would hide it. + if [[ -f build/CMakeCache.txt ]]; then + qt_core_dir=$(sed -n 's/^Qt6Core_DIR:PATH=//p' build/CMakeCache.txt | head -1) + fi + if [[ -n $qt_core_dir ]]; then + qt_dir=$(dirname "$(dirname "$(dirname "$qt_core_dir")")") + fi + if [[ -z $qt_dir || ! -d $qt_dir ]]; then + # Warnings, not failures: the upload of our own binaries is what this + # step is for, and continue-on-error already means an upload failure + # cannot be told apart from a successful one. + echo "::warning::Qt runtime directory not resolved from build/CMakeCache.txt; Qt images get no unwind information" + elif [[ ! -f build/required-runtime.txt ]]; then + echo "::warning::build/required-runtime.txt is missing; cannot tell which Qt DLLs ship" + else + while IFS= read -r runtime; do + name=${runtime##*/} + if [[ $runtime == */* ]]; then + # A Qt plugin: the install root keeps it as plugins//. + # `|| true`: an unmatched glob makes ls exit non-zero, and pipefail + # turns that into a step abort. + runtime_path=$(ls "$qt_dir/plugins"/*/"$name" 2>/dev/null | head -1) || true + else + runtime_path="$qt_dir/bin/$name" + fi + if [[ -f ${runtime_path:-} ]]; then + paths+=("$runtime_path") + else + echo "::warning::shipped Qt runtime $runtime not found under $qt_dir" + fi + done < <(grep -E '^(Qt6[^/]*\.dll|Plugins/)' build/required-runtime.txt) + fi + else + # Release builds carry DWARF (-g, kept by CrashReporting.cmake); macOS keeps its + # debug info in dSYM bundles, so materialize them before uploading. + for app in cockatrice oracle servatrice; do + binary="build/$app/$app.app/Contents/MacOS/$app" + if [[ -f $binary ]]; then + if [[ '${{ matrix.os }}' == 'macOS' ]]; then + dsymutil "$binary" -o "$binary.dSYM" + paths+=("$binary.dSYM") + # The dSYM holds the debug information, but the unwind information + # Sentry needs to walk a minidump stack lives in the Mach-O itself. + paths+=("$binary") + else + paths+=("$binary") + fi + fi + done + fi + if [[ ${#paths[@]} == 0 ]]; then + # Fail loudly instead of skipping: this step is the only way symbols reach + # Sentry, and `continue-on-error` keeps the job green either way, so a + # silent skip is indistinguishable from a successful upload. It also hid a + # wrong Windows path (build//Release, which Ninja never creates). + echo "::error::No debug symbol files found; nothing would be uploaded" + exit 1 + fi + # Project slug on Sentry; must match the project the baked DSN points at. + # No --release: see the Linux leg above — the flag does not exist on + # `debug-files upload` and DIFs are matched by debug ID, not release. + sentry-cli debug-files upload --org cockatrice --project cockatrice-desktop "${paths[@]}" + # CPack reports a failed makensis run as "Problem running NSIS command" # plus a path to NSISOutput.log, and that log lives only on the runner, so # the actual compile error is otherwise nowhere in the workflow output. @@ -703,19 +924,6 @@ jobs: if-no-files-found: error path: ${{ steps.build.outputs.path }} - # Ninja is single-config: binaries and their PDBs land directly in the - # target directories instead of a Release subdirectory. - - name: "[Windows] Upload PDBs (Program Databases)" - if: matrix.os == 'Windows' && github.ref_type != 'tag' - uses: actions/upload-artifact@v7 - with: - if-no-files-found: error - name: ${{ steps.build.outputs.name }}-PDBs - path: | - build/cockatrice/*.pdb - build/oracle/*.pdb - build/servatrice/*.pdb - - name: "Upload to release" if: needs.configure.outputs.tag != null && matrix.make_package == '1' id: upload_release @@ -727,6 +935,33 @@ jobs: tag_name: ${{ needs.configure.outputs.tag }} run: gh release upload "$tag_name" "$asset_path#$asset_name" + # Ninja is single-config: binaries and their PDBs land directly in the + # target directories instead of a Release subdirectory. + # + # Archived for tag builds only, and ordered after the release upload so + # a missing PDB can never block a release from being published. On a tag + # build this artifact is the only durable copy of the symbols: the Sentry + # upload is continue-on-error, so without it a failed upload would lose + # the ability to symbolicate that release permanently. Other builds upload + # no symbols at all, so archiving them there would only cost storage. + # + # The executables travel with their PDBs: re-uploading the pair together is + # what lets sentry-cli associate the PE's code identifier with the PDB's + # debug identifier, and the PE is what carries the unwind information. + - name: "[Windows] Upload debug symbols (exe + pdb)" + if: matrix.os == 'Windows' && needs.configure.outputs.tag != null + uses: actions/upload-artifact@v7 + with: + if-no-files-found: error + name: ${{ steps.build.outputs.name }}-Symbols + path: | + build/cockatrice/*.exe + build/cockatrice/*.pdb + build/oracle/*.exe + build/oracle/*.pdb + build/servatrice/*.exe + build/servatrice/*.pdb + - name: "Attest binary provenance" if: steps.upload_release.outcome == 'success' id: attestation diff --git a/.tx/github_integration_config.yml b/.tx/github_integration_config.yml new file mode 100644 index 000000000..2586b4800 --- /dev/null +++ b/.tx/github_integration_config.yml @@ -0,0 +1,16 @@ +# Integration Docs: https://help.transifex.com/en/articles/6265125-github-installation-and-configuration#h_de29ef5cec +# Config Parameter: https://help.transifex.com/en/articles/6265125-github-installation-and-configuration#h_a734c3e219 +# Supported Files: https://help.transifex.com/en/collections/3519116-file-formats + +filters: + - filter_type: file + file_format: QT + source_language: en + source_file: cockatrice/cockatrice_en@source.ts + translation_files_expression: cockatrice/translations/cockatrice_.ts + + - filter_type: file + file_format: QT + source_language: en + source_file: oracle/oracle_en@source.ts + translation_files_expression: oracle/translations/oracle_.ts diff --git a/CMakeLists.txt b/CMakeLists.txt index bee47972c..8a25fe2c6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -90,7 +90,7 @@ endif() # A project name is needed for CPack # Version can be overriden by git tags, see cmake/getversion.cmake -project("Cockatrice" VERSION 3.1.2) +project("Cockatrice" VERSION 3.2.0) # Set release name if not provided via env/cmake var if(NOT DEFINED GIT_TAG_RELEASENAME) @@ -220,6 +220,7 @@ endif() # when ENABLE_SANITIZERS or ENABLE_COVERAGE is on. include(Sanitizers) include(Coverage) +include(CrashReporting) # GNU systems need to define the Mersenne exponent for the RNG to compile w/o warning if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") @@ -249,7 +250,22 @@ message(STATUS "Update Translations: ${UPDATE_TRANSLATIONS}") include(FindQtRuntime) +# Reject APIs that were deprecated before the oldest Qt release we support +# (6.4). Qt 6.5 renamed the switch, so use whichever spelling the found +# version understands while still compiling everywhere. +if(Qt6_VERSION VERSION_GREATER_EQUAL 6.5) + add_compile_definitions(QT_DISABLE_DEPRECATED_UP_TO=0x060400) +else() + add_compile_definitions(QT_DISABLE_DEPRECATED_BEFORE=0x060400) +endif() + +# Set once here for every target: these variables are inherited by the +# subdirectories, so the per-directory copies were redundant. Only the +# variables are set; qt_standard_project_setup would also change the runtime +# output directories and the install RPATH. set(CMAKE_AUTOMOC TRUE) +set(CMAKE_AUTOUIC TRUE) +set(CMAKE_AUTORCC TRUE) # Find other needed libraries find_package(Protobuf CONFIG) @@ -327,7 +343,7 @@ if(UNIX) endif() elseif(WIN32) set(CPACK_GENERATOR NSIS ${CPACK_GENERATOR}) - if("${CMAKE_GENERATOR_PLATFORM}" MATCHES "(x64)") + if(CMAKE_SIZEOF_VOID_P EQUAL 8) set(TRICE_IS_64_BIT 1) else() set(TRICE_IS_64_BIT 0) @@ -336,6 +352,15 @@ elseif(WIN32) # Configure file with custom definitions for NSIS. configure_file("${COCKATRICE_CMAKE_PATH}/NSIS.definitions.nsh.in" "${PROJECT_BINARY_DIR}/NSIS.definitions.nsh") + # Installer-log sender for the AbortInstall Sentry hook (values parsed out of COCKATRICE_SENTRY_DSN in + # cmake/CrashReporting.cmake). Generated only when a DSN was parsed: cmake/NSIS.template.in embeds it under + # the same CPACK_SENTRY_ENABLED check, so a build without reporting never references or carries the helper. + if(CPACK_SENTRY_ENABLED) + configure_file( + "${COCKATRICE_CMAKE_PATH}/SendInstallerLog.ps1.in" "${PROJECT_BINARY_DIR}/SendInstallerLog.ps1" @ONLY + ) + endif() + # Every runtime DLL a Windows install has to contain, as paths relative to the # install root. .ci/compile.sh reads this and fails the build when the packaged # installer is missing one of them, or carries a runtime it does not know about. @@ -410,6 +435,9 @@ if(WITH_ORACLE OR WITH_CLIENT) add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_models ${CMAKE_BINARY_DIR}/libcockatrice_models) add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_filters ${CMAKE_BINARY_DIR}/libcockatrice_filters) endif() +if(WITH_CLIENT) + add_subdirectory(${CMAKE_SOURCE_DIR}/libcockatrice_game ${CMAKE_BINARY_DIR}/libcockatrice_game) +endif() if(WITH_SERVER) add_subdirectory(servatrice) @@ -421,6 +449,13 @@ if(WITH_CLIENT) set(CPACK_INSTALL_CMAKE_PROJECTS "Cockatrice;Cockatrice;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS}) endif() +if(WIN32 AND WITH_CLIENT) + # Runs from the installer's $PLUGINSDIR when a purge fails, and is never installed + # (see LogProcessesUsingInstallDir in cmake/NSIS.template.in), so it has no install + # rules and is not part of the client's staged tree. + add_subdirectory(${CMAKE_SOURCE_DIR}/tools/installer-holder-probe ${CMAKE_BINARY_DIR}/tools/installer-holder-probe) +endif() + if(WITH_ORACLE) add_subdirectory(oracle) set(CPACK_INSTALL_CMAKE_PROJECTS "Oracle;Oracle;ALL;/" ${CPACK_INSTALL_CMAKE_PROJECTS}) @@ -433,5 +468,5 @@ endif() if(Qt6_FOUND AND Qt6_VERSION_MINOR GREATER_EQUAL 3) # Qt6.3+ requires project finalization to support translations - qt6_finalize_project() + qt_finalize_project() endif() diff --git a/README.md b/README.md index f2a170868..fa9272c0f 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,10 @@ ---

-To get started with Cockatrice ⇢ [view our webpage](https://cockatrice.github.io/)
-To get support, or suggest changes to the app ⇢ [file an issue](https://github.com/Cockatrice/Cockatrice/issues) ([How?](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice)) -To help with development ⇢ learn [how to contribute](#contribute-) +To get started with Cockatrice ⇢ [view our webpage](https://cockatrice.github.io/) +To get support, or suggest changes to the app ⇢ [file an issue](https://github.com/Cockatrice/Cockatrice/issues) ([How?](https://github.com/Cockatrice/Cockatrice/wiki/How-to-Create-a-GitHub-Ticket-Regarding-Cockatrice))
+To learn about the app and its code ⇢ [read our code documentation](https://cockatrice.github.io/docs/) +To help with development ⇢ learn [how to contribute](#contribute)


@@ -83,7 +84,7 @@ Join our [Discord community](https://discord.gg/3Z9yzmA) to connect with other p ### Code -To contribute code to the project, please review our [guidelines](https://github.com/Cockatrice/Cockatrice/blob/master/.github/CONTRIBUTING.md) first.
+To commit code to the project, please review our [Contribution Guidelines](https://github.com/Cockatrice/Cockatrice/blob/master/.github/CONTRIBUTING.md) first.
We maintain two tags for contributors to easier find issues to potentially work on: - [![Good first issue](https://img.shields.io/github/issues/cockatrice/cockatrice/Good%20First%20Issue)](https://github.com/Cockatrice/Cockatrice/issues?utf8=%E2%9C%93&q=is%3Aopen%20is%3Aissue%20label%3A%22Good%20first%20issue%22%20)
Issues tagged in this way provide a simple way to get started. They don't require much experience to be worked on. @@ -95,7 +96,7 @@ We'll happily advice on how best to implement a feature, or we can show you wher You can also have a look at our `Todo List` in our [Code Documentation](https://cockatrice.github.io/docs) or search the repo for [`\todo` comments](https://github.com/search?q=repo%3ACockatrice%2FCockatrice%20%5Ctodo&type=code). -### Documentation [![CI Docs](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml/badge.svg?event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml?query=event%3Apush) +### Documentation [![CI Docs](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml/badge.svg?event=push)](https://github.com/Cockatrice/Cockatrice/actions/workflows/documentation-build.yml?query=event%3Arelease+event%3Aworkflow_dispatch) There are various places where useful information for different needs are maintained: - [Official Code Documentation](https://cockatrice.github.io/docs/) @@ -104,7 +105,8 @@ There are various places where useful information for different needs are mainta - [Official README](https://github.com/Cockatrice/Cockatrice/blob/master/README.md) `This file` - [Official CONTRIBUTING](https://github.com/Cockatrice/Cockatrice/blob/master/.github/CONTRIBUTING.md) -Cockatrice tries to use the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation. We encourage you to improve the documentation by suggesting edits based on this guide. +Cockatrice tries to use the [Google Developer Documentation Style Guide](https://developers.google.com/style/) to ensure consistent documentation. +We encourage everybody to improve the documentation by suggesting edits based on this guide. ### Translation [![Transifex Project](https://img.shields.io/badge/translate-on%20transifex-brightgreen)](https://explore.transifex.com/cockatrice/cockatrice/) diff --git a/cmake/CrashReporting.cmake b/cmake/CrashReporting.cmake new file mode 100644 index 000000000..6f9cf4849 --- /dev/null +++ b/cmake/CrashReporting.cmake @@ -0,0 +1,212 @@ +# Crash reporting via sentry-native (Sentry protocol, MIT). +# Included once from the root CMakeLists.txt, right after the sanitizer/coverage helpers. +# +# Inputs: +# WITH_CRASH_REPORTING -- build the client with the sentry-native SDK (default OFF, our release +# CI passes ON; packagers and from-source builds stay SDK-free until this +# ships something real, so offline package builds never fetch the SDK) +# COCKATRICE_SENTRY_DSN -- Sentry DSN baked into the build (default empty = SDK init skipped at runtime) +# COCKATRICE_SENTRY_TAG -- sentry-native release tag to vendor via FetchContent +# ENABLE_SANITIZERS -- forces the sentry backend to "none" (crashpad vs ASan conflict) +# Defines: cockatrice_target_enable_crash_reporting() + +option(WITH_CRASH_REPORTING "Build with crash reporting via sentry-native" OFF) +set(COCKATRICE_SENTRY_DSN + "" + CACHE STRING "Sentry DSN baked into the client; empty disables crash-report upload at runtime" +) +# Pinned stable release; tag verified at https://github.com/getsentry/sentry-native/tags. +set(COCKATRICE_SENTRY_TAG + "0.17.3" + CACHE STRING "sentry-native release tag to vendor via FetchContent" +) + +# Wire the SDK (or the no-op fallback) into a client target. Always callable: with the +# option OFF the compile definitions below make crash_reporter.cpp compile to no-ops. +function(cockatrice_target_enable_crash_reporting _target) + if(WITH_CRASH_REPORTING AND TARGET sentry::sentry) + target_link_libraries(${_target} PRIVATE sentry::sentry) + target_compile_definitions(${_target} PRIVATE COCKATRICE_WITH_CRASH_REPORTING=1) + else() + target_compile_definitions(${_target} PRIVATE COCKATRICE_WITH_CRASH_REPORTING=0) + endif() + # Always baked in (possibly empty): main() prefers the COCKATRICE_SENTRY_DSN env var at runtime. + target_compile_definitions(${_target} PRIVATE "COCKATRICE_SENTRY_DSN=\"${COCKATRICE_SENTRY_DSN}\"") + # Sentry release for events from this target: PROJECT_VERSION (getversion.cmake ran before this + # file was included). Deliberately not PROJECT_VERSION_FRIENDLY / VERSION_STRING — that carries + # a build date, so every build of the same version would land in a different Sentry release. + # PROJECT_VERSION matches what the installer reports in its own log and what CPack derives the + # package version from, so client and installer events group under one release. + target_compile_definitions(${_target} PRIVATE "COCKATRICE_SENTRY_RELEASE=\"${PROJECT_VERSION}\"") +endfunction() + +if(WITH_CRASH_REPORTING AND WITH_CLIENT) + include(FetchContent) + + if(ENABLE_SANITIZERS) + # The crash handler cannot run under ASan/UBSan, so there is no backend to capture with. + set(_sentry_backend "none") + else() + # Breakpad on every OS (increment 3 probe, 2026-10-14): the crashpad sources + # do vendor friction-free (FetchContent inits all submodules recursively by + # default, verified populated in a local build tree), but the crashpad backend + # would need its out-of-process crashpad_handler binary installed next to the + # exe, into the macOS bundle, and through NSIS, plus handler-path wiring and + # notarization care. Breakpad is in-process, needs no extra binary and no + # packaging on any OS sentry-native builds, so simplicity wins. + set(_sentry_backend "breakpad") + endif() + # FORCE so toggling ENABLE_SANITIZERS on a reused build dir cannot leave a stale backend behind. + set(SENTRY_BACKEND + "${_sentry_backend}" + CACHE STRING "Crash handler backend for sentry-native" FORCE + ) + unset(_sentry_backend) + + if(NOT MSVC) + # Symbol upload needs debug info in Release binaries: the top-level flags build GCC + # Release as -s -O2 (stripped, no DWARF) and Clang/Apple Release as plain -O2 (no DWARF), + # both of which upload nothing useful. Keep -g and drop the strip flag while the SDK is + # on; the OFF default leaves local, distro and Debug builds untouched. MSVC already + # links /DEBUG with PDBs in release (see top-level CMakeLists.txt), so it needs nothing. + string(REGEX REPLACE "(^| )-s( |$)" "\\1\\2" CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}") + if(NOT CMAKE_CXX_FLAGS_RELEASE MATCHES "(^| )-g( |$)") + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -g") + endif() + endif() + + # No Qt integration: it runs find_package(Qt6) inside the vendored build, which + # cannot see our thin/aqt Qt layouts (macOS CI) under foreign toolchains, and its + # breadcrumbs could carry unredacted log lines into native crash events that bypass + # before_send. Our attached redacted log preview already carries the log context. + set(SENTRY_INTEGRATION_QT + OFF + CACHE BOOL "Qt log-message breadcrumbs for sentry-native (off: discovery + secret surface)" FORCE + ) + # The transport stays at the SDK default (curl on Apple/Linux, winhttp on Windows). + set(SENTRY_BUILD_TESTS + OFF + CACHE BOOL "Build sentry-native tests" FORCE + ) + set(SENTRY_BUILD_EXAMPLES + OFF + CACHE BOOL "Build sentry-native examples" FORCE + ) + + fetchcontent_declare( + sentry + GIT_REPOSITORY https://github.com/getsentry/sentry-native.git + GIT_TAG ${COCKATRICE_SENTRY_TAG} + GIT_SHALLOW TRUE + ) + fetchcontent_makeavailable(sentry) + + # Vendored sentry-native (and its breakpad/crashpad trees) warn under our -Wall -Wextra + # (e.g. breakpad's -Wmissing-field-initializers), and WARNING_AS_ERROR would fail the build + # on code we cannot fix. Those findings are not actionable for us, so opt the whole vendored + # tree out of warnings-as-errors while keeping the warnings themselves visible; our own + # targets keep -Werror. Interface libraries compile nothing, so they are skipped outright: + # an INTERFACE -Wno-error would propagate to our consumers and weaken their build. + function(_cockatrice_sentry_opt_out_of_werror _dir) + get_property( + _sentry_targets + DIRECTORY "${_dir}" + PROPERTY BUILDSYSTEM_TARGETS + ) + foreach(_sentry_target IN LISTS _sentry_targets) + get_target_property(_sentry_type ${_sentry_target} TYPE) + if(_sentry_type STREQUAL "INTERFACE_LIBRARY") + continue() + elseif(_sentry_type MATCHES "LIBRARY|EXECUTABLE") + get_target_property(_sentry_imported ${_sentry_target} IMPORTED) + if(NOT _sentry_imported) + target_compile_options(${_sentry_target} PRIVATE -Wno-error) + endif() + endif() + endforeach() + get_property( + _sentry_subdirs + DIRECTORY "${_dir}" + PROPERTY SUBDIRECTORIES + ) + foreach(_sentry_subdir IN LISTS _sentry_subdirs) + _cockatrice_sentry_opt_out_of_werror("${_sentry_subdir}") + endforeach() + endfunction() + if(NOT MSVC) + _cockatrice_sentry_opt_out_of_werror("${sentry_SOURCE_DIR}") + endif() + + message(STATUS "Crash reporting: sentry-native ${COCKATRICE_SENTRY_TAG} (backend: ${SENTRY_BACKEND})") +else() + message(STATUS "Crash reporting: SDK not vendored " + "(WITH_CRASH_REPORTING=${WITH_CRASH_REPORTING}, WITH_CLIENT=${WITH_CLIENT})" + ) +endif() + +# Packaged release binaries must not ship the DWARF that the symbol upload needs: it is +# 80-90% of an unstripped binary and nobody downloads a 500 MB installer to get it. The +# build tree keeps -g (CI uploads symbols from there); the *packaged copy* is split at +# install time by a CPack install script, which extracts the debug info next to the staging +# dir and strips the binary. Only ELF (Linux/BSD) needs this: Windows links /DEBUG with +# separate PDBs, and macOS keeps its dSYM extracted from the bundle. +option(COCKATRICE_SPLIT_DEBUG_SYMBOLS "Strip DWARF out of packaged release binaries" ON) +set(COCKATRICE_DEBUG_SYMBOL_DIR + "${CMAKE_BINARY_DIR}/debug-symbols" + CACHE PATH "Where the packaged binaries' split debug information is written (kept out of the package)" +) +if(WITH_CRASH_REPORTING + AND WITH_CLIENT + AND COCKATRICE_SPLIT_DEBUG_SYMBOLS + AND CMAKE_BUILD_TYPE STREQUAL "Release" + AND UNIX + AND NOT APPLE +) + find_program(COCKATRICE_OBJCOPY NAMES objcopy) + find_program(COCKATRICE_STRIP_TOOL NAMES strip) + if(COCKATRICE_OBJCOPY AND COCKATRICE_STRIP_TOOL) + configure_file( + "${CMAKE_CURRENT_LIST_DIR}/SplitDebugSymbols.cmake.in" "${CMAKE_BINARY_DIR}/cmake/SplitDebugSymbols.cmake" @ONLY + ) + # PRE_BUILD runs after CPack stages the install tree and before the package is produced. + # CPACK_INSTALL_SCRIPTS would run too early (staging is still empty); the script walks + # CPACK_TEMPORARY_DIRECTORY, never CMAKE_INSTALL_PREFIX, and refuses to run otherwise. + list(APPEND CPACK_PRE_BUILD_SCRIPTS "${CMAKE_BINARY_DIR}/cmake/SplitDebugSymbols.cmake") + message(STATUS "Crash reporting: packaged binaries stripped, debug info split to ${COCKATRICE_DEBUG_SYMBOL_DIR}") + else() + message(WARNING "COCKATRICE_SPLIT_DEBUG_SYMBOLS is ON but objcopy/strip were not found; " + "packaged release binaries will keep their debug information" + ) + endif() +endif() + +# Installer failure reporting (increment 4): parse COCKATRICE_SENTRY_DSN into the parts the Windows installer +# needs. These are CPACK_* so CPack snapshots them at include(CPack) time and substitutes them into +# cmake/NSIS.template.in at package time; on non-Windows they are inert (only the NSIS template consumes them). +# Set from COCKATRICE_SENTRY_DSN on every configure, so a changed or removed DSN can never leave a stale value +# behind. Plain variables, not CACHE: they only need to be in scope when include(CPack) later snapshots every +# CPACK_* variable into CPackConfig.cmake. +set(CPACK_SENTRY_ENABLED "0") +set(CPACK_SENTRY_KEY "") +set(CPACK_SENTRY_ENVELOPE_URL "") +set(CPACK_SENTRY_DSN_FULL "") +# Deliberately PROJECT_VERSION: that is what the client bakes as its Sentry release and what CI passes to +# `sentry-cli debug-files upload`, so installer and client events group under one release. It is not the +# CPACK_PACKAGE_VERSION the installer logs in its own log line — on Development tags PROJECT_VERSION carries a +# "-

Run Oracle now?

"); - const QMessageBox::StandardButton answer = QMessageBox::question( - this, tr("Card text & images language changed"), message, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); - // The answer only controls whether Oracle starts right away; the caches stay // cleared so the next import or launch rebuilds them in the new language. - if (answer == QMessageBox::Yes) { + if (QtUtils::confirm(this, tr("Card text & images language changed"), message, QMessageBox::Yes)) { emit cardDatabaseUpdateRequested(); } } @@ -556,7 +568,7 @@ void GeneralSettingsPage::retranslateUi() const auto &settings = SettingsCache::instance(); languageGroupBox->setTitle(tr("Language settings")); - languageLabel.setText(tr("Language:")); + languageLabel.setText(tr("Interface language:")); cardLanguageBox.setItemText(0, tr("English")); cardLanguageLabel.setText(tr("Card text & images language:")); cardLanguageNoteLabel.setText( @@ -569,13 +581,19 @@ void GeneralSettingsPage::retranslateUi() tr("English and selected card language")); advertiseTranslationPageLabel.setText( QString("%2").arg(WIKI_TRANSLATION_FAQ).arg(tr("How to help with translations"))); - versionGroupBox->setTitle(tr("Version settings")); updateReleaseChannelLabel.setText(tr("Update channel")); startupUpdateCheckCheckBox.setText(tr("Check for client updates on startup")); updateNotificationCheckBox.setText(tr("Notify if a feature supported by the server is missing in my client")); newVersionOracleCheckBox.setText(tr("Automatically run Oracle when running a new version of Cockatrice")); + crashReportingGroupBox->setTitle(tr("Crash reporting")); + crashReportingLabel.setText(tr("Send crash reports:")); + crashReportingBox.setItemText(static_cast(PersonalSettings::CrashReportingMode::Ask), tr("Ask every time")); + crashReportingBox.setItemText(static_cast(PersonalSettings::CrashReportingMode::AlwaysSend), + tr("Always send")); + crashReportingBox.setItemText(static_cast(PersonalSettings::CrashReportingMode::NeverSend), tr("Never send")); + // We can't change the strings after they're put into the QComboBox, so this is our workaround int oldIndex = updateReleaseChannelBox.currentIndex(); updateReleaseChannelBox.clear(); diff --git a/cockatrice/src/interface/widgets/settings_page/general_settings_page.h b/cockatrice/src/interface/widgets/settings_page/general_settings_page.h index fcf1c78d0..9451db434 100644 --- a/cockatrice/src/interface/widgets/settings_page/general_settings_page.h +++ b/cockatrice/src/interface/widgets/settings_page/general_settings_page.h @@ -26,7 +26,6 @@ public: void retranslateUi() override; static QStringList findQmFiles(); - static QString languageName(const QString &lang); signals: /// Request to re-import the card database with the newly selected card language @@ -49,6 +48,7 @@ private slots: private: QGroupBox *languageGroupBox; QGroupBox *versionGroupBox; + QGroupBox *crashReportingGroupBox; QGroupBox *cardDatabaseGroupBox; QGroupBox *startupGroupBox; QGroupBox *pathsGroupBox; @@ -70,6 +70,9 @@ private: QCheckBox updateNotificationCheckBox; QCheckBox newVersionOracleCheckBox; + QLabel crashReportingLabel; + QComboBox crashReportingBox; + QLabel startupCardUpdateCheckBehaviorLabel; QComboBox startupCardUpdateCheckBehaviorSelector; QLabel cardUpdateCheckIntervalLabel; diff --git a/cockatrice/src/interface/widgets/settings_page/messages_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/messages_settings_page.cpp index 9494155af..6128a7830 100644 --- a/cockatrice/src/interface/widgets/settings_page/messages_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/messages_settings_page.cpp @@ -118,6 +118,26 @@ MessagesSettingsPage::MessagesSettingsPage() highlightGroupBox = new QGroupBox; highlightGroupBox->setLayout(highlightNotice); + channelUnreadColor = new QLineEdit(); + channelUnreadColor->setText(SettingsCache::instance().chat().getChannelUnreadColor()); + updateChannelUnreadPreview(); + connect(channelUnreadColor, &QLineEdit::textChanged, this, &MessagesSettingsPage::updateChannelUnreadColor); + + channelMentionColor = new QLineEdit(); + channelMentionColor->setText(SettingsCache::instance().chat().getChannelMentionColor()); + updateChannelMentionPreview(); + connect(channelMentionColor, &QLineEdit::textChanged, this, &MessagesSettingsPage::updateChannelMentionColor); + + auto *channelGrid = new QGridLayout; + channelGrid->addWidget(&channelUnreadColorLabel, 0, 0); + channelGrid->addWidget(channelUnreadColor, 0, 1); + channelGrid->addWidget(&hexChannelUnreadLabel, 0, 2); + channelGrid->addWidget(&channelMentionColorLabel, 1, 0); + channelGrid->addWidget(channelMentionColor, 1, 1); + channelGrid->addWidget(&hexChannelMentionLabel, 1, 2); + channelGroupBox = new QGroupBox; + channelGroupBox->setLayout(channelGrid); + messageList = new QListWidget; int count = SettingsCache::instance().messages().getCount(); @@ -159,6 +179,7 @@ MessagesSettingsPage::MessagesSettingsPage() mainLayout->addWidget(messageGroupBox); mainLayout->addWidget(chatGroupBox); mainLayout->addWidget(highlightGroupBox); + mainLayout->addWidget(channelGroupBox); setLayout(mainLayout); @@ -195,6 +216,34 @@ void MessagesSettingsPage::updateHighlightColor(const QString &value) } } +void MessagesSettingsPage::updateChannelUnreadColor(const QString &value) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)) + QColor colorToSet = QColor::fromString("#" + value); +#else + QColor colorToSet; + colorToSet.setNamedColor("#" + value); +#endif + if (colorToSet.isValid()) { + SettingsCache::instance().chat().setChannelUnreadColor(value); + updateChannelUnreadPreview(); + } +} + +void MessagesSettingsPage::updateChannelMentionColor(const QString &value) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)) + QColor colorToSet = QColor::fromString("#" + value); +#else + QColor colorToSet; + colorToSet.setNamedColor("#" + value); +#endif + if (colorToSet.isValid()) { + SettingsCache::instance().chat().setChannelMentionColor(value); + updateChannelMentionPreview(); + } +} + void MessagesSettingsPage::updateTextColor(QT_STATE_CHANGED_T value) { SettingsCache::instance().chat().setChatMentionForeground(value); @@ -221,6 +270,18 @@ void MessagesSettingsPage::updateHighlightPreview() ";color: " + (SettingsCache::instance().chat().getChatHighlightForeground() ? "white" : "black") + ";}"); } +void MessagesSettingsPage::updateChannelUnreadPreview() +{ + channelUnreadColor->setStyleSheet("QLineEdit{background:#" + + SettingsCache::instance().chat().getChannelUnreadColor() + ";}"); +} + +void MessagesSettingsPage::updateChannelMentionPreview() +{ + channelMentionColor->setStyleSheet("QLineEdit{background:#" + + SettingsCache::instance().chat().getChannelMentionColor() + ";}"); +} + void MessagesSettingsPage::storeSettings() { SettingsCache::instance().messages().setCount(messageList->count()); @@ -267,6 +328,9 @@ void MessagesSettingsPage::retranslateUi() { chatGroupBox->setTitle(tr("Chat settings")); highlightGroupBox->setTitle(tr("Custom alert words")); + channelGroupBox->setTitle(tr("Room channel tabs")); + channelUnreadColorLabel.setText(tr("Unread messages color")); + channelMentionColorLabel.setText(tr("Mentioned messages color")); chatMentionCheckBox.setText(tr("Enable chat mentions")); chatMentionCompleterCheckbox.setText(tr("Enable mention completer")); messageGroupBox->setTitle(tr("In-game message macros")); @@ -284,6 +348,8 @@ void MessagesSettingsPage::retranslateUi() useGameTimeCheckBox.setText(tr("Use game time instead of local time in game logs")); hexLabel.setText(tr("(Color is hexadecimal)")); hexHighlightLabel.setText(tr("(Color is hexadecimal)")); + hexChannelUnreadLabel.setText(tr("(Color is hexadecimal)")); + hexChannelMentionLabel.setText(tr("(Color is hexadecimal)")); customAlertStringLabel.setText(tr("Separate words with a space, alphanumeric characters only")); customAlertString->setPlaceholderText(tr("Word1 Word2 Word3")); aAdd->setText(tr("Add New Message")); diff --git a/cockatrice/src/interface/widgets/settings_page/messages_settings_page.h b/cockatrice/src/interface/widgets/settings_page/messages_settings_page.h index e85e6cca7..112ff4e29 100644 --- a/cockatrice/src/interface/widgets/settings_page/messages_settings_page.h +++ b/cockatrice/src/interface/widgets/settings_page/messages_settings_page.h @@ -27,6 +27,8 @@ private slots: void actRemove(); void updateColor(const QString &value); void updateHighlightColor(const QString &value); + void updateChannelUnreadColor(const QString &value); + void updateChannelMentionColor(const QString &value); void updateTextColor(QT_STATE_CHANGED_T value); void updateTextHighlightColor(QT_STATE_CHANGED_T value); @@ -49,18 +51,27 @@ private: QCheckBox useGameTimeCheckBox; QGroupBox *chatGroupBox; QGroupBox *highlightGroupBox; + QGroupBox *channelGroupBox; QGroupBox *messageGroupBox; QLineEdit *mentionColor; QLineEdit *highlightColor; + QLineEdit *channelUnreadColor; + QLineEdit *channelMentionColor; QLineEdit *customAlertString; QLabel hexLabel; QLabel hexHighlightLabel; + QLabel channelUnreadColorLabel; + QLabel channelMentionColorLabel; + QLabel hexChannelUnreadLabel; + QLabel hexChannelMentionLabel; QLabel customAlertStringLabel; QLabel explainMessagesLabel; void storeSettings(); void updateMentionPreview(); void updateHighlightPreview(); + void updateChannelUnreadPreview(); + void updateChannelMentionPreview(); }; #endif // COCKATRICE_MESSAGES_SETTINGS_PAGE_H diff --git a/cockatrice/src/interface/widgets/settings_page/shortcut_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/shortcut_settings_page.cpp index ea7ed745c..a77c0c2c3 100644 --- a/cockatrice/src/interface/widgets/settings_page/shortcut_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/shortcut_settings_page.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include ShortcutSettingsPage::ShortcutSettingsPage() @@ -112,16 +113,15 @@ void ShortcutSettingsPage::currentItemChanged(const QString &key) void ShortcutSettingsPage::resetShortcuts() { - if (QMessageBox::question(this, tr("Restore all default shortcuts"), - tr("Do you really want to restore all default shortcuts?")) == QMessageBox::Yes) { + if (QtUtils::confirm(this, tr("Restore all default shortcuts"), + tr("Do you really want to restore all default shortcuts?"))) { SettingsCache::instance().shortcuts().resetAllShortcuts(); } } void ShortcutSettingsPage::clearShortcuts() { - if (QMessageBox::question(this, tr("Clear all default shortcuts"), - tr("Do you really want to clear all shortcuts?")) == QMessageBox::Yes) { + if (QtUtils::confirm(this, tr("Clear all default shortcuts"), tr("Do you really want to clear all shortcuts?"))) { SettingsCache::instance().shortcuts().clearAllShortcuts(); } } diff --git a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp index 0a5f57e1f..44ce01aba 100644 --- a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include enum visualDeckStoragePromptForConversionIndex @@ -248,8 +249,7 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() updateCommanderSpellbookUiState(); - auto *labelLayout = new QHBoxLayout; - labelLayout->setContentsMargins(0, 0, 0, 0); + auto *labelLayout = QtUtils::makeLayout(); labelLayout->addWidget(&commanderSpellbookIntegrationUseOfficialBracketNamesLabel); labelLayout->addWidget(&commanderSpellbookIntegrationUseOfficialBracketNamesExplainer); diff --git a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp index 4044f03db..1e80b2715 100644 --- a/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp +++ b/cockatrice/src/interface/widgets/tabs/abstract_tab_deck_editor.cpp @@ -11,9 +11,7 @@ #include "../../../client/settings/cache_settings.h" #include "../../../client/settings/shortcuts_settings.h" -#include "../../deck_loader/deck_file_format.h" #include "../../deck_loader/deck_loader.h" -#include "../../deck_loader/loaded_deck.h" #include "../cards/additional_info/deck_color_identity.h" #include "../cards/card_info_frame_widget.h" #include "../client/network/interfaces/deck_stats_interface.h" @@ -55,6 +53,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp index 555ec7ec5..46d2466c6 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_display_widget.cpp @@ -2,7 +2,6 @@ #include "../../../../../../client/settings/cache_settings.h" #include "../../../../../deck_loader/card_node_function.h" -#include "../../../../../deck_loader/loaded_deck.h" #include "../../../../cards/deck_card_zone_display_widget.h" #include "../../../../visual_deck_editor/visual_deck_display_options_widget.h" #include "../../../../visual_deck_editor/visual_deck_editor_widget.h" @@ -30,7 +29,9 @@ #include #include #include +#include #include +#include #include #include @@ -66,10 +67,7 @@ ArchidektApiResponseDeckDisplayWidget::ArchidektApiResponseDeckDisplayWidget(QWi connect(displayOptionsWidget, &VisualDeckDisplayOptionsWidget::groupCriteriaChanged, this, &ArchidektApiResponseDeckDisplayWidget::onGroupCriteriaChange); - scrollArea = new QScrollArea(this); - scrollArea->setWidgetResizable(true); - scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea = QtUtils::makeScrollArea(this); layout->addWidget(scrollArea); @@ -240,7 +238,5 @@ void ArchidektApiResponseDeckDisplayWidget::constructZoneWidgetsFromDeckListMode void ArchidektApiResponseDeckDisplayWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); - layout->invalidate(); - layout->activate(); - layout->update(); + QtUtils::relayout(layout); } diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp index b5e28fcf5..b22dac8de 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_entry_display_widget.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -241,9 +242,7 @@ void ArchidektApiResponseDeckEntryDisplayWidget::updateScaledPreview() void ArchidektApiResponseDeckEntryDisplayWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); - layout->invalidate(); - layout->activate(); - layout->update(); + QtUtils::relayout(layout); } void ArchidektApiResponseDeckEntryDisplayWidget::actRequestNavigationToDeck() diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp index 9c4ea59ab..f8bf3c0dc 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/display/archidekt_api_response_deck_listings_display_widget.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include ArchidektApiResponseDeckListingsDisplayWidget::ArchidektApiResponseDeckListingsDisplayWidget( @@ -19,16 +21,14 @@ ArchidektApiResponseDeckListingsDisplayWidget::ArchidektApiResponseDeckListingsD CardSizeWidget *_cardSizeSlider) : QWidget(parent), cardSizeSlider(_cardSizeSlider) { - layout = new QHBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); + layout = QtUtils::makeLayout(this); setLayout(layout); flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); cardSizeSlider->enableCtrlScrollResize(flowWidget); - imageNetworkManager = new QNetworkAccessManager(this); - imageNetworkManager->setTransferTimeout(); // Use Qt's default timeout + imageNetworkManager = NetworkManagerFactory::create(this); imageNetworkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); // Add widgets for deck listings @@ -64,7 +64,5 @@ void ArchidektApiResponseDeckListingsDisplayWidget::append(const ArchidektDeckLi void ArchidektApiResponseDeckListingsDisplayWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); - layout->invalidate(); - layout->activate(); - layout->update(); + QtUtils::relayout(layout); } diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp index 02badf1aa..7f7a2d3fd 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.cpp @@ -48,7 +48,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -57,8 +59,7 @@ TabArchidekt::TabArchidekt(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor), currentPage(1), isLoadingMore(false), isListMode(true) { // Initialize network - networkManager = new QNetworkAccessManager(this); - networkManager->setTransferTimeout(); + networkManager = NetworkManagerFactory::create(this); networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); connect(networkManager, &QNetworkAccessManager::finished, this, &TabArchidekt::processApiJson); @@ -80,9 +81,7 @@ void TabArchidekt::initializeUi() { // Main container container = new QWidget(this); - mainLayout = new QVBoxLayout(container); - mainLayout->setContentsMargins(0, 0, 0, 0); - mainLayout->setSpacing(0); + mainLayout = QtUtils::makeLayout(container, 0); // Primary toolbar (most important filters) primaryToolbar = new QWidget(container); @@ -105,9 +104,7 @@ void TabArchidekt::initializeUi() // Color filter (inline) QWidget *colorWidget = new QWidget(primaryToolbar); - QHBoxLayout *colorLayout = new QHBoxLayout(colorWidget); - colorLayout->setContentsMargins(0, 0, 0, 0); - colorLayout->setSpacing(2); + QHBoxLayout *colorLayout = QtUtils::makeLayout(colorWidget, 2); QString colorIdentity = "WUBRG"; for (const QChar &color : colorIdentity) { @@ -186,15 +183,10 @@ void TabArchidekt::initializeUi() secondaryToolbarLayout->setSpacing(6); // Scrollable results area - scrollArea = new QScrollArea(container); - scrollArea->setWidgetResizable(true); - scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + scrollArea = QtUtils::makeScrollArea(container); resultsContainer = new QWidget(); - resultsLayout = new QVBoxLayout(resultsContainer); - resultsLayout->setContentsMargins(0, 0, 0, 0); - resultsLayout->setSpacing(0); + resultsLayout = QtUtils::makeLayout(resultsContainer, 0); scrollArea->setWidget(resultsContainer); @@ -228,9 +220,9 @@ void TabArchidekt::setupFilterWidgets() // Advanced filters (in secondary toolbar) // EDH Bracket - auto *bracketLabel = new QLabel(tr("Bracket:"), secondaryToolbar); + bracketLabel = new QLabel(secondaryToolbar); edhBracketCombo = new QComboBox(secondaryToolbar); - edhBracketCombo->addItem(tr("Any")); + edhBracketCombo->addItem(QString()); edhBracketCombo->addItems({"1", "2", "3", "4", "5"}); edhBracketCombo->setSizeAdjustPolicy(QComboBox::AdjustToContents); @@ -284,7 +276,6 @@ void TabArchidekt::setupFilterWidgets() sizeLayout->setContentsMargins(4, 4, 4, 4); minDeckSizeSpin = new QSpinBox(sizeContainer); - minDeckSizeSpin->setSpecialValueText(tr("Any")); minDeckSizeSpin->setRange(0, 200); minDeckSizeSpin->setValue(0); @@ -292,7 +283,8 @@ void TabArchidekt::setupFilterWidgets() minDeckSizeLogicCombo->addItems({"Exact", "≥", "≤"}); minDeckSizeLogicCombo->setCurrentIndex(1); - sizeLayout->addWidget(new QLabel(tr("Cards:"), sizeContainer)); + cardsLabel = new QLabel(sizeContainer); + sizeLayout->addWidget(cardsLabel); sizeLayout->addWidget(minDeckSizeSpin); sizeLayout->addWidget(minDeckSizeLogicCombo); @@ -349,9 +341,9 @@ void TabArchidekt::connectSignals() }); // Format filters trigger search - connect(edhBracketCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch); + connect(edhBracketCombo, &QComboBox::currentIndexChanged, this, &TabArchidekt::doSearch); connect(minDeckSizeSpin, qOverload(&QSpinBox::valueChanged), this, &TabArchidekt::doSearch); - connect(minDeckSizeLogicCombo, &QComboBox::currentTextChanged, this, &TabArchidekt::doSearch); + connect(minDeckSizeLogicCombo, &QComboBox::currentIndexChanged, this, &TabArchidekt::doSearch); // Allow Enter key in text fields to trigger search connect(nameField, &QLineEdit::returnPressed, this, &TabArchidekt::doSearchImmediate); @@ -401,8 +393,15 @@ void TabArchidekt::retranslateUi() commandersField->setPlaceholderText(tr("Commander...")); deckTagNameField->setPlaceholderText(tr("Tag...")); + bracketLabel->setText(tr("Bracket:")); + // Index 0 is the "Any" placeholder added in setupFilterWidgets() + edhBracketCombo->setItemText(0, tr("Any")); + formatButton->setButtonText(tr("Formats")); deckSizeButton->setButtonText(tr("Deck Size")); + cardsLabel->setText(tr("Cards:")); + minDeckSizeSpin->setSpecialValueText(tr("Any")); + minDeckSizeLogicCombo->setItemText(0, tr("Exact")); searchButton->setText(tr("Search")); @@ -492,13 +491,17 @@ QString TabArchidekt::buildSearchUrl() query.addQueryItem("size", QString::number(minDeckSizeSpin->value())); QString logic = "GTE"; - QString selected = minDeckSizeLogicCombo->currentText(); - if (selected == "≥") { - logic = "GTE"; - } else if (selected == "≤") { - logic = "LTE"; - } else { - logic = ""; + // Index order matches setupFilterWidgets(): 0 = Exact, 1 = ≥, 2 = ≤ + switch (minDeckSizeLogicCombo->currentIndex()) { + case 1: + logic = "GTE"; + break; + case 2: + logic = "LTE"; + break; + default: + logic = ""; + break; } if (!logic.isEmpty()) { @@ -599,11 +602,7 @@ void TabArchidekt::processTopDecksResponse(QJsonObject reply) // New search → clear everything if (currentPage == 1) { - QLayoutItem *item; - while ((item = resultsLayout->takeAt(0)) != nullptr) { - delete item->widget(); - delete item; - } + QtUtils::clearLayoutRec(resultsLayout, QtUtils::DeletePolicy::Immediate); listingsWidget = new ArchidektApiResponseDeckListingsDisplayWidget(resultsContainer, deckData, cardSizeSlider); @@ -629,11 +628,7 @@ void TabArchidekt::processDeckResponse(QJsonObject reply) isListMode = false; // Clear existing results for single deck view - QLayoutItem *item; - while ((item = resultsLayout->takeAt(0)) != nullptr) { - delete item->widget(); - delete item; - } + QtUtils::clearLayoutRec(resultsLayout, QtUtils::DeletePolicy::Immediate); auto display = new ArchidektApiResponseDeckDisplayWidget(resultsContainer, deckData, cardSizeSlider); connect(display, &ArchidektApiResponseDeckDisplayWidget::requestNavigation, this, &TabArchidekt::actNavigatePage); diff --git a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h index 015b8590b..c74380b63 100644 --- a/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h +++ b/cockatrice/src/interface/widgets/tabs/api/archidekt/tab_archidekt.h @@ -287,9 +287,11 @@ private: SettingsButtonWidget *formatButton; ///< Collapsible button for format filters QVector formatChecks; ///< Individual checkboxes for each format + QLabel *bracketLabel; ///< Label for EDH bracket selection QComboBox *edhBracketCombo; ///< Dropdown for EDH bracket selection SettingsButtonWidget *deckSizeButton; ///< Collapsible button for deck size filter + QLabel *cardsLabel; ///< Label for deck size selection QSpinBox *minDeckSizeSpin; ///< Spinner to select minimum deck size QComboBox *minDeckSizeLogicCombo; ///< Combo box for size comparison logic @@ -300,4 +302,4 @@ private: CardInfoPtr cardToQuery; ///< Optional pre-selected card for initial filtering }; -#endif // COCKATRICE_TAB_ARCHIDEKT_H \ No newline at end of file +#endif // COCKATRICE_TAB_ARCHIDEKT_H diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.cpp index 06a76dd19..5279165af 100644 --- a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/commander_bracket_widget.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include template class QSharedPointer; @@ -44,8 +45,7 @@ CommanderBracketWidget::CommanderBracketWidget(QWidget *parent) : QWidget(parent connect(bracketRefreshButton, &QToolButton::clicked, this, &CommanderBracketWidget::requestBracketEstimate); - auto *layout = new QGridLayout(this); - layout->setContentsMargins(0, 0, 0, 0); + auto *layout = QtUtils::makeLayout(this); layout->addWidget(bracketLabel, 0, 0); auto *bracketRow = new QHBoxLayout; @@ -110,8 +110,7 @@ bool CommanderBracketWidget::promptCommanderSpellbookIntegration() // Create label + explainer button auto *labelWidget = new QWidget(&dialog); - auto *labelLayout = new QHBoxLayout(labelWidget); - labelLayout->setContentsMargins(0, 0, 0, 0); + auto *labelLayout = QtUtils::makeLayout(labelWidget); auto *namingLabel = new QLabel(tr("Bracket naming:"), labelWidget); auto *explainerButton = new QToolButton(labelWidget); diff --git a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.cpp b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.cpp index 22200ffa2..407f53e99 100644 --- a/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/commander_spellbook/handle_commander_brackets.cpp @@ -12,12 +12,13 @@ #include #include #include +#include #include static const QUrl COMMANDER_BRACKET_JSON_URL(QStringLiteral("https://cockatrice.github.io/commander-brackets.json")); HandleCommanderBrackets::HandleCommanderBrackets(QObject *parent) - : QObject(parent), nam(new QNetworkAccessManager(this)), reply(nullptr) + : QObject(parent), nam(NetworkManagerFactory::create(this)), reply(nullptr) { } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp index 630e0b85a..2b76de973 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/cards/edhrec_api_response_card_details_display_widget.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWidget( @@ -51,15 +52,7 @@ EdhrecApiResponseCardDetailsDisplayWidget::EdhrecApiResponseCardDetailsDisplayWi layout->addWidget(backgroundPlateWidget); - QWidget *currentParent = parentWidget(); - TabEdhRecMain *parentTab = nullptr; - - while (currentParent) { - if ((parentTab = qobject_cast(currentParent))) { - break; - } - currentParent = currentParent->parentWidget(); - } + auto *parentTab = QtUtils::findParentOfType(this); if (parentTab) { cardPictureWidget->setScaleFactor(parentTab->getCardSizeSlider()->getSlider()->value()); diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp index 56b9d74cb..df0319f88 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_api_response_commander_details_display_widget.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCommanderDetailsDisplayWidget( @@ -35,15 +36,7 @@ EdhrecCommanderResponseCommanderDetailsDisplayWidget::EdhrecCommanderResponseCom commanderPicture = new CardInfoPictureWidget(this); commanderPicture->setCard(CardDatabaseManager::query()->getCard({commanderDetails.getName()})); - QWidget *currentParent = parentWidget(); - TabEdhRecMain *parentTab = nullptr; - - while (currentParent) { - if ((parentTab = qobject_cast(currentParent))) { - break; - } - currentParent = currentParent->parentWidget(); - } + auto *parentTab = QtUtils::findParentOfType(this); if (parentTab) { connect(parentTab->getCardSizeSlider()->getSlider(), &QSlider::valueChanged, commanderPicture, diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_display_widget.cpp index 111d91b91..6dc987b76 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_display_widget.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -61,10 +62,7 @@ EdhrecCommanderApiResponseDisplayWidget::EdhrecCommanderApiResponseDisplayWidget } // Create a QScrollArea to hold the card display widgets - scrollArea = new QScrollArea(splitter); - scrollArea->setWidgetResizable(true); - scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea = QtUtils::makeScrollArea(splitter); // Set the cardDisplayLayout inside the scroll area auto scrollWidget = new QWidget(scrollArea); @@ -109,9 +107,7 @@ void EdhrecCommanderApiResponseDisplayWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); qDebug() << event->size(); - layout->invalidate(); - layout->activate(); - layout->update(); + QtUtils::relayout(layout); if (scrollArea && scrollArea->widget()) { scrollArea->widget()->resize(event->size()); } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp index 304db6336..57070a5e1 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/commander/edhrec_commander_api_response_navigation_widget.cpp @@ -9,6 +9,7 @@ #include #include #include +#include EdhrecCommanderApiResponseNavigationWidget::EdhrecCommanderApiResponseNavigationWidget( QWidget *parent, @@ -46,15 +47,7 @@ EdhrecCommanderApiResponseNavigationWidget::EdhrecCommanderApiResponseNavigation layout->addWidget(bracketNavigationWidget, 1, 0, 1, 2); layout->addWidget(budgetNavigationWidget, 2, 0, 1, 2); - QWidget *currentParent = parentWidget(); - TabEdhRecMain *parentTab = nullptr; - - while (currentParent) { - if ((parentTab = qobject_cast(currentParent))) { - break; - } - currentParent = currentParent->parentWidget(); - } + auto *parentTab = QtUtils::findParentOfType(this); if (parentTab) { connect(comboPushButton, &QPushButton::clicked, this, diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_cards/edhrec_top_cards_api_response_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_cards/edhrec_top_cards_api_response_display_widget.cpp index 6f32db102..680926ef4 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_cards/edhrec_top_cards_api_response_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_cards/edhrec_top_cards_api_response_display_widget.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include EdhrecTopCardsApiResponseDisplayWidget::EdhrecTopCardsApiResponseDisplayWidget(QWidget *parent, @@ -29,10 +30,7 @@ EdhrecTopCardsApiResponseDisplayWidget::EdhrecTopCardsApiResponseDisplayWidget(Q } // Create a QScrollArea to hold the card display widgets - scrollArea = new QScrollArea(this); - scrollArea->setWidgetResizable(true); - scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea = QtUtils::makeScrollArea(this); // Set the cardDisplayLayout inside the scroll area auto scrollWidget = new QWidget(scrollArea); @@ -45,9 +43,7 @@ EdhrecTopCardsApiResponseDisplayWidget::EdhrecTopCardsApiResponseDisplayWidget(Q void EdhrecTopCardsApiResponseDisplayWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); - layout->invalidate(); - layout->activate(); - layout->update(); + QtUtils::relayout(layout); if (scrollArea && scrollArea->widget()) { scrollArea->widget()->resize(event->size()); } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_commander/edhrec_top_commanders_api_response_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_commander/edhrec_top_commanders_api_response_display_widget.cpp index 4ab333fd4..105386597 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_commander/edhrec_top_commanders_api_response_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_commander/edhrec_top_commanders_api_response_display_widget.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -33,10 +34,7 @@ EdhrecTopCommandersApiResponseDisplayWidget::EdhrecTopCommandersApiResponseDispl } // Create a QScrollArea to hold the card display widgets - scrollArea = new QScrollArea(this); - scrollArea->setWidgetResizable(true); - scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea = QtUtils::makeScrollArea(this); // Set the cardDisplayLayout inside the scroll area auto scrollWidget = new QWidget(scrollArea); @@ -50,9 +48,7 @@ void EdhrecTopCommandersApiResponseDisplayWidget::resizeEvent(QResizeEvent *even { QWidget::resizeEvent(event); qDebug() << event->size(); - layout->invalidate(); - layout->activate(); - layout->update(); + QtUtils::relayout(layout); if (scrollArea && scrollArea->widget()) { scrollArea->widget()->resize(event->size()); } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.cpp index 9c7ce8d06..ae8686f3a 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/display/top_tags/edhrec_top_tags_api_response_display_widget.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include EdhrecTopTagsApiResponseDisplayWidget::EdhrecTopTagsApiResponseDisplayWidget(QWidget *parent, @@ -29,10 +30,7 @@ EdhrecTopTagsApiResponseDisplayWidget::EdhrecTopTagsApiResponseDisplayWidget(QWi } // Create a QScrollArea to hold the card display widgets - scrollArea = new QScrollArea(this); - scrollArea->setWidgetResizable(true); - scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + scrollArea = QtUtils::makeScrollArea(this); // Set the cardDisplayLayout inside the scroll area auto scrollWidget = new QWidget(scrollArea); @@ -45,9 +43,7 @@ EdhrecTopTagsApiResponseDisplayWidget::EdhrecTopTagsApiResponseDisplayWidget(QWi void EdhrecTopTagsApiResponseDisplayWidget::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); - layout->invalidate(); - layout->activate(); - layout->update(); + QtUtils::relayout(layout); if (scrollArea && scrollArea->widget()) { scrollArea->widget()->resize(event->size()); } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp index 3aa395126..9dd0cca03 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec.cpp @@ -18,14 +18,14 @@ #include #include #include +#include #include class TabSupervisor; TabEdhRec::TabEdhRec(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) { - networkManager = new QNetworkAccessManager(this); - networkManager->setTransferTimeout(); // Use Qt's default timeout + networkManager = NetworkManagerFactory::create(this); networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); connect(networkManager, &QNetworkAccessManager::finished, this, &TabEdhRec::processApiJson); } diff --git a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp index ca575dc02..c8822930e 100644 --- a/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp +++ b/cockatrice/src/interface/widgets/tabs/api/edhrec/tab_edhrec_main.cpp @@ -1,7 +1,6 @@ #include "tab_edhrec_main.h" #include "../../../../../client/settings/cache_settings.h" -#include "../../../../deck_loader/loaded_deck.h" #include "../../../cards/card_size_widget.h" #include "../../../quick_settings/settings_button_widget.h" #include "../../../utility/completer_utils.h" @@ -28,10 +27,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include @@ -44,8 +45,7 @@ static bool canBeCommander(const CardInfoPtr &cardInfo) TabEdhRecMain::TabEdhRecMain(TabSupervisor *_tabSupervisor) : Tab(_tabSupervisor) { - networkManager = new QNetworkAccessManager(this); - networkManager->setTransferTimeout(); // Use Qt's default timeout + networkManager = NetworkManagerFactory::create(this); networkManager->setRedirectPolicy(QNetworkRequest::ManualRedirectPolicy); connect(networkManager, SIGNAL(finished(QNetworkReply *)), this, SLOT(processApiJson(QNetworkReply *))); diff --git a/cockatrice/src/interface/widgets/tabs/tab_card_art_rules.cpp b/cockatrice/src/interface/widgets/tabs/tab_card_art_rules.cpp index 1fc2854ce..f18aa82b9 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_card_art_rules.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_card_art_rules.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -29,7 +30,9 @@ #include #include #include +#include #include +#include #include class QObject; @@ -172,6 +175,7 @@ void TabCardArtRules::setupUi() addBtn = new QPushButton; removeBtn = new QPushButton; refreshBtn = new QPushButton; + clearBannedArtBtn = new QPushButton; modeBox->addItems({"ALLOW", "DENY"}); @@ -188,10 +192,14 @@ void TabCardArtRules::setupUi() form->addRow(tr("Mode:"), modeBox); form->addRow(tr("Reason:"), reasonEdit); + // The bulk clear acts on every user, so keep it visibly apart from the + // rules this tab edits. auto *buttons = new QHBoxLayout; buttons->addWidget(addBtn); buttons->addWidget(removeBtn); buttons->addWidget(refreshBtn); + buttons->addStretch(); + buttons->addWidget(clearBannedArtBtn); auto *layout = new QVBoxLayout; layout->addLayout(form); @@ -207,6 +215,8 @@ void TabCardArtRules::setupUi() connect(refreshBtn, &QPushButton::clicked, this, &TabCardArtRules::refresh); + connect(clearBannedArtBtn, &QPushButton::clicked, this, &TabCardArtRules::clearBlacklistedBanners); + retranslateUi(); } @@ -263,6 +273,7 @@ void TabCardArtRules::retranslateUi() addBtn->setText(tr("Add rule")); removeBtn->setText(tr("Remove rule")); refreshBtn->setText(tr("Refresh")); + clearBannedArtBtn->setText(tr("Clear blacklisted banners")); } void TabCardArtRules::refresh() @@ -299,4 +310,47 @@ void TabCardArtRules::removeSelected() client->sendCommand(client->prepareModeratorCommand(cmd)); refresh(); -} \ No newline at end of file +} + +void TabCardArtRules::clearBlacklistedBanners() +{ + if (!QtUtils::confirm(this, tr("Clear blacklisted banners"), + tr("Remove the banner card from every user whose current banner matches a DENY rule?\n\n" + "Users who are online keep showing their banner until they log in again."), + QMessageBox::No)) { + return; + } + + Command_ClearBlacklistedBanners cmd; + PendingCommand *pend = client->prepareModeratorCommand(cmd); + connect(pend, &PendingCommand::finished, this, &TabCardArtRules::clearBlacklistedBannersResponse); + client->sendCommand(pend); +} + +void TabCardArtRules::clearBlacklistedBannersResponse(const Response &r) +{ + switch (r.response_code()) { + case Response::RespOk: + break; + case Response::RespFunctionNotAllowed: + QMessageBox::warning(this, tr("Not supported"), + tr("This server does not support clearing blacklisted banners. It may be running an " + "older version.")); + return; + case Response::RespInternalError: + QMessageBox::critical(this, tr("Error"), + tr("The server could not clear blacklisted banners because of a database error.")); + return; + default: + QMessageBox::critical(this, tr("Error"), + tr("The server rejected the request: %1") + .arg(QString::fromStdString(Response_ResponseCode_Name(r.response_code())))); + return; + } + + const auto &resp = r.GetExtension(Response_ClearBlacklistedBanners::ext); + QMessageBox::information(this, tr("Banners cleared"), + tr("%n banner card(s) cleared.\n\n" + "Users who are online keep showing their banner until they log in again.", + "number of banners cleared", resp.cleared_count())); +} diff --git a/cockatrice/src/interface/widgets/tabs/tab_card_art_rules.h b/cockatrice/src/interface/widgets/tabs/tab_card_art_rules.h index f34300179..1e0155ab7 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_card_art_rules.h +++ b/cockatrice/src/interface/widgets/tabs/tab_card_art_rules.h @@ -78,6 +78,8 @@ private slots: void addRule(); void removeSelected(); void refresh(); + void clearBlacklistedBanners(); + void clearBlacklistedBannersResponse(const Response &r); private: AbstractClient *client; @@ -97,6 +99,7 @@ private: QPushButton *addBtn; QPushButton *removeBtn; QPushButton *refreshBtn; + QPushButton *clearBannedArtBtn; QTableView *table; CardArtRulesModel *tableModel; diff --git a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp index 44d04bd24..3dfdc956b 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_deck_storage.cpp @@ -1,9 +1,7 @@ #include "tab_deck_storage.h" #include "../../../client/settings/cache_settings.h" -#include "../../deck_loader/deck_file_format.h" #include "../../deck_loader/deck_loader.h" -#include "../../deck_loader/loaded_deck.h" #include "../../pixel_map_generator.h" #include "../cards/additional_info/deck_color_identity.h" #include "../deck_share/deck_share_utils.h" @@ -20,7 +18,6 @@ #include #include #include -#include #include #include #include @@ -45,13 +42,14 @@ #include #include #include -#include #include #include #include #include #include +#include #include +#include #include #include #include @@ -65,6 +63,7 @@ #include #include #include +#include #include #include #include @@ -524,7 +523,7 @@ void TabDeckStorage::actDeleteLocalDeck() void TabDeckStorage::actOpenDecksFolder() { QString dir = localDirModel->rootPath(); - QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); + QtUtils::openPathInFileManager(dir); } void TabDeckStorage::actRemoteDoubleClick(const QModelIndex &curRight) diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index 4cf5919ba..9e724727f 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -2,16 +2,10 @@ #include "../../../client/settings/cache_settings.h" #include "../../../client/settings/shortcuts_settings.h" -#include "../../../game/abstract_game.h" -#include "../../../game/game_event_handler.h" -#include "../../../game/game_meta_info.h" -#include "../../../game/game_state.h" -#include "../../../game/player/player_info.h" -#include "../../../game/player/player_manager.h" +#include "../../../database/interface/settings_card_preference_provider.h" #include "../../../game_graphics/board/abstract_card_item.h" -#include "../game/game.h" -#include "../game/player/player_logic.h" -#include "../game/replay.h" +#include "../game_graphics/board/arrow_item.h" +#include "../game_graphics/board/card_item.h" #include "../game_graphics/deckview/deck_view_container.h" #include "../game_graphics/deckview/tabbed_deck_view_container.h" #include "../game_graphics/game_scene.h" @@ -71,6 +65,15 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include @@ -78,6 +81,7 @@ #include #include #include +#include #include #include #include @@ -96,7 +100,8 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay) : Tab(_tabSupervisor), sayLabel(nullptr), sayEdit(nullptr) { // THIS CTOR IS USED ON REPLAY - game = new Replay(this, _replay, tabSupervisor->getIsLocalGame()); + game = new Replay(this, _replay, tabSupervisor->getIsLocalGame(), &SettingsCache::instance().userInterface(), + new SettingsCardPreferenceProvider(this)); createCardInfoDock(true); createPlayerListDock(true); @@ -140,7 +145,8 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, : Tab(_tabSupervisor), userListProxy(_tabSupervisor->getUserListManager()) { // THIS CTOR IS USED ON GAMES - game = new Game(this, tabSupervisor->getIsLocalGame(), _clients, event, _roomGameTypes); + game = new Game(this, tabSupervisor->getIsLocalGame(), _clients, event, _roomGameTypes, + &SettingsCache::instance().userInterface(), new SettingsCardPreferenceProvider(this)); createCardInfoDock(); createPlayerListDock(); @@ -605,15 +611,14 @@ void TabGame::actConcede() return; } if (!player->getConceded()) { - if (QMessageBox::question(this, tr("Concede"), tr("Are you sure you want to concede this game?"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) { + if (!QtUtils::confirm(this, tr("Concede"), tr("Are you sure you want to concede this game?"), + QMessageBox::No)) { return; } emit game->getPlayerManager()->activeLocalPlayerConceded(); } else { - if (QMessageBox::question(this, tr("Unconcede"), - tr("You have already conceded. Do you want to return to this game?"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) { + if (!QtUtils::confirm(this, tr("Unconcede"), + tr("You have already conceded. Do you want to return to this game?"), QMessageBox::No)) { return; } emit game->getPlayerManager()->activeLocalPlayerUnconceded(); @@ -630,8 +635,8 @@ bool TabGame::leaveGame() if (!game->getGameState()->isGameClosed()) { if (!game->getPlayerManager()->isSpectator()) { tabSupervisor->setCurrentWidget(this); - if (QMessageBox::question(this, tr("Leave game"), tr("Are you sure you want to leave this game?"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No) != QMessageBox::Yes) { + if (!QtUtils::confirm(this, tr("Leave game"), tr("Are you sure you want to leave this game?"), + QMessageBox::No)) { return false; } } @@ -1276,8 +1281,14 @@ void TabGame::createPlayAreaWidget(bool bReplay) { phasesToolbar = new PhasesToolbar; if (!bReplay) { - connect(phasesToolbar, &PhasesToolbar::sendGameCommand, game->getGameEventHandler(), - qOverload(&GameEventHandler::sendGameCommand)); + connect(phasesToolbar, &PhasesToolbar::setActivePhaseRequested, game->getGameEventHandler(), + &GameEventHandler::handleActivePhaseChanged); + connect(phasesToolbar, &PhasesToolbar::nextTurnRequested, game->getGameEventHandler(), + &GameEventHandler::handleNextTurn); + connect(phasesToolbar, &PhasesToolbar::untapAllRequested, game->getGameEventHandler(), + &GameEventHandler::handleUntapAll); + connect(phasesToolbar, &PhasesToolbar::drawCardRequested, game->getGameEventHandler(), + &GameEventHandler::handleDrawCard); } scene = new GameScene(phasesToolbar, this); connect(game->getPlayerManager(), &PlayerManager::playerConceded, scene, &GameScene::rearrange); @@ -1287,8 +1298,7 @@ void TabGame::createPlayAreaWidget(bool bReplay) connect(game->getGameEventHandler(), &GameEventHandler::arrowDeleted, scene, &GameScene::deleteArrow); gameView = new GameView(scene); - auto gamePlayAreaVBox = new QVBoxLayout; - gamePlayAreaVBox->setContentsMargins(0, 0, 0, 0); + auto gamePlayAreaVBox = QtUtils::makeLayout(); gamePlayAreaVBox->addWidget(gameView); gamePlayAreaWidget = new QWidget; @@ -1320,8 +1330,7 @@ void TabGame::createDeckViewContainerWidget(bool bReplay) deckViewContainerWidget = new QWidget(); deckViewContainerWidget->setObjectName("deckViewContainerWidget"); - deckViewContainerLayout = new QVBoxLayout; - deckViewContainerLayout->setContentsMargins(0, 0, 0, 0); + deckViewContainerLayout = QtUtils::makeLayout(); deckViewContainerWidget->setLayout(deckViewContainerLayout); } @@ -1336,8 +1345,7 @@ void TabGame::createCardInfoDock(bool bReplay) cardInfoFrameWidget = new CardInfoFrameWidget(); auto cardHInfoLayout = new QHBoxLayout; - auto cardVInfoLayout = new QVBoxLayout; - cardVInfoLayout->setContentsMargins(0, 0, 0, 0); + auto cardVInfoLayout = QtUtils::makeLayout(); cardVInfoLayout->addWidget(cardInfoFrameWidget); cardVInfoLayout->addLayout(cardHInfoLayout); @@ -1364,9 +1372,7 @@ void TabGame::createPlayerListDock(bool bReplay) playerListWidget->setFocusPolicy(Qt::NoFocus); auto *playerListBox = new QWidget(this); - auto *vbox = new QVBoxLayout(playerListBox); - vbox->setContentsMargins(0, 0, 0, 0); - vbox->setSpacing(0); + auto *vbox = QtUtils::makeLayout(playerListBox, 0); vbox->addWidget(playerListWidget); @@ -1391,8 +1397,7 @@ void TabGame::createPlayerListDock(bool bReplay) void TabGame::createMessageDock(bool bReplay) { - auto messageLogLayout = new QVBoxLayout; - messageLogLayout->setContentsMargins(0, 0, 0, 0); + auto messageLogLayout = QtUtils::makeLayout(); // clock if (!bReplay) { diff --git a/cockatrice/src/interface/widgets/tabs/tab_home.cpp b/cockatrice/src/interface/widgets/tabs/tab_home.cpp index 1358645d0..af9994fdd 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_home.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_home.cpp @@ -12,4 +12,5 @@ TabHome::TabHome(TabSupervisor *_tabSupervisor, AbstractClient *_client) : Tab(_ void TabHome::retranslateUi() { + homeWidget->retranslateUi(); } diff --git a/cockatrice/src/interface/widgets/tabs/tab_logs.cpp b/cockatrice/src/interface/widgets/tabs/tab_logs.cpp index 9f17afea2..9509f48ec 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_logs.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_logs.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -21,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -31,14 +33,25 @@ class TabSupervisor; +namespace +{ +/** @brief Identifies the newest row of a page well enough to recognise a repeated page. */ +QString pageStartKey(const Response_ViewLogHistory &response) +{ + const ServerInfo_ChatMessage &first = response.log_message(0); + return QString::fromStdString(first.time()) + QChar(0x1f) + QString::fromStdString(first.sender_name()) + + QChar(0x1f) + QString::fromStdString(first.message()); +} +} // namespace + TabLog::TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _canUseDeveloperCommands) : Tab(_tabSupervisor), client(_client), canUseDeveloperCommands(_canUseDeveloperCommands) { roomTable = new QTableWidget(); - roomTable->setColumnCount(6); + roomTable->setColumnCount(7); roomTable->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); roomTable->setHorizontalHeaderLabels( - QString(tr("Time;SenderName;SenderIP;Message;TargetID;TargetName")).split(";")); + QString(tr("Time;SenderName;SenderIP;Message;TargetID;TargetName;Channel")).split(";")); gameTable = new QTableWidget(); gameTable->setColumnCount(6); @@ -52,12 +65,20 @@ TabLog::TabLog(TabSupervisor *_tabSupervisor, AbstractClient *_client, bool _can chatTable->setHorizontalHeaderLabels( QString(tr("Time;SenderName;SenderIP;Message;TargetID;TargetName")).split(";")); - QTabWidget *tabManager = new QTabWidget(); + tabManager = new QTabWidget(); tabManager->addTab(roomTable, tr("Room Logs")); tabManager->addTab(gameTable, tr("Game Logs")); tabManager->addTab(chatTable, tr("Chat Logs")); setCentralWidget(tabManager); + connect(roomTable->verticalScrollBar(), &QScrollBar::valueChanged, this, &TabLog::checkFetchMore); + connect(gameTable->verticalScrollBar(), &QScrollBar::valueChanged, this, &TabLog::checkFetchMore); + connect(chatTable->verticalScrollBar(), &QScrollBar::valueChanged, this, &TabLog::checkFetchMore); + // A tab that gains only a few rows has nothing to scroll, so its scrollbar never fires: + // switching to it would leave its next page unfetched until the moderator scrolled + // another tab. Re-check on every tab switch so the visible table can continue the browse. + connect(tabManager, &QTabWidget::currentChanged, this, &TabLog::checkFetchMore); + createDock(); restartLayout(); clearClicked(); @@ -74,12 +95,9 @@ void TabLog::retranslateUi() void TabLog::getClicked() { - if (findUsername->text().isEmpty() && findIPAddress->text().isEmpty() && findGameName->text().isEmpty() && - findGameID->text().isEmpty() && findMessage->text().isEmpty()) { - QMessageBox::critical(this, tr("Error"), tr("You must select at least one filter.")); - return; - } - + // Empty filters are a valid browse: room/game history going back from + // today, paginated. Only require a date range + location, both of which + // get sensible defaults below so a bare "Get" just works. if (!lastHour->isChecked() && !today->isChecked() && !pastDays->isChecked()) { pastDays->setChecked(true); pastXDays->setValue(20); @@ -99,39 +117,71 @@ void TabLog::getClicked() } if (maximumResults->value() == 0) { - maximumResults->setValue(1000); + maximumResults->setValue(200); } + pageSize = qBound(1, maximumResults->value(), 1000); - int dateRange = 0; - if (lastHour->isChecked()) { - dateRange = 1; - } - - if (today->isChecked()) { - dateRange = 24; - } - - if (pastDays->isChecked()) { - dateRange = pastXDays->value() * 24; - } - - Command_ViewLogHistory cmd; - cmd.set_user_name(findUsername->text().toStdString()); - cmd.set_ip_address(findIPAddress->text().toStdString()); - cmd.set_game_name(findGameName->text().toStdString()); - cmd.set_game_id(findGameID->text().toStdString()); - cmd.set_message(findMessage->text().toStdString()); + // Build the command once, here, and let every page copy it: rebuilding it from the + // widgets per page would run page 2+ against filters edited after Get was clicked, + // appending rows selected by a different query under the first page's results. + currentQuery.Clear(); + currentQuery.set_user_name(findUsername->text().toStdString()); + currentQuery.set_ip_address(findIPAddress->text().toStdString()); + currentQuery.set_game_name(findGameName->text().toStdString()); + currentQuery.set_game_id(findGameID->text().toStdString()); + currentQuery.set_message(findMessage->text().toStdString()); if (mainRoom->isChecked()) { - cmd.add_log_location("room"); - }; + currentQuery.add_log_location("room"); + } if (gameRoom->isChecked()) { - cmd.add_log_location("game"); - }; + currentQuery.add_log_location("game"); + } if (privateChat->isChecked()) { - cmd.add_log_location("chat"); - }; - cmd.set_date_range(dateRange); - cmd.set_maximum_results(maximumResults->value()); + currentQuery.add_log_location("chat"); + } + currentQuery.set_date_range(dateRangeForSelection()); + currentQuery.set_maximum_results(pageSize); + hasCurrentQuery = true; + snapshotBefore.clear(); + lastPageStartKey.clear(); + + // Fresh search: reset pagination state and clear previous pages. + currentOffset = 0; + roomCounter = 0; + gameCounter = 0; + chatCounter = 0; + roomTable->setRowCount(0); + gameTable->setRowCount(0); + chatTable->setRowCount(0); + + fetchPage(0); +} + +int TabLog::dateRangeForSelection() const +{ + if (lastHour->isChecked()) { + return 1; + } + if (today->isChecked()) { + return 24; + } + if (pastDays->isChecked()) { + return pastXDays->value() * 24; + } + return 0; +} + +void TabLog::fetchPage(int offset) +{ + if (isFetching || !hasCurrentQuery) { + return; + } + + Command_ViewLogHistory cmd = currentQuery; + cmd.set_offset(offset); + if (!snapshotBefore.isEmpty()) { + cmd.set_before_time(snapshotBefore.toStdString()); + } PendingCommand *pend; if (canUseDeveloperCommands) { @@ -141,10 +191,42 @@ void TabLog::getClicked() pend = client->prepareModeratorCommand(cmd); } + isFetching = true; + getButton->setEnabled(false); + connect(pend, &PendingCommand::finished, this, &TabLog::viewLogHistory_processResponse); client->sendCommand(pend); } +void TabLog::checkFetchMore() +{ + if (isFetching || !hasMoreResults) { + return; + } + + // Only the table the moderator is looking at may pull the next page. Checking all + // three would make an empty or hidden table nearBottom by definition (maximum() and + // value() are both zero), so a room-only browse chained page after page without any + // scrolling - pulling the whole range with a growing OFFSET scan per page, and + // looping forever against a server that ignores `offset`. + auto *table = qobject_cast(tabManager->currentWidget()); + if (table == nullptr || table->rowCount() == 0) { + return; + } + + // ScrollPerItem (the default) counts rows, not pixels: `value` is the top visible row + // and `maximum` the last one that can be scrolled to. Prefetch half a page ahead so + // scrolling never visibly stalls, without the fixed 300-row threshold firing the next + // page the moment a page loads. + const QScrollBar *bar = table->verticalScrollBar(); + const int prefetchRows = qMax(1, pageSize / 2); + if (bar->maximum() - bar->value() > prefetchRows) { + return; + } + + fetchPage(currentOffset); +} + void TabLog::clearClicked() { findUsername->clear(); @@ -166,6 +248,10 @@ void TabLog::clearClicked() pastDays->setAutoExclusive(true); today->setAutoExclusive(true); lastHour->setAutoExclusive(true); + currentOffset = 0; + hasMoreResults = false; + hasCurrentQuery = false; + snapshotBefore.clear(); } void TabLog::createDock() @@ -209,14 +295,15 @@ void TabLog::createDock() pastXDays = new QSpinBox; pastXDays->setMaximum(20); - labelMaximum = new QLabel(tr("Maximum Results: ")); + labelMaximum = new QLabel(tr("Results per page: ")); maximumResults = new QSpinBox; maximumResults->setMaximum(1000); + maximumResults->setToolTip(tr("Page size for smooth scrolling: more rows load automatically as you scroll.")); - labelDescription = new QLabel(tr( - "At least one filter is required.\nThe more information you put in, the more specific your results will be.")); + labelDescription = new QLabel(tr("Leave filters empty to browse recent room and game messages.\n" + "More rows load automatically as you scroll.")); - getButton = new QPushButton(tr("Get User Logs")); + getButton = new QPushButton(tr("Get Logs")); getButton->setAutoDefault(true); connect(getButton, &QPushButton::clicked, this, &TabLog::getClicked); @@ -262,7 +349,7 @@ void TabLog::createDock() maxResultsGrid->addWidget(labelMaximum, 0, 0); maxResultsGrid->addWidget(maximumResults, 0, 1); - maxResultsGroupBox = new QGroupBox(tr("Maximum Results")); + maxResultsGroupBox = new QGroupBox(tr("Results per page")); maxResultsGroupBox->setLayout(maxResultsGrid); descriptionGrid = new QGridLayout; @@ -295,82 +382,134 @@ void TabLog::createDock() searchDock->setWidget(searchDockContents); } +void TabLog::appendChatMessage(QTableWidget *table, + int &counter, + const QString &time, + const QString &senderName, + const QString &senderIp, + const QString &messageText, + const QString &targetId, + const QString &targetName, + const QString &channel) +{ + table->insertRow(counter); + table->setItem(counter, 0, new QTableWidgetItem(time)); + table->setItem(counter, 1, new QTableWidgetItem(senderName)); + table->setItem(counter, 2, new QTableWidgetItem(senderIp)); + table->setItem(counter, 3, new QTableWidgetItem(messageText)); + table->setItem(counter, 4, new QTableWidgetItem(targetId)); + table->setItem(counter, 5, new QTableWidgetItem(targetName)); + if (table->columnCount() > 6) { + table->setItem(counter, 6, new QTableWidgetItem(channel)); + } + ++counter; +} + void TabLog::viewLogHistory_processResponse(const Response &resp) { const Response_ViewLogHistory &response = resp.GetExtension(Response_ViewLogHistory::ext); + const bool isFirstPage = (currentOffset == 0); + isFetching = false; + getButton->setEnabled(true); + if (resp.response_code() != Response::RespOk) { QMessageBox::critical(static_cast(parent()), tr("Message History"), tr("Failed to collect message history information.")); return; } - if (response.log_message_size() == 0) { + if (isFirstPage && response.log_message_size() == 0) { QMessageBox::information(static_cast(parent()), tr("Message History"), tr("There are no messages for the selected filters.")); + hasMoreResults = false; return; } - int roomCounter = 0, gameCounter = 0, chatCounter = 0; - roomTable->setRowCount(roomCounter); - gameTable->setRowCount(gameCounter); - chatTable->setRowCount(chatCounter); + const QString thisPageStart = response.log_message_size() > 0 ? pageStartKey(response) : QString(); + + if (isFirstPage) { + // Rows come newest first, so the first one fixes the snapshot every later page is + // bounded by. Without it, each message logged between two page fetches pushes the + // window down by one and repeats a row the moderator already has. + snapshotBefore = QString::fromStdString(response.log_message(0).time()); + } else if (!response.has_has_more() && !thisPageStart.isEmpty() && thisPageStart == lastPageStartKey) { + // A server that predates `offset` ignores it and returns the same page again. It + // also predates `has_more`, so the full-page heuristic below can never prove + // exhaustion and every scroll would append the same rows forever. The identical + // first row is the proof; stop instead of looping. + hasMoreResults = false; + return; + } + lastPageStartKey = thisPageStart; for (int i = 0; i < response.log_message_size(); ++i) { ServerInfo_ChatMessage message = response.log_message(i); - if (QString::fromStdString(message.target_type()) == "room") { - roomTable->insertRow(roomCounter); - roomTable->setItem(roomCounter, 0, new QTableWidgetItem(QString::fromStdString(message.time()))); - roomTable->setItem(roomCounter, 1, new QTableWidgetItem(QString::fromStdString(message.sender_name()))); - roomTable->setItem(roomCounter, 2, new QTableWidgetItem(QString::fromStdString(message.sender_ip()))); - roomTable->setItem(roomCounter, 3, new QTableWidgetItem(QString::fromStdString(message.message()))); - roomTable->setItem(roomCounter, 4, new QTableWidgetItem(QString::fromStdString(message.target_id()))); - roomTable->setItem(roomCounter, 5, new QTableWidgetItem(QString::fromStdString(message.target_name()))); - ++roomCounter; + const QString targetType = QString::fromStdString(message.target_type()); + if (targetType == "room" || targetType == "room_channel") { + appendChatMessage(roomTable, roomCounter, QString::fromStdString(message.time()), + QString::fromStdString(message.sender_name()), + QString::fromStdString(message.sender_ip()), QString::fromStdString(message.message()), + QString::fromStdString(message.target_id()), + QString::fromStdString(message.target_name()), QString::fromStdString(message.channel())); } - if (QString::fromStdString(message.target_type()) == "game") { - gameTable->insertRow(gameCounter); - gameTable->setItem(gameCounter, 0, new QTableWidgetItem(QString::fromStdString(message.time()))); - gameTable->setItem(gameCounter, 1, new QTableWidgetItem(QString::fromStdString(message.sender_name()))); - gameTable->setItem(gameCounter, 2, new QTableWidgetItem(QString::fromStdString(message.sender_ip()))); - gameTable->setItem(gameCounter, 3, new QTableWidgetItem(QString::fromStdString(message.message()))); - gameTable->setItem(gameCounter, 4, new QTableWidgetItem(QString::fromStdString(message.target_id()))); - gameTable->setItem(gameCounter, 5, new QTableWidgetItem(QString::fromStdString(message.target_name()))); - ++gameCounter; + if (targetType == "game") { + appendChatMessage(gameTable, gameCounter, QString::fromStdString(message.time()), + QString::fromStdString(message.sender_name()), + QString::fromStdString(message.sender_ip()), QString::fromStdString(message.message()), + QString::fromStdString(message.target_id()), + QString::fromStdString(message.target_name())); } - if (QString::fromStdString(message.target_type()) == "chat") { - chatTable->insertRow(chatCounter); - chatTable->setItem(chatCounter, 0, new QTableWidgetItem(QString::fromStdString(message.time()))); - chatTable->setItem(chatCounter, 1, new QTableWidgetItem(QString::fromStdString(message.sender_name()))); - chatTable->setItem(chatCounter, 2, new QTableWidgetItem(QString::fromStdString(message.sender_ip()))); - chatTable->setItem(chatCounter, 3, new QTableWidgetItem(QString::fromStdString(message.message()))); - chatTable->setItem(chatCounter, 4, new QTableWidgetItem(QString::fromStdString(message.target_id()))); - chatTable->setItem(chatCounter, 5, new QTableWidgetItem(QString::fromStdString(message.target_name()))); - ++chatCounter; + if (targetType == "chat") { + appendChatMessage(chatTable, chatCounter, QString::fromStdString(message.time()), + QString::fromStdString(message.sender_name()), + QString::fromStdString(message.sender_ip()), QString::fromStdString(message.message()), + QString::fromStdString(message.target_id()), + QString::fromStdString(message.target_name())); } } + currentOffset += response.log_message_size(); + if (response.has_has_more()) { + hasMoreResults = response.has_more(); + } else { + // Legacy server without has_more: a full page implies more may exist. + hasMoreResults = (response.log_message_size() >= pageSize); + } + if (roomCounter) { roomTable->show(); - roomTable->resizeColumnsToContents(); + if (isFirstPage) { + roomTable->resizeColumnsToContents(); + } } else { roomTable->hide(); } if (gameCounter) { - gameTable->resizeColumnsToContents(); + if (isFirstPage) { + gameTable->resizeColumnsToContents(); + } gameTable->show(); } else { gameTable->hide(); } if (chatCounter) { - chatTable->resizeColumnsToContents(); + if (isFirstPage) { + chatTable->resizeColumnsToContents(); + } chatTable->show(); } else { chatTable->hide(); } + + // The new rows may already fill less than a viewport (or the user is + // still parked at the bottom), so chain the next page without waiting + // for another scroll event. The prefetch stays invisible: rows simply + // keep appearing as you scroll. + checkFetchMore(); } void TabLog::restartLayout() diff --git a/cockatrice/src/interface/widgets/tabs/tab_logs.h b/cockatrice/src/interface/widgets/tabs/tab_logs.h index fc8ef1ea0..f2f5842c5 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_logs.h +++ b/cockatrice/src/interface/widgets/tabs/tab_logs.h @@ -10,6 +10,7 @@ #include "tab.h" #include +#include #include class AbstractClient; @@ -25,6 +26,7 @@ class QWidget; class QGridLayout; class QVBoxLayout; class QTableWidget; +class QTabWidget; class Response; class TabSupervisor; @@ -48,8 +50,37 @@ private: *buttonGroupBox; QVBoxLayout *mainLayout; QTableWidget *roomTable, *gameTable, *chatTable; + QTabWidget *tabManager = nullptr; + + //! The filters of the search in flight, captured when Get was clicked so that + //! later pages cannot pick up edits the moderator made after starting it. + Command_ViewLogHistory currentQuery; + bool hasCurrentQuery = false; + //! Newest `log_time` of the first page, sent back as the upper bound of every + //! later page so messages logged in the meantime do not shift the window. + QString snapshotBefore; + //! Identity of the previous page's first row, used to spot a server that ignores + //! `offset` and hands back the same page forever. + QString lastPageStartKey; + int currentOffset = 0; + int pageSize = 200; + int roomCounter = 0, gameCounter = 0, chatCounter = 0; + bool isFetching = false; + bool hasMoreResults = false; void createDock(); + [[nodiscard]] int dateRangeForSelection() const; + void fetchPage(int offset); + void appendChatMessage(QTableWidget *table, + int &counter, + const QString &time, + const QString &senderName, + const QString &senderIp, + const QString &messageText, + const QString &targetId, + const QString &targetName, + const QString &channel = QString()); + void checkFetchMore(); signals: private slots: diff --git a/cockatrice/src/interface/widgets/tabs/tab_moderation.cpp b/cockatrice/src/interface/widgets/tabs/tab_moderation.cpp index 09a640540..be1adeef8 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_moderation.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_moderation.cpp @@ -30,12 +30,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -85,12 +87,16 @@ TabModeration::TabModeration(TabSupervisor *_tabSupervisor, AbstractClient *_cli removeAvatarButton = new QPushButton; connect(removeAvatarButton, &QPushButton::clicked, this, &TabModeration::removeAvatar); + removeBannerButton = new QPushButton; + connect(removeBannerButton, &QPushButton::clicked, this, &TabModeration::removeBanner); + auto *topBar = new QHBoxLayout; topBar->addWidget(searchEdit); topBar->addWidget(investigateButton); topBar->addStretch(); topBar->addWidget(resetPasswordButton); topBar->addWidget(removeAvatarButton); + topBar->addWidget(removeBannerButton); userInfoGroup = new QGroupBox; userInfoNameLabel = new QLabel; @@ -186,6 +192,7 @@ void TabModeration::retranslateUi() investigateButton->setText(tr("Investigate")); resetPasswordButton->setText(tr("Reset Password")); removeAvatarButton->setText(tr("Remove Avatar")); + removeBannerButton->setText(tr("Remove Banner")); refreshStaffButton->setText(tr("Refresh")); userInfoGroup->setTitle(tr("User Info")); @@ -227,6 +234,7 @@ void TabModeration::clearUserData() sessionsTable->setRowCount(0); resetPasswordButton->setEnabled(false); removeAvatarButton->setEnabled(false); + removeBannerButton->setEnabled(false); } void TabModeration::investigate(const QString &userName) @@ -247,6 +255,7 @@ void TabModeration::investigateUser() currentUser = userName; resetPasswordButton->setEnabled(true); removeAvatarButton->setEnabled(true); + removeBannerButton->setEnabled(true); altsTable->setRowCount(0); sessionsTable->setRowCount(0); requestUserInfo(userName); @@ -414,13 +423,11 @@ void TabModeration::resetPassword() return; } - QMessageBox::StandardButton choice = - QMessageBox::warning(this, tr("Reset Password"), - tr("Reset the password of %1? A temporary password will be generated and shown to you. " - "The user must change it on their first login.") - .arg(currentUser), - QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel); - if (choice != QMessageBox::Ok) { + if (!QtUtils::confirm(this, tr("Reset Password"), + tr("Reset the password of %1? A temporary password will be generated and shown to you. " + "The user must change it on their first login.") + .arg(currentUser), + QMessageBox::No)) { return; } @@ -451,11 +458,9 @@ void TabModeration::removeAvatar() return; } - QMessageBox::StandardButton choice = - QMessageBox::warning(this, tr("Remove Avatar"), - tr("Remove the avatar of %1? The user will have to upload a new one.").arg(currentUser), - QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel); - if (choice != QMessageBox::Ok) { + if (!QtUtils::confirm(this, tr("Remove Avatar"), + tr("Remove the avatar of %1? The user will have to upload a new one.").arg(currentUser), + QMessageBox::No)) { return; } @@ -477,3 +482,67 @@ void TabModeration::removeAvatarResponse(const Response &response) QMessageBox::information(this, tr("Avatar removed"), tr("The avatar of %1 has been removed.").arg(QString::fromStdString(resp.user_name()))); } + +void TabModeration::removeBanner() +{ + if (currentUser.isEmpty()) { + return; + } + + if (!QtUtils::confirm(this, tr("Remove Banner"), + tr("Remove the banner card of %1? The user will have to choose a new one.\n\n" + "Other players stop seeing the banner the next time %1 logs in.") + .arg(currentUser), + QMessageBox::No)) { + return; + } + + Command_RemoveUserBanner cmd; + cmd.set_user_name(currentUser.toStdString()); + PendingCommand *pend = client->prepareModeratorCommand(cmd); + connect(pend, &PendingCommand::finished, this, &TabModeration::removeBannerResponse); + client->sendCommand(pend); +} + +void TabModeration::removeBannerResponse(const Response &response) +{ + const QString target = currentUser.isEmpty() ? tr("this user") : currentUser; + + switch (response.response_code()) { + case Response::RespOk: + break; + case Response::RespNameNotFound: + QMessageBox::warning(this, tr("User not found"), + tr("The server does not know a user named %1.").arg(target)); + return; + case Response::RespFunctionNotAllowed: + QMessageBox::warning(this, tr("Not supported"), + tr("This server does not support removing banner cards. It may be running an older " + "version.")); + return; + case Response::RespInternalError: + QMessageBox::critical( + this, tr("Error"), + tr("The server could not clear the banner card of %1 because of a database error.").arg(target)); + return; + default: + QMessageBox::critical( + this, tr("Error"), + tr("The server rejected the request for %1: %2") + .arg(target, QString::fromStdString(Response_ResponseCode_Name(response.response_code())))); + return; + } + + const Response_RemoveUserBanner &resp = response.GetExtension(Response_RemoveUserBanner::ext); + const QString removedUser = resp.user_name().empty() ? target : QString::fromStdString(resp.user_name()); + if (!resp.had_banner()) { + QMessageBox::information(this, tr("No banner set"), + tr("%1 has no banner card set, so there was nothing to remove.").arg(removedUser)); + return; + } + + QMessageBox::information(this, tr("Banner removed"), + tr("The banner card of %1 has been removed.\n\n" + "Other players stop seeing it the next time %1 logs in.") + .arg(removedUser)); +} diff --git a/cockatrice/src/interface/widgets/tabs/tab_moderation.h b/cockatrice/src/interface/widgets/tabs/tab_moderation.h index e352e90bb..ce23b5a7a 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_moderation.h +++ b/cockatrice/src/interface/widgets/tabs/tab_moderation.h @@ -20,7 +20,7 @@ class TabSupervisor; /** * Staff investigation tool. Lets moderators look up a user's account data, * alternate accounts, login sessions, and staff login activity, and offers - * the password-reset and remove-avatar actions. + * the password-reset, remove-avatar, and remove-banner actions. */ class TabModeration : public Tab { @@ -44,6 +44,8 @@ private slots: void resetPasswordResponse(const Response &response); void removeAvatar(); void removeAvatarResponse(const Response &response); + void removeBanner(); + void removeBannerResponse(const Response &response); private: void requestUserInfo(const QString &userName); @@ -60,6 +62,7 @@ private: QPushButton *investigateButton; QPushButton *resetPasswordButton; QPushButton *removeAvatarButton; + QPushButton *removeBannerButton; QGroupBox *userInfoGroup; QLabel *userInfoNameLabel; diff --git a/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp b/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp index ea623f5a4..dc14b6e4a 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_public_decks.cpp @@ -2,7 +2,6 @@ #include "../../../client/settings/cache_settings.h" #include "../../deck_loader/deck_loader.h" -#include "../../deck_loader/loaded_deck.h" #include "../cards/card_size_widget.h" #include "../general/layout_containers/flow_widget.h" #include "../interface/theme_manager.h" @@ -29,12 +28,14 @@ #include #include #include +#include #include #include #include #include #include #include +#include #include #include @@ -46,16 +47,16 @@ TabPublicDecks::TabPublicDecks(TabSupervisor *_tabSupervisor, AbstractClient *_c titleLabel = new QLabel(tr("Public decks of %1").arg(userName.toHtmlEscaped()), this); // Family comes from the InterfaceDefault theme role; bold stays caller-side. - auto applyThemeFonts = [this]() { - QFont titleFont = themeManager->appFont(AppFont::InterfaceDefault); - titleFont.setBold(true); - titleLabel->setFont(titleFont); + auto applyTitleFont = [this]() { + titleLabel->setFont(QtUtils::boldFont(themeManager->appFont(AppFont::InterfaceDefault))); + }; + applyTitleFont(); + connect(themeManager, &ThemeManager::fontsChanged, this, [this, applyTitleFont] { + applyTitleFont(); // Preview cells paint with the live role font; repaint the grid so the // new family shows without waiting for a scroll or hover. flowWidget->update(); - }; - applyThemeFonts(); - connect(themeManager, &ThemeManager::fontsChanged, this, applyThemeFonts); + }); auto *headerLayout = new QHBoxLayout; headerLayout->addWidget(titleLabel); diff --git a/cockatrice/src/interface/widgets/tabs/tab_replays.cpp b/cockatrice/src/interface/widgets/tabs/tab_replays.cpp index 52bc4cd8f..d37bff3d8 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_replays.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_replays.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include #include #include @@ -45,7 +44,6 @@ #include #include #include -#include #include #include #include @@ -61,6 +59,7 @@ #include #include #include +#include #include #include #include @@ -400,7 +399,7 @@ void TabReplays::actDeleteLocalReplay() void TabReplays::actOpenReplaysFolder() { QString dir = localDirModel->rootPath(); - QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); + QtUtils::openPathInFileManager(dir); } void TabReplays::actRemoteDoubleClick(const QModelIndex &curRight) diff --git a/cockatrice/src/interface/widgets/tabs/tab_report.cpp b/cockatrice/src/interface/widgets/tabs/tab_report.cpp index 1df5bc078..7986286b3 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_report.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_report.cpp @@ -52,6 +52,7 @@ #include #include #include +#include #include #include @@ -212,8 +213,7 @@ TabReport::TabReport(TabSupervisor *_tabSupervisor, AbstractClient *_client) : T sgLayout->addWidget(statsTrendLabel); sgLayout->addWidget(statsCategoriesLabel); sgLayout->addWidget(statsDetailText); - auto *statsGroupLayout = new QVBoxLayout(statsGroup); - statsGroupLayout->setContentsMargins(0, 0, 0, 0); + auto *statsGroupLayout = QtUtils::makeLayout(statsGroup); statsGroupLayout->addWidget(statsContent); statsGroup->setChecked(true); connect(statsGroup, &QGroupBox::toggled, this, [this, statsContent](bool checked) { diff --git a/cockatrice/src/interface/widgets/tabs/tab_room.cpp b/cockatrice/src/interface/widgets/tabs/tab_room.cpp index 67106ba34..f63b77445 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_room.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_room.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -38,9 +39,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -52,6 +55,7 @@ #include #include #include +#include #include #include #include @@ -61,12 +65,45 @@ #include #include +namespace +{ +// Empty id refers to the implicit Main channel. +const QString MAIN_CHANNEL = QString(); + +/** + * @brief Privilege rank mirroring Server_Room::userPrivilegeLevel. + * + * Higher = more privileged. Used to evaluate channel write access locally so + * the input can reflect read-only channels; the server re-checks on send. + */ +int userPrivilegeRank(uint32_t userLevel) +{ + if (userLevel & ServerInfo_User::IsDeveloper) { + return static_cast(ServerInfo_RoomChannel::Developer); + } + if (userLevel & ServerInfo_User::IsAdmin) { + return static_cast(ServerInfo_RoomChannel::Admin); + } + if (userLevel & ServerInfo_User::IsModerator) { + return static_cast(ServerInfo_RoomChannel::Moderator); + } + if (userLevel & ServerInfo_User::IsJudge) { + return static_cast(ServerInfo_RoomChannel::Judge); + } + if (userLevel & ServerInfo_User::IsRegistered) { + return static_cast(ServerInfo_RoomChannel::Registered); + } + return static_cast(ServerInfo_RoomChannel::Public); +} +} // namespace + TabRoom::TabRoom(TabSupervisor *_tabSupervisor, AbstractClient *_client, ServerInfo_User *_ownUser, const ServerInfo_Room &info) : Tab(_tabSupervisor), client(_client), roomId(info.room_id()), roomName(QString::fromStdString(info.name())), - ownUser(_ownUser), userListProxy(_tabSupervisor->getUserListManager()) + ownUser(_ownUser), userListProxy(_tabSupervisor->getUserListManager()), activeChannelId(MAIN_CHANNEL), + chatCollapsed(false) { const int gameTypeListSize = info.gametype_list_size(); for (int i = 0; i < gameTypeListSize; ++i) { @@ -86,14 +123,61 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor, const auto gameInviteLinkProvider = [this]() { return tabSupervisor->getGameInviteLinksForRoom(roomId); }; userList->setGameInviteLinkProvider(gameInviteLinkProvider); - chatView = new ChatView(tabSupervisor, nullptr, true, this); - connect(chatView, &ChatView::showMentionPopup, this, &TabRoom::actShowMentionPopup); - connect(chatView, &ChatView::messageClickedSignal, this, &TabRoom::focusTab); - connect(chatView, &ChatView::openMessageDialog, this, &TabRoom::openMessageDialog); - connect(chatView, &ChatView::cockatriceLinkActivated, this, &TabRoom::cockatriceLinkActivated); - connect(chatView, &ChatView::showCardInfoPopup, this, &TabRoom::showCardInfoPopup); - connect(chatView, &ChatView::deleteCardInfoPopup, this, &TabRoom::deleteCardInfoPopup); - connect(chatView, &ChatView::addMentionTag, this, &TabRoom::addMentionTag); + chatViewStack = new QStackedWidget; + channelTabBar = new QTabBar; + channelTabBar->setShape(QTabBar::RoundedSouth); + channelTabBar->setExpanding(true); + channelTabBar->setUsesScrollButtons(true); + + channelViews.insert(MAIN_CHANNEL, makeChatView()); + channelIds << MAIN_CHANNEL; + channelDisplayNames.insert(MAIN_CHANNEL, tr("Main")); + channelUnreadCounts.insert(MAIN_CHANNEL, 0); + channelMentioned.insert(MAIN_CHANNEL, false); + channelWritable.insert(MAIN_CHANNEL, true); + channelTabBar->addTab(channelDisplayNames.value(MAIN_CHANNEL)); + chatViewStack->addWidget(channelViews.value(MAIN_CHANNEL)); + + const uint32_t ownLevel = ownUser != nullptr ? ownUser->user_level() : 0; + const int ownRank = userPrivilegeRank(ownLevel); + + const int channelCount = info.channel_list_size(); + for (int i = 0; i < channelCount; ++i) { + const ServerInfo_RoomChannel &channel = info.channel_list(i); + const QString channelId = QString::fromStdString(channel.id()); + if (channelId.isEmpty()) { + continue; + } + QString displayName = QString::fromStdString(channel.display_name()); + if (displayName.isEmpty()) { + displayName = channelId; + } + const int writeLevel = channel.has_write_access_level() ? static_cast(channel.write_access_level()) + : static_cast(channel.access_level()); + ChatView *channelView = makeChatView(); + const QString banner = QString::fromStdString(channel.banner_message()); + if (!banner.isEmpty()) { + channelView->appendMessage(banner); + } + channelViews.insert(channelId, channelView); + channelIds << channelId; + channelDisplayNames.insert(channelId, displayName); + channelUnreadCounts.insert(channelId, 0); + channelMentioned.insert(channelId, false); + channelWritable.insert(channelId, ownRank >= writeLevel); + channelTabBar->addTab(displayName); + chatViewStack->addWidget(channelViews.value(channelId)); + } + + chatView = channelViews.value(MAIN_CHANNEL); + chatViewStack->setCurrentWidget(chatView); + channelTabBar->setCurrentIndex(0); + // Old servers never send a channel catalog, so the tab bar would only show + // a single "Main" tab where a misclick hides the chat. Only show it when + // there is more than one channel to switch between. + channelTabBar->setVisible(channelIds.size() > 1); + connect(channelTabBar, &QTabBar::currentChanged, this, &TabRoom::onChannelChanged); + connect(channelTabBar, &QTabBar::tabBarClicked, this, &TabRoom::onChannelTabClicked); connect(&SettingsCache::instance().chat(), &ChatSettings::chatMentionCompleterChanged, this, &TabRoom::actCompleterChanged); sayLabel = new QLabel; @@ -101,6 +185,7 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor, sayEdit->setMaxLength(MAX_TEXT_LENGTH); sayLabel->setBuddy(sayEdit); connect(sayEdit, &LineEditCompleter::returnPressed, this, &TabRoom::sendMessage); + updateSayEnabled(); auto *chatSettingsMenu = new QMenu(this); @@ -122,19 +207,27 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor, sayHbox->addWidget(sayEdit); sayHbox->addWidget(chatSettingsButton); + sayWidget = new QWidget; + sayWidget->setLayout(sayHbox); + auto *chatVbox = new QVBoxLayout; - chatVbox->addWidget(chatView); - chatVbox->addLayout(sayHbox); + chatVbox->addWidget(chatViewStack); + chatVbox->addWidget(sayWidget); + chatVbox->addWidget(channelTabBar); chatGroupBox = new QGroupBox; chatGroupBox->setLayout(chatVbox); - auto *splitter = new QSplitter(Qt::Vertical); - splitter->addWidget(gameSelector); - splitter->addWidget(chatGroupBox); + chatSplitter = new QSplitter(Qt::Vertical); + chatSplitter->addWidget(gameSelector); + chatSplitter->addWidget(chatGroupBox); + // Splitter children are collapsible by default, so a zero size would hide + // the whole chat group box including the tab bar. Keep it clamped to its + // minimum size (title + tab bar) when collapsed. + chatSplitter->setCollapsible(1, false); auto *hbox = new QHBoxLayout; - hbox->addWidget(splitter, 3); + hbox->addWidget(chatSplitter, 3); hbox->addWidget(userListPanel, 1); aLeaveRoom = new QAction(this); @@ -189,7 +282,9 @@ TabRoom::TabRoom(TabSupervisor *_tabSupervisor, void TabRoom::retranslateUi() { gameSelector->retranslateUi(); - chatView->retranslateUi(); + for (auto it = channelViews.constBegin(); it != channelViews.constEnd(); ++it) { + it.value()->retranslateUi(); + } userListPanel->retranslateUi(); sayLabel->setText(tr("&Say:")); chatGroupBox->setTitle(tr("Chat")); @@ -197,6 +292,11 @@ void TabRoom::retranslateUi() aLeaveRoom->setText(tr("&Leave room")); aClearChat->setText(tr("&Clear chat")); aOpenChatSettings->setText(tr("Chat Settings...")); + channelDisplayNames.insert(MAIN_CHANNEL, tr("Main")); + for (const QString &channelId : channelIds) { + updateChannelTab(channelId); + } + updateSayEnabled(); } void TabRoom::focusTab() @@ -233,6 +333,11 @@ void TabRoom::tabActivated() if (!sayEdit->hasFocus()) { sayEdit->setFocus(); } + if (!chatCollapsed) { + channelUnreadCounts[activeChannelId] = 0; + channelMentioned[activeChannelId] = false; + updateChannelTab(activeChannelId); + } } QString TabRoom::sanitizeHtml(QString dirty) const @@ -240,29 +345,232 @@ QString TabRoom::sanitizeHtml(QString dirty) const return dirty.replace("&", "&").replace("<", "<").replace(">", ">"); } +void TabRoom::updateSayEnabled() +{ + const bool writable = channelWritable.value(activeChannelId, true); + sayLabel->setEnabled(writable); + sayEdit->setEnabled(writable); + sayEdit->setPlaceholderText(writable ? QString() : tr("This channel is read-only.")); +} + void TabRoom::sendMessage() { + if (!channelWritable.value(activeChannelId, true)) { + return; + } if (sayEdit->text().isEmpty()) { return; } else if (sayEdit->hasVisibleCompleterPopup()) { sayEdit->hideCompleterPopups(); return; } else { - Command_RoomSay cmd; - cmd.set_message(sayEdit->text().toStdString()); + ChatView *targetView = channelViews.value(activeChannelId, chatView); + auto onFinished = [targetView](const Response &response) { + if (response.response_code() == Response::RespChatFlood) { + targetView->appendMessage(tr("You are flooding the chat. Please wait a couple of seconds.")); + } else if (response.response_code() == Response::RespFunctionNotAllowed || + response.response_code() == Response::RespInvalidCommand) { + targetView->appendMessage(tr("Your message could not be sent: the channel is not available or you " + "are not allowed to talk there.")); + } + }; - PendingCommand *pend = prepareRoomCommand(cmd); - connect(pend, &PendingCommand::finished, this, &TabRoom::sayFinished); - sendRoomCommand(pend); + if (activeChannelId.isEmpty()) { + Command_RoomSay cmd; + cmd.set_message(sayEdit->text().toStdString()); + + PendingCommand *pend = prepareRoomCommand(cmd); + connect(pend, &PendingCommand::finished, this, onFinished); + sendRoomCommand(pend); + } else { + Command_RoomChannelSay cmd; + cmd.set_channel_id(activeChannelId.toStdString()); + cmd.set_message(sayEdit->text().toStdString()); + + PendingCommand *pend = prepareRoomCommand(cmd); + connect(pend, &PendingCommand::finished, this, onFinished); + sendRoomCommand(pend); + } sayEdit->clear(); } } -void TabRoom::sayFinished(const Response &response) +void TabRoom::onChannelChanged(int index) { - if (response.response_code() == Response::RespChatFlood) { - chatView->appendMessage(tr("You are flooding the chat. Please wait a couple of seconds.")); + if (index < 0 || index >= channelIds.size()) { + return; } + activeChannelId = channelIds.at(index); + ChatView *view = channelViews.value(activeChannelId); + if (view) { + chatView = view; + chatViewStack->setCurrentWidget(view); + } + // Viewing a channel clears its unread state + channelUnreadCounts[activeChannelId] = 0; + channelMentioned[activeChannelId] = false; + updateChannelTab(activeChannelId); + updateSayEnabled(); + if (chatCollapsed) { + setChatCollapsed(false); + } +} + +void TabRoom::onChannelTabClicked(int index) +{ + if (index < 0 || index >= channelIds.size()) { + return; + } + // Clicking the active tab toggles collapse; clicking another tab switches to it + if (channelTabBar->currentIndex() == index && channelIds.at(index) == activeChannelId) { + setChatCollapsed(!chatCollapsed); + } +} + +void TabRoom::setChatCollapsed(bool collapsed) +{ + chatCollapsed = collapsed; + chatViewStack->setVisible(!collapsed); + sayWidget->setVisible(!collapsed); + if (collapsed) { + // Minimize the chat section of the splitter down to just the tab bar + savedSplitterSizes = chatSplitter->sizes(); + chatSplitter->setSizes({1, 0}); + } else { + // Expanding onto the active channel marks it read + channelUnreadCounts[activeChannelId] = 0; + channelMentioned[activeChannelId] = false; + updateChannelTab(activeChannelId); + chatViewStack->setCurrentWidget(chatView); + if (!savedSplitterSizes.isEmpty()) { + chatSplitter->setSizes(savedSplitterSizes); + } + } +} + +QColor TabRoom::parseColorHex(const QString &hex, const QColor &fallback) +{ +#if (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)) + QColor color = QColor::fromString("#" + hex); +#else + QColor color; + color.setNamedColor("#" + hex); +#endif + return color.isValid() ? color : fallback; +} + +void TabRoom::updateChannelTab(const QString &channelId) +{ + int index = channelIds.indexOf(channelId); + if (index < 0) { + return; + } + QString baseName = channelDisplayNames.value(channelId, channelId.isEmpty() ? tr("Main") : channelId); + int unread = channelUnreadCounts.value(channelId, 0); + bool mentioned = channelMentioned.value(channelId, false); + + QString label = baseName; + if (unread > 0) { + label += tr(" (%1)").arg(unread); + } + channelTabBar->setTabText(index, label); + + if (mentioned) { + channelTabBar->setTabTextColor( + index, parseColorHex(SettingsCache::instance().chat().getChannelMentionColor(), QColor(194, 31, 47))); + } else if (unread > 0) { + channelTabBar->setTabTextColor( + index, parseColorHex(SettingsCache::instance().chat().getChannelUnreadColor(), QColor(26, 123, 0))); + } else { + channelTabBar->setTabTextColor(index, QColor()); + } +} + +bool TabRoom::messageContainsMention(const QString &message, const ServerInfo_User &senderInfo) const +{ + if (ownUser == nullptr) { + return false; + } + QString ownName = QString::fromStdString(ownUser->name()); + if (ownName.isEmpty()) { + return false; + } + // Match ChatView::checkMention / checkWord: whole-word, case-insensitive. + // "@zach" must not match "@zachary", and highlight words match full words. + auto containsWholeWord = [](const QString &haystack, const QString &needle) { + if (needle.isEmpty()) { + return false; + } + QRegularExpression re(QStringLiteral("\\b%1\\b").arg(QRegularExpression::escape(needle)), + QRegularExpression::CaseInsensitiveOption); + return re.match(haystack).hasMatch(); + }; + + if (containsWholeWord(message, "@" + ownName)) { + return true; + } + // ChatView only highlights @all for moderator/admin senders + // (ChatView::isModeratorSendingGlobal). Gating here keeps the tab + // mention color from drifting ahead of the message view. + const uint32_t senderLevel = senderInfo.user_level(); + const bool senderIsPrivileged = + (senderLevel & ServerInfo_User::IsModerator) || (senderLevel & ServerInfo_User::IsAdmin); + if (senderIsPrivileged && containsWholeWord(message, "@all")) { + return true; + } + const QStringList words = SettingsCache::instance().chat().getHighlightWords().split(' ', Qt::SkipEmptyParts); + for (const QString &word : words) { + if (containsWholeWord(message, word)) { + return true; + } + } + return false; +} + +void TabRoom::markChannelMessage(const QString &channelId, const QString &message, const ServerInfo_User &senderInfo) +{ + bool roomVisible = (tabSupervisor->currentWidget() == this); + bool viewing = (channelId == activeChannelId) && !chatCollapsed && roomVisible; + if (viewing) { + return; + } + channelUnreadCounts[channelId] = channelUnreadCounts.value(channelId, 0) + 1; + if (messageContainsMention(message, senderInfo)) { + channelMentioned[channelId] = true; + } + updateChannelTab(channelId); +} + +bool TabRoom::isRoomMessageIgnored(const QString &senderName, ServerInfo_User &userInfoOut) const +{ + if (userListProxy->isUserIgnored(senderName)) { + return true; + } + + UserListTWI *twi = userList->getUsers().value(senderName); + if (twi) { + userInfoOut = twi->getUserInfo(); + if (SettingsCache::instance().chat().getIgnoreUnregisteredUsers() && + !UserLevelFlags(userInfoOut.user_level()).testFlag(ServerInfo_User::IsRegistered)) { + return true; + } + } else { + userInfoOut = ServerInfo_User{}; + } + return false; +} + +ChatView *TabRoom::makeChatView() +{ + ChatView *view = new ChatView(tabSupervisor, nullptr, true, this); + connect(view, &ChatView::showMentionPopup, this, &TabRoom::actShowMentionPopup); + connect(view, &ChatView::messageClickedSignal, this, &TabRoom::focusTab); + connect(view, &ChatView::openMessageDialog, this, &TabRoom::openMessageDialog); + connect(view, &ChatView::cockatriceLinkActivated, this, &TabRoom::cockatriceLinkActivated); + connect(view, &ChatView::showCardInfoPopup, this, &TabRoom::showCardInfoPopup); + connect(view, &ChatView::deleteCardInfoPopup, this, &TabRoom::deleteCardInfoPopup); + connect(view, &ChatView::addMentionTag, this, &TabRoom::addMentionTag); + return view; } void TabRoom::actClearChat() @@ -298,6 +606,9 @@ void TabRoom::processRoomEvent(const RoomEvent &event) case RoomEvent::ROOM_SAY: processRoomSayEvent(event.GetExtension(Event_RoomSay::ext)); break; + case RoomEvent::ROOM_CHANNEL_SAY: + processRoomChannelSayEvent(event.GetExtension(Event_RoomChannelSay::ext)); + break; case RoomEvent::REMOVE_MESSAGES: processRemoveMessagesEvent(event.GetExtension(Event_RemoveMessages::ext)); break; @@ -335,18 +646,9 @@ void TabRoom::processRoomSayEvent(const Event_RoomSay &event) QString senderName = QString::fromStdString(event.name()); QString message = QString::fromStdString(event.message()); - if (userListProxy->isUserIgnored(senderName)) { - return; - } - - UserListTWI *twi = userList->getUsers().value(senderName); ServerInfo_User userInfo = {}; - if (twi) { - userInfo = twi->getUserInfo(); - if (SettingsCache::instance().chat().getIgnoreUnregisteredUsers() && - !UserLevelFlags(userInfo.user_level()).testFlag(ServerInfo_User::IsRegistered)) { - return; - } + if (isRoomMessageIgnored(senderName, userInfo)) { + return; } if (event.message_type() == Event_RoomSay::ChatHistory && !SettingsCache::instance().chat().getRoomHistory()) { @@ -360,7 +662,49 @@ void TabRoom::processRoomSayEvent(const Event_RoomSay &event) "] " + message; } - chatView->appendMessage(message, event.message_type(), userInfo, true); + ChatView *mainView = channelViews.value(MAIN_CHANNEL); + if (mainView == nullptr) { + return; + } + + mainView->appendMessage(message, event.message_type(), userInfo, true); + if (event.message_type() != Event_RoomSay::ChatHistory) { + markChannelMessage(MAIN_CHANNEL, message, userInfo); + } + emit userEvent(false); +} + +void TabRoom::processRoomChannelSayEvent(const Event_RoomChannelSay &event) +{ + const QString channelId = QString::fromStdString(event.channel_id()); + ChatView *view = channelViews.value(channelId); + if (view == nullptr) { + return; + } + + QString senderName = QString::fromStdString(event.name()); + QString message = QString::fromStdString(event.message()); + + ServerInfo_User userInfo = {}; + if (isRoomMessageIgnored(senderName, userInfo)) { + return; + } + + if (senderName.isEmpty()) { + if (!SettingsCache::instance().chat().getRoomHistory()) { + return; + } + message = + "[" + + QString(QDateTime::fromMSecsSinceEpoch(event.time_of()).toLocalTime().toString("d MMM yyyy HH:mm:ss")) + + "] " + message; + } + + view->appendMessage(message, {}, userInfo, true); + if (!senderName.isEmpty()) { + // Empty senders mark replayed history, which must not count as unread + markChannelMessage(channelId, message, userInfo); + } emit userEvent(false); } @@ -368,7 +712,9 @@ void TabRoom::processRemoveMessagesEvent(const Event_RemoveMessages &event) { QString userName = QString::fromStdString(event.name()); int amount = event.amount(); - chatView->redactMessages(userName, amount); + for (ChatView *view : channelViews) { + view->redactMessages(userName, amount); + } } void TabRoom::refreshShortcuts() diff --git a/cockatrice/src/interface/widgets/tabs/tab_room.h b/cockatrice/src/interface/widgets/tabs/tab_room.h index 031056145..8f7c8f918 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_room.h +++ b/cockatrice/src/interface/widgets/tabs/tab_room.h @@ -10,6 +10,7 @@ #include "tab.h" +#include #include #include #include @@ -18,8 +19,12 @@ class UserListProxy; class QAction; +class QTabBar; +class QWidget; class QGroupBox; +class QSplitter; class QMenu; +class QStackedWidget; class QStringListModel; class TabSupervisor; @@ -42,6 +47,7 @@ class Event_ListGames; class Event_JoinRoom; class Event_LeaveRoom; class Event_RoomSay; +class Event_RoomChannelSay; class Event_RemoveMessages; class GameSelector; class Response; @@ -64,10 +70,23 @@ private: UserListWidget *userList; const UserListProxy *userListProxy; ChatView *chatView; + QStackedWidget *chatViewStack; + QTabBar *channelTabBar; + QWidget *sayWidget; + QMap channelViews; + QMap channelDisplayNames; + QMap channelUnreadCounts; + QMap channelMentioned; + QMap channelWritable; + QStringList channelIds; + QString activeChannelId; + bool chatCollapsed; QLabel *sayLabel; LineEditCompleter *sayEdit; QStringListModel *mentionModel; QGroupBox *chatGroupBox; + QSplitter *chatSplitter; + QList savedSplitterSizes; QMenu *roomMenu; QAction *aLeaveRoom; @@ -85,7 +104,9 @@ signals: void gameListUpdated(); private slots: void sendMessage(); - void sayFinished(const Response &response); + void onChannelChanged(int index); + void onChannelTabClicked(int index); + void setChatCollapsed(bool collapsed); void actClearChat(); void actOpenChatSettings(); void addMentionTag(QString mentionTag); @@ -98,9 +119,18 @@ private slots: void processJoinRoomEvent(const Event_JoinRoom &event); void processLeaveRoomEvent(const Event_LeaveRoom &event); void processRoomSayEvent(const Event_RoomSay &event); + void processRoomChannelSayEvent(const Event_RoomChannelSay &event); void processRemoveMessagesEvent(const Event_RemoveMessages &event); void refreshShortcuts(); + ChatView *makeChatView(); + void updateChannelTab(const QString &channelId); + void updateSayEnabled(); + void markChannelMessage(const QString &channelId, const QString &message, const ServerInfo_User &senderInfo); + [[nodiscard]] bool messageContainsMention(const QString &message, const ServerInfo_User &senderInfo) const; + [[nodiscard]] bool isRoomMessageIgnored(const QString &senderName, ServerInfo_User &userInfoOut) const; + static QColor parseColorHex(const QString &hex, const QColor &fallback); + protected slots: void closeEvent(QCloseEvent *event) override; diff --git a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp index 2352d7d7b..3316232a4 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_supervisor.cpp @@ -2,10 +2,6 @@ #include "../../../client/settings/cache_settings.h" #include "../../../client/settings/shortcuts_settings.h" -#include "../../../game/abstract_game.h" -#include "../../../game/game_event_handler.h" -#include "../../../game/game_meta_info.h" -#include "../../deck_loader/loaded_deck.h" #include "../../intents/contexts/context_connect_to_server.h" #include "../../intents/contexts/context_join_game.h" #include "../../intents/contexts/context_join_room.h" @@ -51,8 +47,6 @@ #include #include #include -#include -#include #include #include #include @@ -65,6 +59,10 @@ #include #include #include +#include +#include +#include +#include #include #include #include @@ -79,6 +77,7 @@ #include #include #include +#include #include #include #include @@ -291,48 +290,11 @@ void TabSupervisor::retranslateUi() aTabDeveloper->setText(tr("Developer")); // tabs - QList tabs; - tabs.append(tabServer); - tabs.append(tabReplays); - tabs.append(tabDeckStorage); - tabs.append(tabAdmin); - tabs.append(tabAccount); - tabs.append(tabLog); - tabs.append(tabReport); - tabs.append(tabModeration); - tabs.append(tabCardArtRules); - tabs.append(tabDeveloper); - QMapIterator roomIterator(roomTabs); - while (roomIterator.hasNext()) { - tabs.append(roomIterator.next().value()); - } - QMapIterator gameIterator(gameTabs); - while (gameIterator.hasNext()) { - tabs.append(gameIterator.next().value()); - } - QMapIterator publicDecksIterator(publicDecksTabs); - while (publicDecksIterator.hasNext()) { - tabs.append(publicDecksIterator.next().value()); - } - QListIterator replayIterator(replayTabs); - while (replayIterator.hasNext()) { - tabs.append(replayIterator.next()); - } - QListIterator deckEditorIterator(deckEditorTabs); - while (deckEditorIterator.hasNext()) { - tabs.append(deckEditorIterator.next()); - } - QMapIterator messageIterator(messageTabs); - while (messageIterator.hasNext()) { - tabs.append(messageIterator.next().value()); - } - - for (auto &tab : tabs) { - if (tab) { - int idx = indexOf(tab); + for (int i = 0; i < count(); ++i) { + if (auto *tab = qobject_cast(widget(i))) { QString tabText = tab->getTabText(); - setTabText(idx, sanitizeTabName(tabText)); - setTabToolTip(idx, sanitizeHtml(tabText)); + setTabText(i, sanitizeTabName(tabText)); + setTabToolTip(i, sanitizeHtml(tabText)); tab->retranslateUi(); } } @@ -362,9 +324,8 @@ void TabSupervisor::closeEvent(QCloseEvent *event) QTabWidget::closeEvent(event); if (getGameCount()) { - if (QMessageBox::question(this, tr("Are you sure?"), - tr("There are still open games. Are you sure you want to quit?"), - QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) { + if (!QtUtils::confirm(this, tr("Are you sure?"), + tr("There are still open games. Are you sure you want to quit?"), QMessageBox::No)) { event->ignore(); return; } diff --git a/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.cpp b/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.cpp index f4b007072..26d29463b 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_visual_database_display.cpp @@ -23,9 +23,10 @@ TabVisualDatabaseDisplay::TabVisualDatabaseDisplay(TabSupervisor *_tabSupervisor void TabVisualDatabaseDisplay::retranslateUi() { + visualDatabaseDisplayWidget->retranslateUi(); } void TabVisualDatabaseDisplay::openEdhrecTab(const CardInfoPtr &info, bool isCommander) const { getTabSupervisor()->addEdhrecTab(info, isCommander); -} \ No newline at end of file +} diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp index be7255dc3..bf4e15aa6 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_editor/tab_deck_editor_visual.cpp @@ -2,7 +2,6 @@ #include "../../../../client/settings/cache_settings.h" #include "../../../../client/settings/shortcuts_settings.h" -#include "../../../deck_loader/loaded_deck.h" #include "../../deck_editor/deck_editor_card_database_dock_widget.h" #include "../../deck_editor/deck_editor_card_info_dock_widget.h" #include "../../deck_editor/deck_editor_deck_dock_widget.h" @@ -44,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -61,8 +61,8 @@ TabDeckEditorVisual::TabDeckEditorVisual(TabSupervisor *_tabSupervisor) : Abstra { setObjectName("TabDeckEditorVisual"); - createCentralFrame(); createMenus(); + createCentralFrame(); installEventFilter(this); @@ -105,7 +105,6 @@ void TabDeckEditorVisual::createCentralFrame() centralFrame->addWidget(tabContainer); setCentralWidget(centralWidget); - setDockOptions(QMainWindow::AnimatedDocks | QMainWindow::AllowNestedDocks | QMainWindow::AllowTabbedDocks); } /** @brief Updates the visual deck, analytics, and sample hand after a deck change. */ diff --git a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp index cb6dfab9c..ece9881b4 100644 --- a/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp +++ b/cockatrice/src/interface/widgets/tabs/visual_deck_storage/tab_deck_storage_visual.cpp @@ -1,9 +1,7 @@ #include "tab_deck_storage_visual.h" #include "../../../../client/settings/cache_settings.h" -#include "../../../deck_loader/deck_file_format.h" #include "../../../deck_loader/deck_loader.h" -#include "../../../deck_loader/loaded_deck.h" #include "../../cards/additional_info/deck_color_identity.h" #include "../../deck_share/deck_share_utils.h" #include "../../deck_share/share_bar_widget.h" @@ -17,12 +15,15 @@ #include #include #include +#include #include +#include #include #include #include #include #include +#include #include #include @@ -46,9 +47,7 @@ TabDeckStorageVisual::TabDeckStorageVisual(TabSupervisor *_tabSupervisor, Abstra }); auto *widget = new QWidget(this); - auto *layout = new QVBoxLayout(widget); - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(0); + auto *layout = QtUtils::makeLayout(widget, 0); widget->setLayout(layout); this->setCentralWidget(widget); layout->addWidget(visualDeckStorageWidget); diff --git a/cockatrice/src/interface/widgets/utility/card_completer_delegate.cpp b/cockatrice/src/interface/widgets/utility/card_completer_delegate.cpp index 073d6e10b..0090e9d65 100644 --- a/cockatrice/src/interface/widgets/utility/card_completer_delegate.cpp +++ b/cockatrice/src/interface/widgets/utility/card_completer_delegate.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include class QObject; @@ -182,9 +183,8 @@ void CardCompleterDelegate::drawManaSymbol(QPainter *p, QPoint centre, const QSt p->setBrush(Qt::NoBrush); p->drawEllipse(pip.adjusted(1, 1, -1, -1)); - QFont f = themeManager->appFont(AppFont::CardText); + QFont f = QtUtils::boldFont(themeManager->appFont(AppFont::CardText)); f.setPixelSize(qMax(radius - 1, 7)); - f.setBold(true); p->setFont(f); p->setPen(mc.text); diff --git a/cockatrice/src/interface/widgets/utility/card_completer_styler.cpp b/cockatrice/src/interface/widgets/utility/card_completer_styler.cpp index f7072686f..1a17bb8be 100644 --- a/cockatrice/src/interface/widgets/utility/card_completer_styler.cpp +++ b/cockatrice/src/interface/widgets/utility/card_completer_styler.cpp @@ -32,6 +32,7 @@ #include #include #include +#include #include template class QSharedPointer; @@ -138,29 +139,33 @@ bool CardCompleterStyler::handlePopupKeyPress(QKeyEvent *event) return false; } - if (!above) { - return false; - } - auto *popup = completer->popup(); const int rowCount = completer->completionModel()->rowCount(); if (rowCount == 0) { return true; } + // Handle navigation ourselves so arrow keys move the highlight without + // rewriting the line edit. QCompleter's default handling selects with a + // selection change that QLineEdit turns into setText(), which fights the + // user while typing (e.g. typing "A" becomes "Axe" and cannot be edited). // With the popup above the text edit the rows are shown in reverse order and // the closest match is in the bottom-most row. The up arrow then advances // through the list (towards its end at the top of the popup). - const int currentRow = popup->currentIndex().row(); - const int step = (key == Qt::Key_Up) ? -1 : 1; - const int newRow = qBound(0, currentRow + step, rowCount - 1); - - if (newRow != currentRow) { - const QModelIndex newIndex = completer->completionModel()->index(newRow, completer->completionColumn()); - popup->selectionModel()->setCurrentIndex(newIndex, - QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + const QModelIndex current = popup->currentIndex(); + int newRow; + if (!current.isValid()) { + newRow = (key == Qt::Key_Down) ? 0 : rowCount - 1; + } else { + const int step = (key == Qt::Key_Up) ? -1 : 1; + newRow = qBound(0, current.row() + step, rowCount - 1); + if (newRow == current.row()) { + return true; + } } + selectWithoutRewrite(completer->completionModel()->index(newRow, completer->completionColumn())); + return true; } @@ -223,8 +228,37 @@ void CardCompleterStyler::ensureClosestSelected() const int closestRow = rowCount - 1; if (currentRow != closestRow) { - popup->selectionModel()->setCurrentIndex(completionModel->index(closestRow, completer->completionColumn()), - QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + selectWithoutRewrite(completionModel->index(closestRow, completer->completionColumn())); + } +} + +// --------------------------------------------------------------------------- + +void CardCompleterStyler::selectWithoutRewrite(const QModelIndex &index) +{ + auto *popup = completer->popup(); + auto *selectionModel = popup->selectionModel(); + + // Changing the popup selection emits selectionChanged, which QCompleter + // turns into highlighted() and QLineEdit turns into setText(). That + // rewrites what the user typed (e.g. "A" becomes "Axe") and fights further + // typing. Block the signals so the highlight and preview move without + // touching the line edit; Enter/click still activates via activated(). + const bool wasBlocked = selectionModel->blockSignals(true); + if (!index.isValid()) { + selectionModel->clear(); + } else { + selectionModel->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); + } + selectionModel->blockSignals(wasBlocked); + popup->viewport()->update(); + + // currentRowChanged was suppressed above, so sync the preview manually. + updatePreview(index); + if (index.isValid()) { + popup->scrollTo(index, QAbstractItemView::EnsureVisible); + } else { + popup->scrollToTop(); } } @@ -318,12 +352,7 @@ void CardCompleterStyler::showPreview() preview->show(); preview->raise(); - auto *fade = new QPropertyAnimation(preview, "windowOpacity", preview); - - fade->setDuration(FadeDuration); - fade->setStartValue(preview->windowOpacity()); - fade->setEndValue(1.0); - fade->start(QAbstractAnimation::DeleteWhenStopped); + QtUtils::fadeWindow(preview, preview->windowOpacity(), 1.0, FadeDuration); } // --------------------------------------------------------------------------- diff --git a/cockatrice/src/interface/widgets/utility/card_completer_styler.h b/cockatrice/src/interface/widgets/utility/card_completer_styler.h index 2912f7312..04310e676 100644 --- a/cockatrice/src/interface/widgets/utility/card_completer_styler.h +++ b/cockatrice/src/interface/widgets/utility/card_completer_styler.h @@ -55,6 +55,7 @@ private: void applyPopupWidth(); void updateOrientation(); void ensureClosestSelected(); + void selectWithoutRewrite(const QModelIndex &index); bool handlePopupKeyPress(QKeyEvent *event); bool isPopupAboveWidget() const; diff --git a/cockatrice/src/interface/widgets/utility/sequence_edit.cpp b/cockatrice/src/interface/widgets/utility/sequence_edit.cpp index e92287c9a..44e6581e9 100644 --- a/cockatrice/src/interface/widgets/utility/sequence_edit.cpp +++ b/cockatrice/src/interface/widgets/utility/sequence_edit.cpp @@ -15,6 +15,7 @@ #include #include #include +#include SequenceEdit::SequenceEdit(const QString &_shortcutName, QWidget *parent) : QWidget(parent) { @@ -26,9 +27,7 @@ SequenceEdit::SequenceEdit(const QString &_shortcutName, QWidget *parent) : QWid clearButton->setIcon(themePixmap(QStringLiteral("icons/clearsearch"))); defaultButton->setIcon(themePixmap(QStringLiteral("icons/update"))); - auto *layout = new QHBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(1); + auto *layout = QtUtils::makeLayout(this, 1); layout->addWidget(lineEdit); layout->addWidget(clearButton); layout->addWidget(defaultButton); diff --git a/cockatrice/src/interface/widgets/visual_database_display/card_database_visual_view.cpp b/cockatrice/src/interface/widgets/visual_database_display/card_database_visual_view.cpp new file mode 100644 index 000000000..a8262c75a --- /dev/null +++ b/cockatrice/src/interface/widgets/visual_database_display/card_database_visual_view.cpp @@ -0,0 +1,206 @@ +#include "card_database_visual_view.h" + +#include "../../card_picture_loader/card_picture_loader.h" +#include "../cards/card_info_picture_widget.h" +#include "../cards/card_picture_delegate.h" +#include "../cards/card_size_widget.h" +#include "card_database_visual_display_model.h" +#include "libcockatrice/card/printing/exact_card.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +CardDatabaseVisualView::CardDatabaseVisualView(CardDatabaseVisualDisplayModel *_model, QWidget *parent) + : QListView(parent), model(_model) +{ + auto *cardDelegate = new CardPictureDelegate(model, this); + setItemDelegate(cardDelegate); + setModel(model); + + setViewMode(QListView::IconMode); + setFlow(QListView::LeftToRight); + setWrapping(true); + setResizeMode(QListView::Adjust); + setMovement(QListView::Static); + setUniformItemSizes(true); + setLayoutMode(QListView::SinglePass); + setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + setFrameShape(QFrame::NoFrame); + setWordWrap(false); + + // QListView hands the delegate's item height to the scroll bar as its single + // step under ScrollPerPixel, so one wheel notch would jump wheelScrollLines() + // whole card rows. Give the bar a text line instead; the wheel handler below + // scales that line to the grid. A positive step also stops Qt from taking the + // item height back on the next layout. + verticalScrollBar()->setSingleStep(qMax(1, fontMetrics().height())); + + // The grid is a read-only surface: clicks are actions on a card, not a + // selection, and they must not steal focus from the search field. + setSelectionMode(QAbstractItemView::NoSelection); + setEditTriggers(QAbstractItemView::NoEditTriggers); + setFocusPolicy(Qt::NoFocus); + + // Hover drives the card preview. + setMouseTracking(true); + viewport()->setMouseTracking(true); + + connect(&CardPictureLoader::getInstance(), &CardPictureLoader::pixmapLoaded, this, + [this] { viewport()->update(); }); + + // A new search result starts at the top, like the widget-per-card grid did. + connect(model, &QAbstractItemModel::modelReset, this, [this] { + verticalScrollBar()->setValue(0); + // The rows under a stationary cursor changed with the reset: re-resolve the + // hover so the preview follows the grid, as enterEvent did per card widget. + hoveredRow = -1; + updateHoveredRow(viewport()->mapFromGlobal(QCursor::pos())); + }); + + setCardSizeFactor(cardSizeFactor); +} + +void CardDatabaseVisualView::setCardSizeFactor(int factor) +{ + cardSizeFactor = qBound(CardSizeWidget::MIN_CARD_SIZE, factor, CardSizeWidget::MAX_CARD_SIZE); + + const QSize cardSize = CardInfoPictureWidget::sizeForScaleFactor(cardSizeFactor); + if (auto *cardDelegate = qobject_cast(itemDelegate())) { + cardDelegate->setCardSize(cardSize); + } + // The delegate's sizeHint does not emit sizeHintChanged, so the grid size is what + // makes a resize take effect. Keep the two in step. + setGridSize(cardSize + QSize(CardPictureDelegate::SPACING, CardPictureDelegate::SPACING)); +} + +void CardDatabaseVisualView::mousePressEvent(QMouseEvent *event) +{ + const QModelIndex index = indexAt(event->pos()); + if (index.isValid() && model != nullptr) { + const ExactCard card = model->cardAt(index.row()); + if (card) { + emit cardClicked(event, card); + event->accept(); + return; + } + } + + QListView::mousePressEvent(event); +} + +void CardDatabaseVisualView::mouseMoveEvent(QMouseEvent *event) +{ + QListView::mouseMoveEvent(event); + updateHoveredRow(event->pos()); +} + +bool CardDatabaseVisualView::viewportEvent(QEvent *event) +{ + // A per-card widget reported its hover from enterEvent, which also fires when the + // grid changes under a stationary cursor. A QListView only reports moves, so + // re-resolve the hover on enter and clear it on leave. + if (event->type() == QEvent::Enter) { + updateHoveredRow(viewport()->mapFromGlobal(QCursor::pos())); + } else if (event->type() == QEvent::Leave) { + hoveredRow = -1; + } + + return QListView::viewportEvent(event); +} + +void CardDatabaseVisualView::wheelEvent(QWheelEvent *event) +{ + // Ctrl+wheel is the card-size gesture and is filtered out before it reaches + // the view; any other modifier keeps QAbstractSlider's own page scrolling. + if (event->modifiers() != Qt::NoModifier) { + QListView::wheelEvent(event); + return; + } + + // macOS uses transient (overlay) scroll bars that hide themselves + // once they fade out, so we need to use the range to determine if we ignore the wheel when there is nothing to + // scroll. This lets an enclosing scroll area take over. + QScrollBar *bar = verticalScrollBar(); + if (bar->maximum() <= bar->minimum()) { + event->ignore(); + return; + } + + // Touchpads and high-resolution wheels already report pixels: follow them. + if (!event->pixelDelta().isNull()) { + bar->setValue(bar->value() - event->pixelDelta().y()); + event->accept(); + return; + } + + if (event->angleDelta().y() == 0) { + QListView::wheelEvent(event); + return; + } + + // Accumulate wheel deltas smaller than a notch, like QAbstractSlider does, so + // a fine-grained wheel still moves. + wheelRemainder += static_cast(event->angleDelta().y()) / 120.0; + const int notches = static_cast(wheelRemainder); + if (notches == 0) { + event->accept(); + return; + } + wheelRemainder -= notches; + + const int pixels = -notches * wheelStep(); + bar->setValue(qBound(bar->minimum(), bar->value() + pixels, bar->maximum())); + event->accept(); +} + +int CardDatabaseVisualView::wheelStep() const +{ + // A card row is far too coarse for one wheel line, and a text line is too + // fine, so a line is an eighth of the grid cell. A notch then moves about a + // third of a card row at any card size, and the OS line count still applies. + const int lineHeight = qMax(1, gridSize().height() / 8); + return qMax(1, QApplication::wheelScrollLines()) * lineHeight; +} + +void CardDatabaseVisualView::updateHoveredRow(const QPoint &position) +{ + // indexAt() also answers for points outside the viewport, so a cursor parked + // below the grid (on the size slider, say) would resolve to the row just under + // the visible area. Only a point inside the viewport can hover a card. + if (model == nullptr || !viewport()->rect().contains(position)) { + return; + } + + const QModelIndex index = indexAt(position); + const int row = index.isValid() ? index.row() : -1; + if (row == hoveredRow) { + return; + } + + hoveredRow = row; + if (row >= 0) { + const ExactCard card = model->cardAt(row); + if (card) { + emit cardHovered(card); + } + } +} + +void CardDatabaseVisualView::leaveEvent(QEvent *event) +{ + hoveredRow = -1; + QListView::leaveEvent(event); +} diff --git a/cockatrice/src/interface/widgets/visual_database_display/card_database_visual_view.h b/cockatrice/src/interface/widgets/visual_database_display/card_database_visual_view.h new file mode 100644 index 000000000..5f004484d --- /dev/null +++ b/cockatrice/src/interface/widgets/visual_database_display/card_database_visual_view.h @@ -0,0 +1,67 @@ +/** + * @file card_database_visual_view.h + * @ingroup VisualCardDatabaseWidgets + * @brief Icon-mode view of the card database, one card picture per item. + */ + +#ifndef CARD_DATABASE_VISUAL_VIEW_H +#define CARD_DATABASE_VISUAL_VIEW_H + +#include +#include +#include + +class CardDatabaseVisualDisplayModel; +class ExactCard; +class QEvent; +class QMouseEvent; +class QPoint; +class QWheelEvent; + +/** + * @brief A wrapping grid of card pictures. + * + * The view virtualizes painting: it only asks the delegate for the items that are + * on screen, so the whole card database can sit in the model without a widget (or + * pixmap) per card. Clicks and hover are reported with the exact card under the + * cursor, which is what the deck editor needs to add, decrement or preview a card. + */ +class CardDatabaseVisualView : public QListView +{ + Q_OBJECT + +public: + explicit CardDatabaseVisualView(CardDatabaseVisualDisplayModel *_model, QWidget *parent = nullptr); + + /** + * @brief Sets the card size as a percentage of the base card width. + * @param factor Percentage in the range of the card size slider. + */ + void setCardSizeFactor(int factor); + +signals: + /** @brief A card was pressed. The event is valid for the duration of the emission. */ + void cardClicked(QMouseEvent *event, const ExactCard &card); + /** @brief The cursor moved onto a different card. */ + void cardHovered(const ExactCard &card); + +protected: + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void leaveEvent(QEvent *event) override; + bool viewportEvent(QEvent *event) override; + void wheelEvent(QWheelEvent *event) override; + +private: + void updateHoveredRow(const QPoint &position); + /// Pixels one wheel notch moves, scaled to the grid and the OS line count. + [[nodiscard]] int wheelStep() const; + + CardDatabaseVisualDisplayModel *model; + /// Fraction of a line left over from a wheel event smaller than one notch. + qreal wheelRemainder = 0.0; + int cardSizeFactor = 100; + int hoveredRow = -1; +}; + +#endif // CARD_DATABASE_VISUAL_VIEW_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp index cf815ace0..464d62e4a 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -49,7 +48,7 @@ VisualDatabaseDisplayColorFilterWidget::VisualDatabaseDisplayColorFilterWidget(Q &VisualDatabaseDisplayColorFilterWidget::updateFilterMode); connect(filterModel, &FilterTreeModel::layoutChanged, this, - [this]() { QTimer::singleShot(100, this, &VisualDatabaseDisplayColorFilterWidget::syncWithFilterModel); }); + [this]() { syncDebouncer.call([this] { syncWithFilterModel(); }); }); // Call retranslateUi to set the initial text retranslateUi(); diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.h index 38812b02a..184f1f96c 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_color_filter_widget.h @@ -12,6 +12,7 @@ #include #include #include +#include #include class FilterTreeModel; @@ -71,6 +72,7 @@ private: QHBoxLayout *layout; QComboBox *modeComboBox; FilterMode currentMode = FilterMode::Includes; // Default mode + QtUtils::Debouncer syncDebouncer{100}; }; #endif // VISUAL_DATABASE_DISPLAY_COLOR_FILTER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp index 3e9d9e525..5063cce14 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_filter_toolbar_widget.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include class DeckListModel; @@ -110,14 +111,10 @@ VisualDatabaseDisplayFilterToolbarWidget::VisualDatabaseDisplayFilterToolbarWidg void VisualDatabaseDisplayFilterToolbarWidget::initialize() { - auto sortLayout = new QHBoxLayout(sortGroupBox); - sortLayout->setContentsMargins(0, 0, 0, 0); - sortLayout->setSpacing(0); + auto sortLayout = QtUtils::makeLayout(sortGroupBox, 0); sortLayout->setAlignment(Qt::AlignLeft); - auto filterLayout = new QHBoxLayout(filterGroupBox); - filterLayout->setContentsMargins(0, 0, 0, 0); - filterLayout->setSpacing(2); + auto filterLayout = QtUtils::makeLayout(filterGroupBox, 2); filterLayout->setAlignment(Qt::AlignLeft); // create settings widgets diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp index a5725ff3f..e0734fbb8 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.cpp @@ -16,12 +16,12 @@ #include #include #include -#include #include #include #include #include #include +#include #include #include @@ -48,8 +48,7 @@ VisualDatabaseDisplayFormatLegalityFilterWidget::VisualDatabaseDisplayFormatLega layout->addWidget(flowWidget); // Create a container for the threshold control - auto *thresholdLayout = new QHBoxLayout(); - thresholdLayout->setContentsMargins(0, 0, 0, 0); + auto *thresholdLayout = QtUtils::makeLayout(); thresholdLabel = new QLabel(this); thresholdLayout->addWidget(thresholdLabel); @@ -72,9 +71,8 @@ VisualDatabaseDisplayFormatLegalityFilterWidget::VisualDatabaseDisplayFormatLega layout->addWidget(toggleButton); connect(toggleButton, &QPushButton::clicked, this, &VisualDatabaseDisplayFormatLegalityFilterWidget::updateFilterMode); - connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() { - QTimer::singleShot(100, this, &VisualDatabaseDisplayFormatLegalityFilterWidget::syncWithFilterModel); - }); + connect(filterModel, &FilterTreeModel::layoutChanged, this, + [this]() { syncDebouncer.call([this] { syncWithFilterModel(); }); }); createFormatButtons(); // Populate buttons initially updateFilterMode(); // Initialize toggle button text diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.h index e94593e67..6cc55062e 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_format_legality_filter_widget.h @@ -4,6 +4,7 @@ #include #include #include +#include #include class FilterTreeModel; @@ -42,6 +43,7 @@ private: QMap formatButtons; // Store toggle buttons bool exactMatchMode = true; // Toggle between "Exact Match" and "Includes" + QtUtils::Debouncer syncDebouncer{100}; }; #endif // COCKATRICE_VISUAL_DATABASE_DISPLAY_FORMAT_LEGALITY_FILTER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp index eff0fe10b..018841b30 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.cpp @@ -16,11 +16,11 @@ #include #include #include -#include #include #include #include #include +#include #include VisualDatabaseDisplayMainTypeFilterWidget::VisualDatabaseDisplayMainTypeFilterWidget(QWidget *parent, @@ -42,8 +42,7 @@ VisualDatabaseDisplayMainTypeFilterWidget::VisualDatabaseDisplayMainTypeFilterWi layout->addWidget(flowWidget); // Create a container for the threshold control - auto *thresholdLayout = new QHBoxLayout(); - thresholdLayout->setContentsMargins(0, 0, 0, 0); + auto *thresholdLayout = QtUtils::makeLayout(); thresholdLabel = new QLabel(this); thresholdLayout->addWidget(thresholdLabel); @@ -65,9 +64,8 @@ VisualDatabaseDisplayMainTypeFilterWidget::VisualDatabaseDisplayMainTypeFilterWi toggleButton = new QPushButton(this); layout->addWidget(toggleButton); connect(toggleButton, &QPushButton::clicked, this, &VisualDatabaseDisplayMainTypeFilterWidget::updateFilterMode); - connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() { - QTimer::singleShot(100, this, &VisualDatabaseDisplayMainTypeFilterWidget::syncWithFilterModel); - }); + connect(filterModel, &FilterTreeModel::layoutChanged, this, + [this]() { syncDebouncer.call([this] { syncWithFilterModel(); }); }); createMainTypeButtons(); // Populate buttons initially updateFilterMode(); // Initialize toggle button text diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h index b5a6f332c..1e2fa2b39 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_main_type_filter_widget.h @@ -10,6 +10,7 @@ #include #include #include +#include #include class FilterTreeModel; @@ -48,6 +49,7 @@ private: QMap typeButtons; // Store toggle buttons bool exactMatchMode = true; // Toggle between "Exact Match" and "Includes" + QtUtils::Debouncer syncDebouncer{100}; }; #endif // VISUAL_DATABASE_DISPLAY_MAIN_TYPE_FILTER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp index d3e497b21..e335cd4c0 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -61,7 +62,7 @@ VisualDatabaseDisplayNameFilterWidget::VisualDatabaseDisplayNameFilterWidget(QWi &VisualDatabaseDisplayNameFilterWidget::actLoadFromClipboard); connect(filterModel, &FilterTreeModel::layoutChanged, this, - [this]() { QTimer::singleShot(100, this, &VisualDatabaseDisplayNameFilterWidget::syncWithFilterModel); }); + [this]() { syncDebouncer.call([this] { syncWithFilterModel(); }); }); retranslateUi(); } @@ -139,20 +140,19 @@ void VisualDatabaseDisplayNameFilterWidget::updateFilterModel() emit filterModel->layoutAboutToBeChanged(); filterModel->clearFiltersOfType(CardFilter::Attr::AttrNameExact); - filterModel->blockSignals(true); - filterModel->filterTree()->blockSignals(true); + { + // Scope the blockers so both models are unblocked before the explicit + // change notifications below. + const QSignalBlocker modelBlocker(filterModel); + const QSignalBlocker treeBlocker(filterModel->filterTree()); - for (const auto &name : activeFilters.keys()) { - QString nameString = name; - filterModel->addFilter(new CardFilter(nameString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrNameExact)); + for (const auto &name : activeFilters.keys()) { + QString nameString = name; + filterModel->addFilter( + new CardFilter(nameString, CardFilter::Type::TypeOr, CardFilter::Attr::AttrNameExact)); + } } - filterModel->blockSignals(false); - filterModel->filterTree()->blockSignals(false); - - filterModel->blockSignals(false); - filterModel->filterTree()->blockSignals(false); - emit filterModel->filterTree()->changed(); emit filterModel->layoutChanged(); } diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.h index 1629cb93b..ba2684e05 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_name_filter_widget.h @@ -10,6 +10,7 @@ #include #include #include +#include #include class DeckListModel; @@ -46,6 +47,7 @@ private: QPushButton *loadFromClipboardButton; QMap activeFilters; // Store active name filter buttons + QtUtils::Debouncer syncDebouncer{100}; private slots: void actLoadFromDeck(); diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp index 3d76fb78a..fe583361a 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -30,13 +29,13 @@ #include #include #include +#include #include VisualDatabaseDisplayRecentSetFilterSettingsWidget::VisualDatabaseDisplayRecentSetFilterSettingsWidget(QWidget *parent) : QWidget(parent) { - layout = new QHBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); + layout = QtUtils::makeLayout(this); setLayout(layout); @@ -101,7 +100,7 @@ VisualDatabaseDisplaySetFilterWidget::VisualDatabaseDisplaySetFilterWidget(QWidg layout->addWidget(toggleButton); connect(toggleButton, &QPushButton::clicked, this, &VisualDatabaseDisplaySetFilterWidget::updateFilterMode); connect(filterModel, &FilterTreeModel::layoutChanged, this, - [this]() { QTimer::singleShot(100, this, &VisualDatabaseDisplaySetFilterWidget::syncWithFilterModel); }); + [this]() { syncDebouncer.call([this] { syncWithFilterModel(); }); }); createSetButtons(); // Populate buttons initially updateFilterMode(); @@ -304,5 +303,5 @@ void VisualDatabaseDisplaySetFilterWidget::updateFilterMode() // Reconnect the layoutChanged -> sync lambda connect(filterModel, &FilterTreeModel::layoutChanged, this, - [this]() { QTimer::singleShot(100, this, &VisualDatabaseDisplaySetFilterWidget::syncWithFilterModel); }); + [this]() { syncDebouncer.call([this] { syncWithFilterModel(); }); }); } diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h index 468e97e0d..4874df79b 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_set_filter_widget.h @@ -10,6 +10,7 @@ #include #include #include +#include #include class FilterTreeModel; @@ -64,6 +65,7 @@ private: QMap activeSets; // Track active set filters bool exactMatchMode = true; // Toggle between "Exact Match" and "Includes" + QtUtils::Debouncer syncDebouncer{100}; }; #endif // VISUAL_DATABASE_DISPLAY_SET_FILTER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp index 705bb8efa..68a1edf11 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.cpp @@ -17,11 +17,11 @@ #include #include #include -#include #include #include #include #include +#include #include VisualDatabaseDisplaySubTypeFilterWidget::VisualDatabaseDisplaySubTypeFilterWidget(QWidget *parent, @@ -47,8 +47,7 @@ VisualDatabaseDisplaySubTypeFilterWidget::VisualDatabaseDisplaySubTypeFilterWidg layout->addWidget(flowWidget); // Create a container for the threshold control - auto *thresholdLayout = new QHBoxLayout(); - thresholdLayout->setContentsMargins(0, 0, 0, 0); + auto *thresholdLayout = QtUtils::makeLayout(); thresholdLabel = new QLabel(this); thresholdLayout->addWidget(thresholdLabel); @@ -70,9 +69,8 @@ VisualDatabaseDisplaySubTypeFilterWidget::VisualDatabaseDisplaySubTypeFilterWidg toggleButton = new QPushButton(this); layout->addWidget(toggleButton); connect(toggleButton, &QPushButton::clicked, this, &VisualDatabaseDisplaySubTypeFilterWidget::updateFilterMode); - connect(filterModel, &FilterTreeModel::layoutChanged, this, [this]() { - QTimer::singleShot(100, this, &VisualDatabaseDisplaySubTypeFilterWidget::syncWithFilterModel); - }); + connect(filterModel, &FilterTreeModel::layoutChanged, this, + [this]() { syncDebouncer.call([this] { syncWithFilterModel(); }); }); createSubTypeButtons(); // Populate buttons initially updateFilterMode(); // Initialize the toggle button text diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h index 7929063fd..8bef870ed 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_sub_type_filter_widget.h @@ -10,6 +10,7 @@ #include #include #include +#include #include class FilterTreeModel; @@ -50,6 +51,7 @@ private: QMap typeButtons; // Store toggle buttons bool exactMatchMode = true; // Toggle between "Exact Match" and "Includes" + QtUtils::Debouncer syncDebouncer{100}; }; #endif // VISUAL_DATABASE_DISPLAY_SUB_TYPE_FILTER_WIDGET_H diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp index 599ac97b4..6037418ad 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.cpp @@ -5,40 +5,29 @@ #include "../../../filters/syntax_help.h" #include "../../key_signals.h" #include "../../pixel_map_generator.h" -#include "../cards/card_info_picture_widget.h" -#include "../cards/card_info_picture_with_text_overlay_widget.h" #include "../cards/card_size_widget.h" #include "../deck_editor/card_database_view.h" #include "../general/layout_containers/flow_widget.h" #include "../utility/custom_line_edit.h" #include "card_database_display_model.h" #include "card_database_model.h" +#include "card_database_visual_display_model.h" +#include "card_database_visual_view.h" #include "deck_list_model.h" #include "libcockatrice/card/card_localization.h" #include "libcockatrice/card/database/card_database_loader.h" #include "libcockatrice/card/database/card_database_querier.h" #include "libcockatrice/card/printing/exact_card.h" -#include "libcockatrice/card/printing/printing_info.h" #include "libcockatrice/filters/filter_card.h" #include "libcockatrice/filters/filter_tree.h" #include "visual_database_display_color_filter_widget.h" #include "visual_database_display_filter_toolbar_widget.h" -#include #include -#include #include #include #include -#include -#include -#include -#include -#include #include -#include -#include -#include #include #include #include @@ -48,13 +37,11 @@ #include #include #include -#include -#include #include #include #include #include -#include +#include class QMouseEvent; @@ -63,11 +50,6 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, DeckListModel *deckListModel) : QWidget(parent) { - debounceTimer = new QTimer(this); - debounceTimer->setSingleShot(true); // Ensure it only fires once after the timeout - - connect(debounceTimer, &QTimer::timeout, this, &VisualDatabaseDisplayWidget::onSearchModelChanged); - // Create display model databaseDisplayModel = new CardDatabaseDisplayModel(this); databaseDisplayModel->setObjectName("databaseDisplayModel"); @@ -85,24 +67,11 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, connect(&SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::cardSearchLanguageChanged, this, applyCardSearchLanguage); - cards = new QList; - connect(databaseDisplayModel, &CardDatabaseDisplayModel::modelDirty, this, - &VisualDatabaseDisplayWidget::modelDirty); - // Set up main layout and widgets setMinimumSize(0, 0); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - mainLayout = new QVBoxLayout(this); + mainLayout = QtUtils::makeLayout(this, 0, 1); setLayout(mainLayout); - mainLayout->setSpacing(1); - mainLayout->setContentsMargins(0, 0, 0, 0); - - flowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarPolicy::ScrollBarAsNeeded); - cardSizeWidget = new CardSizeWidget(this, flowWidget, - SettingsCache::instance().cardsDisplay().getVisualDatabaseDisplayCardSize()); - connect(cardSizeWidget, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance().cardsDisplay(), - &CardsDisplaySettings::setVisualDatabaseDisplayCardSize); - cardSizeWidget->enableCtrlScrollResize(flowWidget); searchContainer = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAlwaysOff); @@ -117,7 +86,7 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, setFocusProxy(searchEdit); setFocusPolicy(Qt::ClickFocus); - displayModeButton = new QPushButton(tr("Visual"), this); + displayModeButton = new QPushButton(this); displayModeButton->setCheckable(true); // Toggle button connect(displayModeButton, &QPushButton::toggled, this, &VisualDatabaseDisplayWidget::onDisplayModeChanged); @@ -160,6 +129,25 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, connect(databaseView, &CardDatabaseView::relatedCardClicked, this, &VisualDatabaseDisplayWidget::onRelatedCardClicked); + // The visual grid is a real view on a flattened model: one row per displayed + // card picture, painted on demand. + visualDisplayModel = new CardDatabaseVisualDisplayModel(databaseDisplayModel, CardDatabaseManager::query(), this); + + cardView = new CardDatabaseVisualView(visualDisplayModel, this); + cardView->setObjectName("visualCardView"); + connect(cardView, &CardDatabaseVisualView::cardClicked, this, + &VisualDatabaseDisplayWidget::cardClickedDatabaseDisplay); + connect(cardView, &CardDatabaseVisualView::cardHovered, this, + &VisualDatabaseDisplayWidget::cardHoveredDatabaseDisplay); + + const int cardSize = SettingsCache::instance().cardsDisplay().getVisualDatabaseDisplayCardSize(); + cardSizeWidget = new CardSizeWidget(this, nullptr, cardSize); + connect(cardSizeWidget, &CardSizeWidget::cardSizeSettingUpdated, &SettingsCache::instance().cardsDisplay(), + &CardsDisplaySettings::setVisualDatabaseDisplayCardSize); + connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, cardView, &CardDatabaseVisualView::setCardSizeFactor); + cardView->setCardSizeFactor(cardSize); + cardSizeWidget->enableCtrlScrollResize(cardView); + colorFilterWidget = new VisualDatabaseDisplayColorFilterWidget(this, filterModel); filterContainer = new VisualDatabaseDisplayFilterToolbarWidget(this, deckListModel); @@ -191,8 +179,6 @@ VisualDatabaseDisplayWidget::VisualDatabaseDisplayWidget(QWidget *parent, databaseLoadIndicator->setVisible(false); } - QScrollBar *scrollBar = flowWidget->scrollArea->verticalScrollBar(); - connect(scrollBar, &QScrollBar::valueChanged, [this](int /*value*/) { loadCurrentPage(); }); retranslateUi(); } @@ -211,7 +197,7 @@ void VisualDatabaseDisplayWidget::initialize() mainLayout->addWidget(databaseView); - mainLayout->addWidget(flowWidget); + mainLayout->addWidget(cardView); mainLayout->addWidget(cardSizeWidget); @@ -242,8 +228,6 @@ void VisualDatabaseDisplayWidget::initializeFilters() databaseDisplayModel->setFilterTree(filterModel->filterTree()); - QTimer::singleShot(5000, this, [this] { loadCurrentPage(); }); - retranslateUi(); }); } @@ -263,6 +247,10 @@ void VisualDatabaseDisplayWidget::retranslateUi() { databaseLoadIndicator->setText(tr("Loading database ...")); clearFilterWidget->setToolTip(tr("Clear all filters")); + searchEdit->setPlaceholderText(tr("Search by card name (or search expressions)")); + updateDisplayModeButton(); + colorFilterWidget->retranslateUi(); + filterContainer->retranslateUi(); } void VisualDatabaseDisplayWidget::highlightAllSearchEdit() @@ -270,72 +258,41 @@ void VisualDatabaseDisplayWidget::highlightAllSearchEdit() searchEdit->setSelection(0, searchEdit->text().length()); } -void VisualDatabaseDisplayWidget::resizeEvent(QResizeEvent *event) +void VisualDatabaseDisplayWidget::updateDisplayModeButton() { - QWidget::resizeEvent(event); - loadCurrentPage(); + displayModeButton->setText(displayModeButton->isChecked() ? tr("Table") : tr("Visual")); } void VisualDatabaseDisplayWidget::onDisplayModeChanged(bool checked) { + updateDisplayModeButton(); if (checked) { // Table mode - displayModeButton->setText(tr("Table")); - flowWidget->setVisible(false); + cardView->setVisible(false); cardSizeWidget->setVisible(false); databaseView->setItemDelegate(new QStyledItemDelegate(databaseView)); databaseView->setVisible(true); } else { // Visual mode - displayModeButton->setText(tr("Visual")); - flowWidget->setVisible(true); + cardView->setVisible(true); cardSizeWidget->setVisible(true); databaseView->setVisible(false); - populateCards(); } } -void VisualDatabaseDisplayWidget::onClick(QMouseEvent *event, const ExactCard &card) -{ - emit cardClickedDatabaseDisplay(event, card); -} - -void VisualDatabaseDisplayWidget::onHover(const ExactCard &hoveredCard) -{ - emit cardHoveredDatabaseDisplay(hoveredCard); -} - -void VisualDatabaseDisplayWidget::addCardToDisplay(const ExactCard &cardToAdd) -{ - cards->append(cardToAdd); - auto *display = new CardInfoPictureWithTextOverlayWidget(flowWidget, false); - display->setScaleFactor(cardSizeWidget->getSlider()->value()); - display->setCard(cardToAdd); - flowWidget->addWidget(display); - connect(display, &CardInfoPictureWithTextOverlayWidget::cardClicked, this, &VisualDatabaseDisplayWidget::onClick); - connect(display, &CardInfoPictureWithTextOverlayWidget::hoveredOnCard, this, &VisualDatabaseDisplayWidget::onHover); - connect(cardSizeWidget->getSlider(), &QSlider::valueChanged, display, &CardInfoPictureWidget::setScaleFactor); -} - bool VisualDatabaseDisplayWidget::isVisualDisplayMode() const { return !displayModeButton->isChecked(); } +QStringList VisualDatabaseDisplayWidget::collectSetFilterTerms() const +{ + return filterModel->getFilterTermsOfType(CardFilter::AttrSet); +} + void VisualDatabaseDisplayWidget::onSearchModelChanged() { - if (flowWidget->isVisible()) { - // Clear the current page and prepare for new data - flowWidget->clearLayout(); // Clear existing cards - cards->clear(); // Clear the card list - // Reset scrollbar position to the top after loading new cards - QScrollBar *scrollBar = flowWidget->scrollArea->verticalScrollBar(); - scrollBar->setValue(0); // Reset scrollbar to top - - currentPage = 0; - loadCurrentPage(); - qCDebug(VisualDatabaseDisplayLog) << "Search model changed"; - } + visualDisplayModel->setSetFilterTerms(collectSetFilterTerms()); } void VisualDatabaseDisplayWidget::onSelectedCardChanged(const QString &cardName) @@ -361,112 +318,3 @@ void VisualDatabaseDisplayWidget::onRelatedCardClicked(const QString &relatedCar ExactCard exactCard = CardDatabaseManager::query()->guessCard({relatedCard}); emit cardInfoRequested(exactCard); } - -bool VisualDatabaseDisplayWidget::nearEndOfPage() const -{ - if (!flowWidget->isVisible()) { - return false; - } - - QScrollBar *scrollBar = flowWidget->scrollArea->verticalScrollBar(); - if (scrollBar->value() + scrollBar->pageStep() * 2 < scrollBar->maximum()) { - return false; // there is at least two pages of space to scroll remaining - } - - return true; -} - -void VisualDatabaseDisplayWidget::loadCurrentPage() -{ - // Ensure only the initial page is loaded - if (currentPage == 0) { - if (!initialLoadScheduled) { - initialLoadScheduled = true; - qCDebug(VisualDatabaseDisplayLog) << "Loading the first page"; - // Defer the first page so the tab switch stays responsive. The card - // grid builds one event loop turn later. This also applies to - // search-driven reloads, which reset currentPage back to 0. - QTimer::singleShot(0, this, [this] { - initialLoadScheduled = false; - populateCards(); - }); - } - } else if (nearEndOfPage()) { - // If not the first page, just load the next page and append to the flow widget - loadNextPage(); - } -} - -void VisualDatabaseDisplayWidget::populateCards() -{ - cards->clear(); - loadNextPage(); -} - -void VisualDatabaseDisplayWidget::loadNextPage() -{ - // Calculate the start and end indices for the next page - int rowCount = databaseDisplayModel->rowCount(); - int start = currentPage * cardsPerPage; - int end = qMin(start + cardsPerPage, rowCount); - - // Load more cards if we are at the end of the current list and can fetch more - if (end >= rowCount && databaseDisplayModel->canFetchMore(QModelIndex())) { - databaseDisplayModel->fetchMore(QModelIndex()); - } - - // Load the next page of cards and add them to the flow widget - flowWidget->setUpdatesEnabled(false); - loadPage(start, end); - flowWidget->setUpdatesEnabled(true); -} - -void VisualDatabaseDisplayWidget::loadPage(int start, int end) -{ - QList setFilters = filterModel->getFiltersOfType(CardFilter::AttrSet); - for (int row = start; row < end; ++row) { - qCDebug(VisualDatabaseDisplayLog) << "Adding " << row; - QModelIndex index = databaseDisplayModel->index(row, CardDatabaseModel::NameColumn); - QVariant name = databaseDisplayModel->data(index, Qt::DisplayRole); - qCDebug(VisualDatabaseDisplayLog) << name.toString(); - - if (CardInfoPtr info = CardDatabaseManager::query()->getCardInfo(name.toString())) { - if (!setFilters.empty()) { - SetToPrintingsMap setMap = info->getSets(); - for (const CardFilter *setFilter : setFilters) { - if (setMap.contains(setFilter->term())) { - for (PrintingInfo printing : setMap[setFilter->term()]) { - addCardToDisplay(ExactCard(info, printing)); - } - } - } - } else { - addCardToDisplay(CardDatabaseManager::query()->getPreferredCard(info)); - } - } else { - qCDebug(VisualDatabaseDisplayLog) << "Card not found in database!"; - } - } - currentPage++; -} - -void VisualDatabaseDisplayWidget::modelDirty() const -{ - debounceTimer->start(debounceTime); -} - -void VisualDatabaseDisplayWidget::sortCardList(const QStringList &properties, - Qt::SortOrder order = Qt::AscendingOrder) const -{ - CardInfoComparator comparator(properties, order); - std::sort(cards->begin(), cards->end(), [comparator](const ExactCard &a, const ExactCard &b) { - return comparator(a.getCardPtr(), b.getCardPtr()); - }); -} - -void VisualDatabaseDisplayWidget::databaseDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight) -{ - (void)topLeft; - (void)bottomRight; - qCDebug(VisualDatabaseDisplayLog) << "Database Data changed"; -} diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h index 9771e55c1..6148670a3 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_display_widget.h @@ -9,8 +9,6 @@ #include "libcockatrice/card/card_info.h" -#include -#include #include #include #include @@ -21,26 +19,21 @@ class CardDatabaseDisplayModel; class CardDatabaseModel; class CardDatabaseView; +class CardDatabaseVisualDisplayModel; +class CardDatabaseVisualView; class CardSizeWidget; -class DeckList; class DeckListModel; class ExactCard; class FilterTreeModel; class FlowWidget; -class OverlapControlWidget; class QLabel; -class QModelIndex; +class QMouseEvent; class QPushButton; -class QScrollArea; -class QTimer; class QToolButton; class QVBoxLayout; class SearchLineEdit; class VisualDatabaseDisplayColorFilterWidget; class VisualDatabaseDisplayFilterToolbarWidget; -template class QList; - -inline Q_LOGGING_CATEGORY(VisualDatabaseDisplayLog, "visual_database_display"); class VisualDatabaseDisplayWidget : public QWidget { @@ -52,14 +45,6 @@ public: DeckListModel *deckListModel = nullptr); void retranslateUi(); - void adjustCardsPerPage(); - void populateCards(); - void loadPage(int start, int end); - void loadNextPage(); - void loadCurrentPage(); - void sortCardList(const QStringList &properties, Qt::SortOrder order) const; - void setDeckList(const DeckList &new_deck_list_model); - /** * @brief Sets the callback used to create a custom zone from the add-to-zone menu. * The callback returns the name of the created zone, or an empty string if creation was cancelled. @@ -87,6 +72,13 @@ public: bool isVisualDisplayMode() const; public slots: + /** + * @brief Pushes the active set filters to the visual grid. + * + * The grid's rows follow the display model's filter and sort signals on their + * own; this only forwards the set filters, which decide which printings the + * grid shows. + */ void onSearchModelChanged(); signals: @@ -101,11 +93,6 @@ signals: protected slots: void initialize(); - void onClick(QMouseEvent *event, const ExactCard &card); - void onHover(const ExactCard &hoveredCard); - void addCardToDisplay(const ExactCard &cardToAdd); - void databaseDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); - void modelDirty() const; void onDisplayModeChanged(bool checked); void onSelectedCardChanged(const QString &cardName); @@ -125,30 +112,21 @@ private: QToolButton *clearFilterWidget; VisualDatabaseDisplayFilterToolbarWidget *filterContainer; CardDatabaseDisplayModel *databaseDisplayModel; + CardDatabaseVisualDisplayModel *visualDisplayModel; CardDatabaseView *databaseView; + CardDatabaseVisualView *cardView; std::function newZoneCreator; - QList *cards; QVBoxLayout *mainLayout; - QScrollArea *scrollArea; - FlowWidget *flowWidget; - QWidget *overlapCategories; - QVBoxLayout *overlapCategoriesLayout; - OverlapControlWidget *overlapControlWidget; CardSizeWidget *cardSizeWidget; - QTimer *debounceTimer; - int debounceTime = 300; // in Ms - int currentPage = 0; // Current page index - int cardsPerPage = 100; // Number of cards per page bool filtersInitialized = false; - bool initialLoadScheduled = false; void initializeFilters(); void highlightAllSearchEdit(); - bool nearEndOfPage() const; + void updateDisplayModeButton(); + [[nodiscard]] QStringList collectSetFilterTerms() const; protected: - void resizeEvent(QResizeEvent *event) override; void showEvent(QShowEvent *event) override; }; diff --git a/cockatrice/src/interface/widgets/visual_database_display/visual_database_filter_display_widget.cpp b/cockatrice/src/interface/widgets/visual_database_display/visual_database_filter_display_widget.cpp index 0f6ac51ec..d13db1273 100644 --- a/cockatrice/src/interface/widgets/visual_database_display/visual_database_filter_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_database_display/visual_database_filter_display_widget.cpp @@ -13,6 +13,7 @@ #include #include #include +#include #include FilterDisplayWidget::FilterDisplayWidget(QWidget *parent, const QString &filename, FilterTreeModel *_filterModel) @@ -69,10 +70,8 @@ void FilterDisplayWidget::loadFilter() void FilterDisplayWidget::deleteFilter() { // Show confirmation dialog before deleting the filter - QMessageBox::StandardButton reply = QMessageBox::question( - this, tr("Confirm Delete"), tr("Are you sure you want to delete the filter '%1'?").arg(filterFilename), - QMessageBox::Yes | QMessageBox::No); - if (reply == QMessageBox::Yes) { + if (QtUtils::confirm(this, tr("Confirm Delete"), + tr("Are you sure you want to delete the filter '%1'?").arg(filterFilename))) { // If confirmed, delete the filter QString filePath = SettingsCache::instance().paths().getFiltersPath() + QDir::separator() + filterFilename; QFile file(filePath); diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp index 25ba41aa9..63ef07835 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -25,9 +26,7 @@ VisualDeckDisplayOptionsWidget::VisualDeckDisplayOptionsWidget(QWidget *parent) : QWidget(parent) { - groupAndSortLayout = new QHBoxLayout(this); - groupAndSortLayout->setContentsMargins(0, 0, 0, 0); - groupAndSortLayout->setSpacing(3); + groupAndSortLayout = QtUtils::makeLayout(this, 3); groupAndSortLayout->setAlignment(Qt::AlignLeft); this->setLayout(groupAndSortLayout); @@ -110,12 +109,33 @@ void VisualDeckDisplayOptionsWidget::retranslateUi() sortByLabel->setText(tr("Sort by:")); sortLabel->setText(tr("Click and drag to change the sort order within the groups")); sortCriteriaButton->setToolTip(tr("Configure how cards are sorted within their groups")); - displayTypeButton->setButtonText(tr("Toggle Layout: Overlap")); - displayTypeButton->setButtonIcon(themePixmap(QStringLiteral("icons/scales"))); + updateDisplayTypeButton(); displayTypeButton->setToolTip( tr("Change how cards are displayed within zones (i.e. overlapped or fully visible.)")); } +void VisualDeckDisplayOptionsWidget::updateDisplayTypeButton() +{ + switch (currentDisplayType) { + case DisplayType::Flat: + displayTypeButton->setButtonText(tr("Toggle Layout: Flat")); + displayTypeButton->setButtonIcon(themePixmap(QStringLiteral("icons/scroll"))); + break; + case DisplayType::Overlap: + displayTypeButton->setButtonText(tr("Toggle Layout: Overlap")); + displayTypeButton->setButtonIcon(themePixmap(QStringLiteral("icons/scales"))); + break; + } +} + +void VisualDeckDisplayOptionsWidget::changeEvent(QEvent *event) +{ + if (event->type() == QEvent::LanguageChange) { + retranslateUi(); + } + QWidget::changeEvent(event); +} + void VisualDeckDisplayOptionsWidget::onSortCriteriaChange() { QStringList selectedCriteria; @@ -132,17 +152,8 @@ void VisualDeckDisplayOptionsWidget::updateDisplayType() // Toggle the display type currentDisplayType = (currentDisplayType == DisplayType::Overlap) ? DisplayType::Flat : DisplayType::Overlap; - // Update UI and emit signal - switch (currentDisplayType) { - case DisplayType::Flat: - displayTypeButton->setButtonText(tr("Toggle Layout: Flat")); - displayTypeButton->setButtonIcon(themePixmap(QStringLiteral("icons/scroll"))); - break; - case DisplayType::Overlap: - displayTypeButton->setButtonText(tr("Toggle Layout: Overlap")); - displayTypeButton->setButtonIcon(themePixmap(QStringLiteral("icons/scales"))); - break; - } + // Update only the toggle button, not every string in the widget + updateDisplayTypeButton(); emit displayTypeChanged(currentDisplayType); } diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.h b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.h index 96600c587..c40b8ffeb 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_display_options_widget.h @@ -11,6 +11,7 @@ class CompactPushButton; class QComboBox; +class QEvent; class QHBoxLayout; class QLabel; class QListWidget; @@ -57,6 +58,7 @@ public slots: * @brief Updates all UI text for retranslation/localization. * * Called when the application language changes. + * Also refreshes the layout toggle button text for the current display type. */ void retranslateUi(); void updateCompactMode(bool mode); @@ -94,6 +96,10 @@ public: return activeSortCriteria; } +protected: + /** @brief Retranslates on QEvent::LanguageChange. */ + void changeEvent(QEvent *event) override; + private slots: /** * @brief Slot triggered whenever the sort list is reordered. @@ -110,6 +116,8 @@ private slots: void updateDisplayType(); private: + /** @brief Updates the layout toggle button's text and icon for the current display type. */ + void updateDisplayTypeButton(); /** @brief Layout for grouping and sorting UI elements. */ QHBoxLayout *groupAndSortLayout; diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp index 1554dceb8..91ad63433 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_sample_hand_widget.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -41,9 +42,7 @@ VisualDeckEditorSampleHandWidget::VisualDeckEditorSampleHandWidget(QWidget *pare layout->setSpacing(0); setLayout(layout); - auto upperLayout = new QVBoxLayout(); - upperLayout->setContentsMargins(0, 0, 0, 0); - upperLayout->setSpacing(0); + auto upperLayout = QtUtils::makeLayout(nullptr, 0); resetAndHandSizeContainerWidget = new QWidget(this); resetAndHandSizeLayout = new QHBoxLayout(resetAndHandSizeContainerWidget); diff --git a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp index 74b940586..506ffa2b4 100644 --- a/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_editor/visual_deck_editor_widget.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include VisualDeckEditorWidget::VisualDeckEditorWidget(QWidget *parent, @@ -188,14 +189,9 @@ void VisualDeckEditorWidget::initializeDisplayOptionsAndSearchWidget() void VisualDeckEditorWidget::initializeScrollAreaAndZoneContainer() { - scrollArea = new QScrollArea(this); - scrollArea->setWidgetResizable(true); + scrollArea = QtUtils::makeScrollArea(this); scrollArea->setMinimumSize(0, 0); - // Set scrollbar policies - scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - zoneContainer = new QWidget(scrollArea); zoneContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); zoneContainer->setObjectName("zoneContainer"); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp index dc619e9fa..cebe92495 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_deck_tags_display_widget.cpp @@ -1,9 +1,7 @@ #include "deck_preview_deck_tags_display_widget.h" #include "../../../../client/settings/cache_settings.h" -#include "../../../deck_loader/deck_file_format.h" #include "../../../deck_loader/deck_loader.h" -#include "../../../deck_loader/loaded_deck.h" #include "../../general/layout_containers/flow_widget.h" #include "deck_preview_tag_addition_widget.h" #include "deck_preview_tag_dialog.h" @@ -14,8 +12,12 @@ #include #include #include +#include #include +#include +#include #include +#include #include #include @@ -26,8 +28,7 @@ DeckPreviewDeckTagsDisplayWidget::DeckPreviewDeckTagsDisplayWidget(QWidget *_par setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); // Create layout - auto *layout = new QHBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); + auto *layout = QtUtils::makeLayout(this); setFixedHeight(100); @@ -117,8 +118,13 @@ void DeckPreviewDeckTagsDisplayWidget::openTagEditDlg() void DeckPreviewDeckTagsDisplayWidget::execTagDialog(const QStringList &knownTags) { + QPointer self(this); DeckPreviewTagDialog dialog(knownTags, currentTags); if (dialog.exec() == QDialog::Accepted) { + // Guard against the widget being destroyed during dialog.exec()'s nested event loop. + if (!self) { + return; + } QStringList updatedTags = dialog.getActiveTags(); if (updatedTags != currentTags) { setTags(updatedTags); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.cpp index 7796f893b..8850060ea 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_tag_item_widget.cpp @@ -2,13 +2,12 @@ #include #include +#include DeckPreviewTagItemWidget::DeckPreviewTagItemWidget(const QString &tagName, bool isChecked, QWidget *parent) : QWidget(parent), checkBox_(new QCheckBox(this)) { - auto *layout = new QHBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); - layout->setSpacing(5); + auto *layout = QtUtils::makeLayout(this, 5); checkBox_->setText(tagName); // Set the tag name as the checkbox label checkBox_->setChecked(isChecked); // Set the initial state of the checkbox diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp index 220c55788..f2002d4e4 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.cpp @@ -3,7 +3,6 @@ #include "../../../../client/settings/cache_settings.h" #include "../../../../interface/widgets/dialogs/dlg_convert_deck_to_cod_format.h" #include "../../../deck_loader/deck_loader.h" -#include "../../../deck_loader/loaded_deck.h" #include "../../cards/additional_info/color_identity_widget.h" #include "../../cards/deck_preview_card_picture_widget.h" #include "../visual_deck_storage_model.h" @@ -45,6 +44,7 @@ #include #include #include +#include #include #include #include diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h index cdeed87c7..f3fc982ac 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/deck_preview_widget.h @@ -16,6 +16,7 @@ #include #include #include +#include #include class QFrame; @@ -131,13 +132,9 @@ protected: // If popup is not open, forward event to parent scroll area if (!combo->view()->isVisible()) { // Try to find a scrollable parent and manually send the event - QWidget *parent = combo->parentWidget(); - while (parent) { - if (auto *scroll = qobject_cast(parent)) { - QApplication::sendEvent(scroll->viewport(), event); - return true; // Mark event as handled - } - parent = parent->parentWidget(); + if (auto *scroll = QtUtils::findParentOfType(combo)) { + QApplication::sendEvent(scroll->viewport(), event); + return true; // Mark event as handled } // If no scrollable parent found, just block return true; diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/public_deck_preview_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/public_deck_preview_widget.cpp index 361747423..11a9da66f 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/public_deck_preview_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/deck_preview/public_deck_preview_widget.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include PublicDeckPreviewWidget::PublicDeckPreviewWidget(QWidget *parent, const RemotePublicDecksModel::DeckEntry &entry) @@ -36,8 +37,7 @@ PublicDeckPreviewWidget::PublicDeckPreviewWidget(QWidget *parent, const RemotePu tagsFlowWidget = new FlowWidget(this, Qt::Horizontal, Qt::ScrollBarAlwaysOff, Qt::ScrollBarAsNeeded); tagsFlowWidget->setSpacing(3, 3); - auto *layout = new QVBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); + auto *layout = QtUtils::makeLayout(this); layout->addWidget(bannerCardDisplayWidget); layout->addWidget(uploadTimeLabel); layout->addWidget(colorIdentityWidget); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_model.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_model.cpp index e84631e52..3611a8c1f 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_model.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_model.cpp @@ -1,8 +1,6 @@ #include "visual_deck_storage_model.h" -#include "../../deck_loader/deck_file_format.h" #include "../../deck_loader/deck_loader.h" -#include "../../deck_loader/loaded_deck.h" #include "../cards/additional_info/deck_color_identity.h" #include "libcockatrice/deck_list/deck_list.h" @@ -21,6 +19,8 @@ #include #include #include +#include +#include #include #include #include diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_model.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_model.h index 57eaebacf..f9af6f318 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_model.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_model.h @@ -13,7 +13,6 @@ #ifndef VISUAL_DECK_STORAGE_MODEL_H #define VISUAL_DECK_STORAGE_MODEL_H -#include "../../deck_loader/loaded_deck.h" #include "libcockatrice/utility/card_ref.h" #include @@ -24,6 +23,9 @@ #include #include #include +#include +#include +#include #include #include diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp index 2c03666c1..f2669f55d 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_search_widget.cpp @@ -9,6 +9,7 @@ #include #include #include +#include /** * @brief Constructs a search bar for filtering decks in the Visual Deck Storage. @@ -20,8 +21,7 @@ */ VisualDeckStorageSearchWidget::VisualDeckStorageSearchWidget(QWidget *parent) : QWidget(parent) { - layout = new QHBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); + layout = QtUtils::makeLayout(this); setLayout(layout); searchBar = new QLineEdit(this); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_filter_proxy_model.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_filter_proxy_model.cpp index abe3327af..c93e496ec 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_filter_proxy_model.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_filter_proxy_model.cpp @@ -1,7 +1,6 @@ #include "visual_deck_storage_sort_filter_proxy_model.h" #include "../../../client/settings/cache_settings.h" -#include "../../deck_loader/loaded_deck.h" #include "../../filters/deck_filter_string.h" #include "visual_deck_storage_model.h" @@ -16,6 +15,7 @@ #include #include #include +#include #include VisualDeckStorageSortFilterProxyModel::VisualDeckStorageSortFilterProxyModel(QObject *parent) diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp index f798a72a9..e4285e360 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_sort_widget.cpp @@ -7,11 +7,11 @@ #include #include #include +#include VisualDeckStorageSortWidget::VisualDeckStorageSortWidget(VisualDeckStorageWidget *parent) : QWidget(parent) { - layout = new QHBoxLayout(this); - layout->setContentsMargins(0, 0, 0, 0); + layout = QtUtils::makeLayout(this); setLayout(layout); // Initialize the ComboBox diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp index 51e9c70e7..4e1d6293f 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(parent) @@ -98,10 +99,7 @@ VisualDeckStorageWidget::VisualDeckStorageWidget(QWidget *parent) : QWidget(pare &VisualDeckStorageWidget::updateSelectionAnimationEnabled); // deck area - scrollArea = new QScrollArea(this); - scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); - scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - scrollArea->setWidgetResizable(true); + scrollArea = QtUtils::makeScrollArea(this); // putting everything together layout->addWidget(searchAndSortContainer); @@ -186,7 +184,9 @@ void VisualDeckStorageWidget::resizeEvent(QResizeEvent *event) void VisualDeckStorageWidget::retranslateUi() { - databaseLoadIndicator->setText(tr("Loading database ...")); + if (databaseLoadIndicator) { + databaseLoadIndicator->setText(tr("Loading database ...")); + } refreshButton->setToolTip(tr("Refresh loaded files")); shareButton->setToolTip(tr("Select decks to share")); @@ -281,6 +281,14 @@ void VisualDeckStorageWidget::createRootFolderWidget() folderWidget = new VisualDeckStorageFolderDisplayWidget(this, this, QString(), false, quickSettingsWidget->getShowFolders()); + // QScrollArea::setWidget() deletes the widget it currently holds. If that is still the + // database load indicator, take it back first so the label stays alive for retranslateUi(). + if (databaseLoadIndicator && scrollArea->widget() == databaseLoadIndicator) { + scrollArea->takeWidget(); + databaseLoadIndicator->setParent(this); + databaseLoadIndicator->setVisible(false); + } + scrollArea->setWidget(folderWidget); // this automatically destroys the old folderWidget scrollArea->widget()->setMaximumWidth(scrollArea->viewport()->width()); scrollArea->widget()->adjustSize(); diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h index b512dd87c..19d16f107 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h @@ -6,6 +6,7 @@ #ifndef VISUAL_DECK_STORAGE_WIDGET_H #define VISUAL_DECK_STORAGE_WIDGET_H +#include #include #include #include @@ -87,7 +88,7 @@ private: QVBoxLayout *layout; QWidget *searchAndSortContainer; QHBoxLayout *searchAndSortLayout; - QLabel *databaseLoadIndicator; + QPointer databaseLoadIndicator; VisualDeckStorageSortWidget *sortWidget; VisualDeckStorageSearchWidget *searchWidget; DeckPreviewColorIdentityFilterWidget *deckPreviewColorIdentityFilterWidget; diff --git a/cockatrice/src/interface/window_main.cpp b/cockatrice/src/interface/window_main.cpp index 57d6e8b15..d546676bc 100644 --- a/cockatrice/src/interface/window_main.cpp +++ b/cockatrice/src/interface/window_main.cpp @@ -37,6 +37,7 @@ #include "../interface/widgets/tabs/tab_supervisor.h" #include "../main.h" #include "abstract_client.h" +#include "at_risk_data_warning.h" #include "card_database_update/card_database_update_status_bar.h" #include "card_database_update/card_update_progress.h" #include "intents/contexts/context_connect_to_server.h" @@ -58,13 +59,11 @@ #include #include #include -#include #include -#include #include #include -#include #include +#include #include #include #include @@ -82,10 +81,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -110,6 +111,7 @@ #include #include #include +#include #include #include #include @@ -252,7 +254,11 @@ void MainWindow::actWatchReplay() void MainWindow::localGameEnded() { - delete localServer; + // Defer deletion so the LocalClients (already scheduled with deleteLater() in TabSupervisor::stop) + // and any queued commands are processed before the server and its interfaces are destroyed. + if (localServer) { + localServer->deleteLater(); + } localServer = nullptr; aConnect->setEnabled(true); @@ -339,7 +345,7 @@ void MainWindow::actViewLog() void MainWindow::actOpenSettingsFolder() { QString dir = SettingsCache::instance().getSettingsPath(); - QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); + QtUtils::openPathInFileManager(dir); } void MainWindow::retranslateUi() @@ -355,6 +361,9 @@ void MainWindow::retranslateUi() aForgotPassword->setText(tr("&Restore password...")); aSettings->setText(tr("&Settings...")); aSettings->setIcon(themePixmap(QStringLiteral("icons/settings"))); + settingsButton->setToolTip(tr("Settings")); + settingsButton->setAccessibleName(tr("Settings")); + settingsButton->setIcon(themePixmap(QStringLiteral("icons/cogwheel"))); aExit->setText(tr("&Exit")); #if defined(__APPLE__) /* For OSX */ @@ -559,6 +568,19 @@ MainWindow::MainWindow(QWidget *parent) setCentralWidget(tabSupervisor); + // Unobtrusive settings entry in the tab bar corner: flat icon-only button that blends into the tab + // bar until hovered, mirroring the menu action for discoverability + settingsButton = new QToolButton(tabSupervisor); + settingsButton->setAutoRaise(true); + settingsButton->setToolButtonStyle(Qt::ToolButtonIconOnly); + settingsButton->setIconSize(QSize(16, 16)); + settingsButton->setCursor(Qt::PointingHandCursor); + settingsButton->setFocusPolicy(Qt::NoFocus); + settingsButton->setStyleSheet( + QStringLiteral("QToolButton { border: none; background: transparent; padding: 4px; }")); + connect(settingsButton, &QToolButton::clicked, this, &MainWindow::actSettings); + tabSupervisor->setCornerWidget(settingsButton, Qt::TopRightCorner); + retranslateUi(); if (!restoreGeometry(SettingsCache::instance().layouts().getMainWindowGeometry())) { @@ -606,38 +628,9 @@ MainWindow::MainWindow(QWidget *parent) QTimer::singleShot(0, this, &MainWindow::applyStartupDestination); } -void MainWindow::warnAboutDataPathsInProgramDir() -{ - auto &paths = SettingsCache::instance().paths(); - if (SettingsCache::instance().getIsPortableBuild() || paths.getAppDirWarningAcknowledged()) { - return; - } - - const QStringList offenders = paths.pathsInsideDir(QCoreApplication::applicationDirPath()); - if (offenders.isEmpty()) { - return; - } - - QMessageBox box(QMessageBox::Warning, tr("Data paths inside the program directory"), - tr("These paths are configured inside the Cockatrice program directory:\n\n%1\n\n" - "Cockatrice keeps its own data outside the program directory by default. Files " - "stored inside it can be deleted by an update or by uninstalling Cockatrice. " - "Change them in the Settings dialog - most are on the General page - to keep " - "them safe.") - .arg(offenders.join(QLatin1Char('\n'))), - QMessageBox::Ok, this); - box.setTextFormat(Qt::PlainText); - auto *dontShowAgain = new QCheckBox(tr("Don't show this again")); - box.setCheckBox(dontShowAgain); - box.exec(); - if (dontShowAgain->isChecked()) { - paths.setAppDirWarningAcknowledged(true); - } -} - void MainWindow::startupConfigCheck() { - warnAboutDataPathsInProgramDir(); + AtRiskDataWarning::warnAboutDataPathsInProgramDir(this); const bool isCleanInstall = SettingsCache::instance().network().getClientVersion() == CLIENT_INFO_NOT_SET; @@ -934,11 +927,28 @@ void MainWindow::actShow() void MainWindow::closeEvent(QCloseEvent *event) { - // workaround Qt bug where closeEvent gets called twice - if (bClosingDown) { - return; + switch (shutdownState) { + case ShutdownState::Accepted: + // A duplicate close for a shutdown that already went through (Qt delivers the event + // twice on some platforms). Accepting it again keeps the quit that is already under + // way; ignoring it would cancel it. + event->accept(); + return; + case ShutdownState::Confirming: + // A second close request arrived while one of our own confirmations is up. Another + // program asking us to close - an installer replacing our files, for one - looks + // exactly like this, and accepting it would be destructive: close() hides this + // window, hides the dialog with it, and leaves the process running inside the nested + // loop that dialog opened. Nothing can reach that loop afterwards, so the process + // stays alive with no window and every later close request is accepted into the same + // dead end. Refuse this one and leave the question in front of the user; the shutdown + // that is already running finishes as soon as they answer it. + event->ignore(); + return; + case ShutdownState::Idle: + break; } - bClosingDown = true; + shutdownState = ShutdownState::Confirming; if (cardUpdateProcess && cardUpdateProcess->state() != QProcess::NotRunning) { const QString runningFor = cardUpdateStatusBar ? cardUpdateStatusBar->statusText() : QString(); @@ -948,22 +958,22 @@ void MainWindow::closeEvent(QCloseEvent *event) : tr("A card database update is still running: %1.\nQuitting now will cancel it.\n" "Are you sure you want to quit?") .arg(runningFor); - if (QMessageBox::question(this, tr("Are you sure?"), message, QMessageBox::Yes | QMessageBox::No, - QMessageBox::No) == QMessageBox::No) { + if (!QtUtils::confirm(this, tr("Are you sure?"), message, QMessageBox::No)) { event->ignore(); - bClosingDown = false; + shutdownState = ShutdownState::Idle; return; } } if (!tabSupervisor->close()) { event->ignore(); - bClosingDown = false; + shutdownState = ShutdownState::Idle; return; } tip->close(); event->accept(); + shutdownState = ShutdownState::Accepted; SettingsCache::instance().layouts().setMainWindowGeometry(saveGeometry()); tabSupervisor->deleteLater(); } @@ -974,12 +984,36 @@ bool MainWindow::closeForUpdate() // closeEvent() is blocked on a user prompt). close() would hit the re-entrancy guard and return // true without any shutdown having happened, so report faithfully instead: the caller must keep // the process alive until the user resolves whatever is blocking the close. - if (bClosingDown) { + if (shutdownState != ShutdownState::Idle) { return false; } return close(); } +void MainWindow::requestInstallerClose() +{ + // The same shutdown the in-app updater asks for: close the window, flush settings and let + // the tabs answer their own prompts. Never a forced quit - ending a process that has not + // agreed is the installer's separate force-close answer, not this one. + if (closeForUpdate()) { + QTimer::singleShot(0, qApp, [] { QCoreApplication::exit(0); }); + return; + } + // A confirmation is already up, or a shutdown is in progress. Bring it forward: an + // installer prompt behind a hidden confirmation is the deadlock this request exists to + // break, and the user has to be able to answer it. showWindowIfHidden() also clears + // Qt::WindowMinimized, which raise() alone does not: a minimized window can keep the + // confirmation behind the installer. + showWindowIfHidden(); + if (QWidget *modal = QApplication::activeModalWidget()) { + modal->raise(); + modal->activateWindow(); + } + raise(); + activateWindow(); + QApplication::alert(this, 0); +} + void MainWindow::changeEvent(QEvent *event) { if (event->type() == QEvent::LanguageChange) { @@ -1029,7 +1063,8 @@ void MainWindow::attemptStartupAutoConnect() qCInfo(WindowMainStartupAutoconnectLog) << "Attempting auto-connect..."; DlgConnect dlg(this); connectionController->connectToServerDirect(dlg.getHost(), static_cast(dlg.getPort()), - dlg.getPlayerName(), dlg.getPassword()); + dlg.getPlayerName(), dlg.getPassword(), dlg.getStoredVerifier(), + dlg.getSaveName(), dlg.getSavePassword()); } } @@ -1501,13 +1536,13 @@ void MainWindow::refreshShortcuts() void MainWindow::actOpenCustomFolder() { QString dir = SettingsCache::instance().paths().getCustomPicsPath(); - QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); + QtUtils::openPathInFileManager(dir); } void MainWindow::actOpenCustomsetsFolder() { QString dir = SettingsCache::instance().getCustomCardDatabasePath(); - QDesktopServices::openUrl(QUrl::fromLocalFile(dir)); + QtUtils::openPathInFileManager(dir); } void MainWindow::actAddCustomSet() diff --git a/cockatrice/src/interface/window_main.h b/cockatrice/src/interface/window_main.h index 2b6ed5633..9e342e4b0 100644 --- a/cockatrice/src/interface/window_main.h +++ b/cockatrice/src/interface/window_main.h @@ -46,6 +46,7 @@ #include class QAction; +class QToolButton; class QWidget; struct LocalGameOptions; @@ -149,9 +150,6 @@ private: void runFirstRunWizard(bool firstRun = false); - /** @brief Warns once when a non-portable install keeps its data paths inside the program directory. */ - void warnAboutDataPathsInProgramDir(); - inline QString getCardUpdaterBinaryName() { return "oracle"; @@ -181,6 +179,7 @@ private: QList tabMenus; QMenu *cockatriceMenu, *dbMenu, *tabsMenu, *helpMenu, *trayIconMenu; QAction *aAbout, *aSettings, *aShow, *aExit; + QToolButton *settingsButton = nullptr; QAction *aConnect, *aDisconnect, *aRegister, *aForgotPassword, *aSinglePlayer, *aWatchReplay, *aFullScreen; QAction *aManageSets, *aEditTokens, *aOpenCustomFolder, *aOpenCustomsetsFolder, *aAddCustomSet, *aReloadCardDatabase; @@ -195,7 +194,17 @@ private: LagMonitor lagMonitor; ///< watches the main thread for event loop stalls LatencyStatusWidget *latencyStatus = nullptr; ///< status bar widget with live round-trip stats and history graph bool bHasActivated, askedForDbUpdater; - bool bClosingDown = false; ///< guards closeEvent() against re-entrancy + /** + * How far a shutdown that was asked for has got. A close request that arrives while one is + * already asking the user something must not be accepted - see closeEvent(). + */ + enum class ShutdownState + { + Idle, ///< no shutdown has been asked for + Confirming, ///< asking about a card database update, open games or an unsaved deck + Accepted, ///< the close was accepted and the window is hiding + }; + ShutdownState shutdownState = ShutdownState::Idle; bool skipStartupAutoConnect = false; bool startupAutoConnectAttempted = false; bool firstRunWizardActive = false; @@ -246,6 +255,17 @@ public: */ bool closeForUpdate(); + /** + * @brief Closes this instance because the installer asked it to over its local socket. + * + * The request reaches an instance hidden in the notification area or sitting on its own + * confirmation, which is exactly where a WM_CLOSE from the installer does not. The close + * is still the ordinary one: a clean client exits, one with unsaved work raises the same + * confirmation the close button does, and a confirmation already up is brought to the + * front instead of being left behind the installer. + */ + void requestInstallerClose(); + protected: void closeEvent(QCloseEvent *event) override; void changeEvent(QEvent *event) override; diff --git a/cockatrice/src/main.cpp b/cockatrice/src/main.cpp index 3f63efc3b..829a8ac42 100644 --- a/cockatrice/src/main.cpp +++ b/cockatrice/src/main.cpp @@ -20,16 +20,19 @@ #include "main.h" +#include "client/crash/crash_reporter.h" #include "client/network/update/card_spoiler/spoiler_background_updater.h" #include "client/settings/cache_settings.h" #include "client/sound_engine.h" #include "client/url_scheme_event_filter.h" #include "database/interface/settings_card_preference_provider.h" +#include "installer_close_server.h" #include "interface/intents/intent.h" #include "interface/intents/intent_open_local_deck.h" #include "interface/logger.h" #include "interface/pixel_map_generator.h" #include "interface/theme_manager.h" +#include "interface/widgets/dialogs/dlg_crash_report.h" #include "interface/window_main.h" #include "libcockatrice/card/database/card_database.h" #include "libcockatrice/rng/rng_abstract.h" @@ -103,60 +106,6 @@ static void CockatriceLogger(QtMsgType type, const QMessageLogContext &ctx, cons Logger::getInstance().log(type, ctx, logMessage); } -#ifdef Q_OS_WIN -// clang-format off -#include -#include -#include -#include -#include - -#pragma comment(lib, "DbgHelp.lib") // Link the DbgHelp library -// clang-format on - -LONG WINAPI CockatriceUnhandledExceptionFilter(EXCEPTION_POINTERS *exceptionPointers) -{ - std::filesystem::path path; - - // Find %LOCALAPPDATA% (or cheat at finding it) - wchar_t *localAppDataFolder; - if (SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_CREATE, NULL, &localAppDataFolder) != S_OK) { - path = std::filesystem::temp_directory_path().parent_path().parent_path(); - } else { - path = std::filesystem::path(localAppDataFolder); - } - - // Plan on writing crash files into %LOCALAPPDATA%/CrashDumps/Cockatrice/cockatrice.crash.*.dmp - path /= "CrashDumps"; - path /= "Cockatrice"; - if (!std::filesystem::exists(path)) { - std::filesystem::create_directories(path); - } - path /= "cockatrice.crash." + std::to_string(std::time(0)) + ".dmp"; - - // Create and write crash files -#ifdef UNICODE - HANDLE hDumpFile = - CreateFile(path.wstring().c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); -#else - HANDLE hDumpFile = - CreateFile(path.string().c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); -#endif - - MINIDUMP_EXCEPTION_INFORMATION mei; - mei.ExceptionPointers = exceptionPointers; - mei.ThreadId = GetCurrentThreadId(); - mei.ClientPointers = 1; - - MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(), hDumpFile, MiniDumpWithFullMemory, &mei, nullptr, - nullptr); - - CloseHandle(hDumpFile); - - return EXCEPTION_EXECUTE_HANDLER; -} -#endif - void installNewTranslator() { QString lang = SettingsCache::instance().personal().getLang(); @@ -197,11 +146,52 @@ static QString redactActivationUrl(const QString &url) return parsed.scheme() + "://" + parsed.host(); } +/** + * @brief Run the startup crash-report flow for an unclean exit (or --simulate-crash). + * + * With a configured DSN the SDK owns capture and upload (consent-gated); with an + * empty DSN the flow falls back to a local mock file. AlwaysSend uploads silently + * (the log was folded into the pending native envelope(s) at startup); Ask offers + * the dialog; NeverSend skips silently. + * + * @param attachedEnvelopes number of pending native envelopes that already carry the + * redacted log, so an AlwaysSend run only sends a standalone + * log event when no crash event existed. + */ +static void runCrashReportFlow(CrashReporter &crashReporter, QWidget *parent, int attachedEnvelopes) +{ + const PersonalSettings::CrashReportingMode crashMode = SettingsCache::instance().personal().getCrashReportingMode(); + const bool crashReady = crashReporter.isReady(); + if (crashMode == PersonalSettings::CrashReportingMode::AlwaysSend) { + if (crashReady) { + // Silent path: the redacted log was folded into the pending native crash + // envelope(s) at startup and consent already released them. Only when no + // pending crash event existed (e.g. --simulate-crash) does a standalone + // log event still need to go out here. + if (attachedEnvelopes == 0) { + const QString crashPreview = DlgCrashReport::buildRedactedPreview(); + if (!crashReporter.sendReport(crashPreview)) { + const QString savedPath = DlgCrashReport::writeMockReport(crashPreview); + qCWarning(MainLog) << "Failed to send crash report, saved locally to" << savedPath; + } + } + } else { + DlgCrashReport::writeMockReport(DlgCrashReport::buildRedactedPreview()); + } + } else if (crashMode == PersonalSettings::CrashReportingMode::Ask) { + // Real mode: Send gives consent + captures the event, Don't send revokes + // consent; the dialog owns both via the reporter. + DlgCrashReport crashDialog(parent, crashReady, crashReady ? &crashReporter : nullptr); + crashDialog.exec(); + } + // NeverSend: skip silently (no dialog, nothing written or sent). +} + int main(int argc, char *argv[]) { -#ifdef Q_OS_WIN - SetUnhandledExceptionFilter(CockatriceUnhandledExceptionFilter); -#endif + // No custom unhandled-exception filter: sentry owns native crash capture on all + // platforms. (The legacy MiniDumpWithFullMemory writer was removed as a privacy + // liability; unclean exits are detected via CrashReporter::beginSession below.) // Logging setup #ifdef Q_OS_APPLE @@ -259,6 +249,82 @@ int main(int argc, char *argv[]) qApp->setAttribute(Qt::AA_DontShowIconsInMenus, true); #endif + // Persistent session log: keeps this run's logs on disk so the next-startup crash dialog can + // attach the crashed session's tail (the in-memory buffer is lost on crash). Opened in Append + // mode with a per-launch session marker; read back via Logger::getPreviousSessionTail(). + // Declared before crash-reporter init: the startup attach below needs the log tail while the + // file still holds the crashed session's lines. + const QString persistentLogPath = + SettingsCache::instance().getSettingsPath() + QStringLiteral("logs/cockatrice.log"); + Logger::getInstance().setPersistentLogPath(persistentLogPath); + + // Crash reporting: init the SDK early so a crash anywhere below is captured into the + // local database. Uploads stay consent-gated (require_user_consent, armed in CrashReporter::init): + // AlwaysSend gives consent here so cached envelopes flush at startup; any other mode starts + // unconsented and the dialog below drives give/revoke. An empty DSN skips the SDK entirely and + // the mock fallback further below behaves exactly as before. + CrashReporter crashReporter; + const QString crashDatabasePath = SettingsCache::instance().getSettingsPath() + QStringLiteral("sentry"); + // Envelopes that received the redacted log at startup (AlwaysSend path); 0 means the log was + // NOT folded into a pending crash event and a standalone log event is still needed below. + int crashLogAttachedEnvelopes = 0; + { + QString crashDsn = QString::fromUtf8(qgetenv("COCKATRICE_SENTRY_DSN")); + const bool dsnFromEnvironment = !crashDsn.isEmpty(); + if (!dsnFromEnvironment) { + crashDsn = QString::fromUtf8(COCKATRICE_SENTRY_DSN); + } + if (crashDsn.isEmpty()) { + // Nothing is armed without a DSN, so no crash is ever captured and nothing is ever sent. + // It is the one disabled configuration a tester cannot see from the UI, so say it here. + qCWarning(CrashReportLog) << "crash reporting disabled: no DSN configured (COCKATRICE_SENTRY_DSN is " + "not set and this build has no DSN baked in)"; + } else if (!CrashReporter::isDsnWellFormed(crashDsn)) { + // Same rule the installer applies (cmake/CrashReporting.cmake): a malformed DSN disables + // reporting rather than silently falling back to the project baked into the build, which + // would upload where the tester never chose. The SDK would otherwise refuse to build a + // request at all and report nothing about it. + qCWarning(CrashReportLog) << "crash reporting disabled:" + << (dsnFromEnvironment ? "COCKATRICE_SENTRY_DSN is set but malformed" + : "the DSN baked into this build is malformed") + << "(expected ://@/)"; + crashDsn.clear(); + } else if (dsnFromEnvironment) { + // Destination and mode are logged by CrashReporter::init() once it resolves them. + qCInfo(CrashReportLog) << "using COCKATRICE_SENTRY_DSN from the environment"; + } + const PersonalSettings::CrashReportingMode crashMode = + SettingsCache::instance().personal().getCrashReportingMode(); + // AlwaysSend folds the crashed session's redacted log into the pending crash + // envelope(s) BEFORE init: sentry_init replays the crashed run's *.run + // envelopes immediately when consent is already persisted, so attaching any + // later would be too late. Consent given below then releases them, and dump, + // stack and log upload as a single Sentry event. Ask mode attaches at the + // dialog instead — its cache-held envelopes stay untouched until giveConsent(). + if (crashMode == PersonalSettings::CrashReportingMode::AlwaysSend && + CrashReporter::isReportingEnabled(crashDsn, crashMode)) { + crashLogAttachedEnvelopes = CrashReporter::attachToPendingCrashEnvelopes( + crashDatabasePath, DlgCrashReport::buildRedactedPreview(), QString()); + } + crashReporter.init(crashDsn, QString::fromUtf8(COCKATRICE_SENTRY_RELEASE), crashDatabasePath, crashMode); + if (crashReporter.isReady() && crashMode == PersonalSettings::CrashReportingMode::AlwaysSend) { + // Silent opt-in: cached native crash envelopes flush automatically once consent is given. + crashReporter.giveConsent(); + } + // Mid-session mode changes must reach the SDK: without this, switching from AlwaysSend to + // NeverSend (or Ask) leaves consent given until restart, so a later crash in this session + // would still upload after the user opted out. crashReporter outlives main(), so capturing + // it here is safe. + QObject::connect(&SettingsCache::instance().personal(), &PersonalSettings::crashReportingModeChanged, + [&crashReporter](PersonalSettings::CrashReportingMode mode) { + if (mode == PersonalSettings::CrashReportingMode::AlwaysSend) { + crashReporter.giveConsent(); + } else { + crashReporter.revokeConsent(); + } + }); + } + // Translations #ifdef Q_OS_MAC translationPath = qApp->applicationDirPath() + "/../Resources/translations"; @@ -276,7 +342,10 @@ int main(int argc, char *argv[]) parser.addOptions( {{{"c", "connect"}, QCoreApplication::translate("main", "Connect on startup"), "user:pass@host:port"}, - {{"d", "debug-output"}, QCoreApplication::translate("main", "Debug to file")}}); + {{"d", "debug-output"}, QCoreApplication::translate("main", "Debug to file")}, + {"simulate-crash", + QCoreApplication::translate("main", + "Show the crash-report dialog (saves a mock file when no DSN is configured)")}}); parser.process(app); @@ -320,6 +389,16 @@ int main(int argc, char *argv[]) } } + // Unclean-exit detection (cross-platform QLockFile session tracking, replacing the + // legacy Windows-only .dmp scan): a leftover session lock means the previous run died + // without reaching endSession(). Our own lock is held until the event loop finishes. + // Deliberately placed after parser.process() (--help/--version exit inside it and must + // not leak a lock) and after the instance hand-off (a handed-off secondary exits + // immediately and must never take a lock). Note this counts any death without + // endSession() as a crash, including SIGTERM at logout, Task Manager kills and + // session-end shutdowns; in AlwaysSend those send a report too. + const bool uncleanExit = crashReporter.beginSession(crashDatabasePath); + rng = new RNG_SFMT(CryptoUtil::randomUInt64()); themeManager = new ThemeManager; soundEngine = new SoundEngine; @@ -353,6 +432,15 @@ int main(int argc, char *argv[]) MainWindow ui; + // The installer asks a running instance to close over this socket, which reaches a + // hidden window and one sitting on its own confirmation. Failure is not fatal: the + // installer falls back to WM_CLOSE for clients that have no socket. + InstallerCloseServer installerClose; + if (installerClose.listen()) { + QObject::connect(&installerClose, &InstallerCloseServer::closeRequested, &ui, + &MainWindow::requestInstallerClose); + } + // A URL launch must own the connection: the intent chain triggered by the // URL connects to the server named in the URL, so the window's own startup // auto-connect must not race against it (two connectToServer calls tear @@ -413,6 +501,13 @@ int main(int argc, char *argv[]) ui.show(); qCInfo(MainLog) << "ui.show() finished"; + // Crash-report flow: the trigger is --simulate-crash or an unclean previous exit + // (leftover session lock; the old %LOCALAPPDATA%/CrashDumps .dmp scan is gone with + // the exception filter). See runCrashReportFlow() for the per-mode handling. + if (parser.isSet("simulate-crash") || uncleanExit) { + runCrashReportFlow(crashReporter, &ui, crashLogAttachedEnvelopes); + } + // force shortcuts to be shown/hidden in right-click menus, regardless of system defaults qApp->setAttribute(Qt::AA_DontShowShortcutsInContextMenus, !SettingsCache::instance().userInterface().getShowShortcuts()); @@ -444,5 +539,10 @@ int main(int argc, char *argv[]) UserLevelPixmapGenerator::clear(); ManaSymbolPixmapGenerator::clear(); + // Released last: crashes anywhere in the teardown above (a common place for Qt apps to + // crash) are still captured by the SDK and detected as unclean exits on the next launch. + crashReporter.shutdown(); + crashReporter.endSession(); + return ret; } diff --git a/cockatrice/translations/cockatrice_cs.ts b/cockatrice/translations/cockatrice_cs.ts index aa15c6fcc..5b130eb06 100644 --- a/cockatrice/translations/cockatrice_cs.ts +++ b/cockatrice/translations/cockatrice_cs.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Nastavit žeton... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter - + New value for counter '%1': Nová hodnota pro žeton '%1': @@ -36,64 +36,113 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. - + There are no cards in your deck to be exported + + AccountSetupPage + + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + AddAnalyticsPanelDialog @@ -105,12 +154,12 @@ Please check that the directory is writable and try again. AdminNotesDialog - + Update Notes - + Admin Notes for %1 @@ -136,218 +185,304 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error Chyba - + Could not create themes directory at '%1'. - + Theme settings Nastavení vzhledu - + Current theme: Aktuální vzhled - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering Vykreslování karet - + Display card names on cards having a picture Zobrazit jména karet na kartách s obrázky - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Light - + Dark - + + System - + + Show all playmats + + + + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + Active theme palette: - + + Active theme style: + + + + + Qt widget style saved to this theme ("System" lets the application decide) + + + + Edit theme palette - + Home tab settings - + Display card name of background in bottom right - + + Dim the home tab background + + + + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + Styling settings - + Style user list - + Card printings - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Zvětšovat karty při najetí myši - + Use rounded card corners - + Card layout - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Rozvržení ruky - + Display hand horizontally (wastes space) Zobrazit ruku horizontálně (plýtvá místem) - + Enable left justification Zapnout - + Table grid layout Rozložení herní mřížky - + Invert vertical coordinate Převrátit vertikální souřadnice - + Minimum player count for multi-column layout: Minimální počet hráčů pro víceřádkové rozvržení: - + Maximum font size for information displayed on cards: Maximální velikost písma pro informace na kartách @@ -355,12 +490,12 @@ Please check that the directory is writable and try again. ArchidektApiResponseDeckDisplayWidget - + Back to results - + Open Deck in Deck Editor @@ -386,112 +521,112 @@ Please check that the directory is writable and try again. BanDialog - + ban &user name Blokovat & Uživatelské jméno - + ban &IP address Blokovat & IP Adresu - + ban client I&D Banovat I&D klienta - + Ban type Typ banu - + &permanent ban &trvalý ban - + &temporary ban &dočasný ban - + &Days: &Dní: - + &Hours: &Hodin: - + &Minutes: &Minut: - + Duration of the ban Trvání banu - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Uveďte prosím důvod pro ban. Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. - + Please enter the reason for the ban that will be visible to the banned person. Uveďte prosám důvod pro ban, který uvidí banovaná osoba. - + Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Zrušit - + Ban user from server Zabanovat uživatele ze serveru - - - - + + + + Error Chyba - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. - + You must have a value in the name ban when selecting the name ban checkbox. - + You must have a value in the ip ban when selecting the ip ban checkbox. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. @@ -499,22 +634,22 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. BetaReleaseChannel - + Beta - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the file update server. @@ -522,7 +657,20 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CardArtPreviewWidget - + + Banner preview + + + + + + + + Zoom %1× + + + + No card selected @@ -530,22 +678,22 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CardArtRulesModel - + Card - + ProviderId - + Mode - + Reason @@ -553,65 +701,209 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CardDatabaseModel - + Name Jméno - + Sets Edice - + Mana cost Sesílací cena - + Card type Typ karty - + P/T S/O - + Color(s) Barva(y) + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + CardDatabaseView - + Add to Deck - + Add to Sideboard - + Select Printing - + + Add to Zone + + + + + Create &new zone... + + + + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards @@ -721,22 +1013,22 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CardInfoFrameWidget - + Image - + Description - + Both - + View transformation @@ -744,22 +1036,22 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -767,27 +1059,27 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CardInfoTextWidget - + Name: - + Set: - + Collector Number: - + Related cards: - + Unknown card: @@ -795,129 +1087,129 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Reduce life by power - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -938,7 +1230,7 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. CardSizeWidget - + Card Size @@ -1078,6 +1370,24 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser @@ -1107,280 +1417,382 @@ Toto je uloženo pouze pro moderátory a banovaná osoba to neuvidí. + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + ConnectionController - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - - - - - - - - - - - + + + + + + + + + + - - + + + Error - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + Server timeout - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Information - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -1388,113 +1800,113 @@ To update your client, go to Help -> Check for Updates. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - + Requesting forgotten password to %1 as %2... - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. @@ -1544,7 +1956,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1560,17 +1972,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add card to &maindeck - + Add card to &sideboard @@ -1578,97 +1990,158 @@ To update your client, go to Help -> Check for Updates. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + Format: - + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1676,17 +2149,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1694,114 +2167,119 @@ To update your client, go to Help -> Check for Updates. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + + Share deck... + + + + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1809,7 +2287,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1817,110 +2295,152 @@ To update your client, go to Help -> Check for Updates. DeckEditorSettingsPage - - + + Update Spoilers Aktualizovat spoilery - + Success Úspěch - + Download URLs have been reset. - + Add URL - - + + URL: URL: - - + + Edit URL - + Add New URL - + Remove URL - + Updating... Aktualizuji... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path Vyberte cestu - + URL Download Priority - + Spoilers Spoilery - + Download Spoilers Automatically Stahovat spoilery automaticky - + Spoiler Location: - + Last Change Poslední změna - + Spoilers download automatically on launch Automaticky stahovat spoilery při spuštění - + Press the button to manually update without relaunching Manuálně aktualizovat bez restartování - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Reset Download URLs @@ -1928,32 +2448,32 @@ To update your client, go to Help -> Check for Updates. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1961,27 +2481,27 @@ To update your client, go to Help -> Check for Updates. DeckListModel - + Count - + Set - + Number Počet - + Provider ID - + Card Karta @@ -2002,17 +2522,22 @@ To update your client, go to Help -> Check for Updates. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -2020,7 +2545,7 @@ To update your client, go to Help -> Check for Updates. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -2028,62 +2553,62 @@ To update your client, go to Help -> Check for Updates. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2091,94 +2616,109 @@ To update your client, go to Help -> Check for Updates. DeckPreviewWidget - + Banner Card - + Open in deck editor - + + Share deck... + + + + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Rename deck + + + + + Rename file + + + + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -2186,57 +2726,102 @@ To update your client, go to Help -> Check for Updates. DeckStateManager - + Rename deck to "%1" from "%2" - + Updated comments (was %1 chars, now %2 chars) - + Set banner card to %1 (%2) - + + Set playmat to %1 + + + + Tags changed - + Set format to %1 - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + %1 1 × "%2" (%3) - + Added - + Removed @@ -2258,94 +2843,122 @@ To update your client, go to Help -> Check for Updates. DeckViewContainer - + Load deck... Načíst balíček... - + Load remote deck... Načíst balíček ze serveru... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start Připraven začít - + Force start - + Sideboard unlocked - + Sideboard locked - - + + Error Chyba - + The selected file could not be loaded. Vybraný soubor se nepodařilo načíst. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? - + Remember and automatically apply choice in the future @@ -2358,143 +2971,143 @@ This will kick all non-ready players from the game. Stahuji... - + Known Hosts Známí hostitelé - + Delete the currently selected saved server - + Refresh the server list with known public servers Znovu načíst seznam serverů se známými veřejnými servery - + New Host Nový hostitel - + Name: Jméno: - + &Host: &Hostitel: - + &Port: &Port: - + Player &name: Jméno &hráče: - + P&assword: H&eslo: - + &Save password &Uložit heslo - + A&uto connect A&utomatické připojení - + Automatically connect to the most recent login when Cockatrice opens Automaticky se připojit k poslednímu přihlášení při otevření Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! Jestli máte potíže se připájením nebo registrací, kontaktujte pracovníky servery pro pomoc! Pouze Anglicky - - + + Webpage Webová stránka - + Reset Password - + Forgot password? - + &Connect &Připojit - + Server Server - + Login Přihlášení - + Server Contact - + Connect to Server Připojit k serveru - + Server URL - + Communication Port Komunikační port - + Unique Server Name - + Connection Warning - + You need to name your new connection profile. - + Connect Warning Varování přihlášení - + The player name can't be empty. Jméno hráče nemůže být prázdné @@ -2502,122 +3115,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings Pamatovat si nastavení - + &Description: &Popis: - + P&layers: H&ráči: - + General Obecné - + Game type Typ hry - + &Password: &Heslo: - + Only &buddies can join Jen pro &přátele - + Only &registered users can join Jen pro &registrované - + Joining restrictions Omezení připojení - + &Spectators can watch &Diváci se mohou dívat - + Spectators &need a password to watch Diváci &potřebují ke sledování heslo - + Spectators can &chat Diváci mohou &chatovat - + Spectators can see &hands Diváci mohou vidět do &rukou - + Create game as spectator - + Spectators Diváci - + Starting life total: - + Open decklists in lobby - + Create game as judge - + Game setup options - + &Clear &Smazat - + Create game Vytvořit hru - + Game information Informace o hře - + Error Chyba - + Server error. Chyba serveru. @@ -2625,97 +3238,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Jméno: - + Token Token - + C&olor: B&arva: - + white bílá - + blue modrá - + black černá - + red červená - + green zelená - + multicolor vícebarevný - + colorless bezbarvý - + &P/T: &S/O: - + &Annotation: &Poznámka: - + &Destroy token when it leaves the table &Token se při odchodu z bojiště zničí - + Create face-down (Only hides name) - + Token data Data tokenů - + Show &all tokens Ukázat &všechny tokeny - + Show tokens from this &deck Ukázat tokeny z tohto &balíčku - + Choose token from list Vybrat token ze seznamu. - + Create token Vytvořit token @@ -2723,53 +3336,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + ✖ - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2778,7 +3391,7 @@ This will kick all non-ready players from the game. DlgEditAvatar - + No image chosen. Nebyl vybrán obrázek. @@ -2790,27 +3403,32 @@ To remove your current avatar, confirm without choosing a new image. Pro odstranění současného avatara potvrďte bez vybrání nového obrázku. - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... Prohlížet... - + Change avatar Změnit avatara - + Open Image Otevřít obrázek - + Image Files (*.png *.jpg *.bmp) Soubory obrázků (*.png *.jpg *.bmp) - + Invalid image chosen. Vybrán neplatný obrázek @@ -2836,38 +3454,38 @@ Pro odstranění současného avatara potvrďte bez vybrání nového obrázku.< DlgEditPassword - + Old password: Staré heslo: - + New password: Nové heslo: - + Confirm new password: Potvrdit nové heslo: - + Change password Změnit heslo - - + + Error Chyba - + Your password is too short. - + The new passwords don't match. Nová hesla nejsou stejná. @@ -2875,93 +3493,93 @@ Pro odstranění současného avatara potvrďte bez vybrání nového obrázku.< DlgEditTokens - + &Name: &Jméno - + C&olor: B&arva - + white bílá - + blue modrá - + black černá - + red červená - + green zelená - + multicolor vícebarevná - + colorless bezbarvá - + &P/T: &S/O - + &Annotation: &Poznámka: - + Token data Data k tokenu - - + + Add token Přidat token - + Remove token Odstranit token - + Edit custom tokens Upravit vlastní tokeny - + Please enter the name of the token: Zadejte prosím jméno pro token: - + Error Chyba - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Vybrané jméno se nemůže shodovat s již existující kartou nebo tokenem. @@ -3040,7 +3658,8 @@ Pro správné zobrazení si aktivujte si edici 'Token' v "Nastavi - Hide games that have started + Hide started games + Hide games that have started @@ -3076,8 +3695,9 @@ Pro správné zobrazení si aktivujte si edici 'Token' v "Nastavi - &Creator name: - Jméno &tvůrce: + &Host name: + &Creator name: + @@ -3143,52 +3763,52 @@ Pro správné zobrazení si aktivujte si edici 'Token' v "Nastavi DlgForgotPasswordChallenge - + Reset Password Challenge Warning - + A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. - + &Host: &Hostitel: - + &Port: &Port: - + Player &name: Jméno &hráče: - + Email: Email: - + Reset Password Challenge - + Reset Password Challenge Error - + The email address can't be empty. Emailová adresa musí být vyplněna. @@ -3196,37 +3816,37 @@ Pro správné zobrazení si aktivujte si edici 'Token' v "Nastavi DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. - + &Host: &Hostitel: - + &Port: &Port: - + Player &name: &Jméno hráče: - + Reset Password Request - + Reset Password Error - + The player name can't be empty. Jméno hráče musí být vyplněno. @@ -3234,94 +3854,127 @@ Pro správné zobrazení si aktivujte si edici 'Token' v "Nastavi DlgForgotPasswordReset - + Reset Password Warning - + A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. - + &Host: &Hostitel: - + &Port: &Port: - + Player &name: &Jméno hráče: - + Token: Token: - - + + New Password: Nové Heslo: - + Reset Password - + The player name can't be empty. Vyplňte jméno hráče. - - - - + + + + Reset Password Error - + The token can't be empty. Vyplňte token. - + The new password can't be empty. Vyplňte nové heslo. - + Error - + Your password is too short. - + The passwords do not match. Hesla se neshodují. + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck @@ -3405,165 +4058,566 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLocalGameOptions - + Players: - + General - + Starting life total: - + Game setup options - + Remember settings - + Local game options + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: &Hostitel: - + &Port: Port: - + Player &name: Jméno &hráče: - + P&assword: H&eslo: - + Password (again): Heslo (znovu): - + Email: Email: - + Email (again): Email (znovu): - + Country: Země: - + Undefined Neznámý - + Real name: Skutečné jméno: - + Register to server Registrovat se na server - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning - + Your password is too short. - + Your passwords do not match, please try again. Vaše hesla se neshodují. - + Your email addresses do not match, please try again. Vaše emailové adresy se neshodují. - + The player name can't be empty. Vyplňte jméno hráče. + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice @@ -3629,46 +4683,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Neznámý error při nahrávání databáze karet. - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - Vaše databáze karet je neplatná. - -Cockatrice s neplatnou databází nemusí fungovat správně. - -Je možné že bude nutné znovu spustit Oracle pro obnovení databáze karet. - -Chtěl/a by jste změnit nastavení lokace databáze? - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - Máte starou verzi databázy karet. - -Může to spůsobit problémy při načítaní informací of kartách nebo jejich obrázků. - -Je možné to spravit spuštěním Oracle pro aktualizaci vaší databáze karet. - -Chcete změnit nastavení lokace databáze? - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3677,7 +4697,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3686,7 +4706,7 @@ Would you like to change your database location setting? Chcete změnit nastavení lokace databáze? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3695,7 +4715,7 @@ Would you like to change your database location setting? Chcete změnit nastavení lokace databáze? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3704,104 +4724,235 @@ Would you like to change your database location setting? - - - + + + Error Chyba - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Cesta k adresáři s balíčky je neplatná. Chcete se vrátit a nastavit správnou? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Cesta k adresáři s obrázky je neplatná. Chcete se vrátit a nastavit správnou? - + Settings Nastavení - + + Search settings... + + + + + OK + + + + General Obecné - + Appearance Vzhled - + User Interface Rozhraní - + Storage - + Card Sources - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat Chat - + Sound Zvuk - + Shortcuts Skratky + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3809,17 +4960,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next Další - + Previous Předchozí - + Tip of the Day Tip @@ -3827,164 +4978,174 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel - + Reinstall Znovu instalovat - + Cancel Download Přerušit stahování - + Open Download Page - + Check for Client Updates - - - - + + + + Error Chyba - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - + Downloading update: %1 - + Checking for updates... Hledám aktualizace... - + Finished checking for updates Aktualizace byly skontrolovány - + No Update Available Žádná dostupná aktualizace - + Cockatrice is up to date! Cockatrice je v aktuální verzi! - + You are already running the latest version available in the chosen release channel. - + Current version Aktuální verze - + Selected release channel - - + + Update Available Dostupná aktualizace - - + + A new version of Cockatrice is available! Je dostupná nová verze Cockatrice! - - + + New version Nová verze - - + + Released - - + + Changelog - + Do you want to update now? Chcete aktualizovat nyní? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error Chyba aktualizace - + An error occurred while checking for updates: Došlo k chybě při kontrole aktualizací: - + An error occurred while downloading an update: Došlo k chybě při stahování aktualizace: - + Installing... Instaluji... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -3992,17 +5153,17 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing - + Copy to clipboard - + Debug Log @@ -4240,7 +5401,7 @@ You may have to manually download the new version. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -4248,7 +5409,7 @@ You may have to manually download the new version. FilterBuilder - + Type your filter here Zde napište svůj filtr: @@ -4256,45 +5417,88 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -4302,140 +5506,156 @@ You may have to manually download the new version. GameSelector - - - - - - - - - + + + + + + + + + Error Chyba - + Please join the appropriate room first. - + Wrong password. Špatné heslo. - + Spectators are not allowed in this game. Do této hry je divákům přístup zakázán. - + The game is already full. Hra je plná. - + The game does not exist any more. Hra již neexistuje. - + This game is only open to registered users. Hra je určena jen pro registrované. - + This game is only open to its creator's buddies. Hra je dostupná jen pro přátele zakládajícícho. - + You are being ignored by the creator of this game. Zakladatel hry vás ignoruje. - + Join Game - + Spectate Game - + Game Information - + + Cop&y game link + + + + Join Game as Judge - + Spectate Game as Judge - + + Join game Připojit ke hře - - Password: - Heslo: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. - + &Filter games &Filtrovat hry: - + C&lear filter Z&rušit filtr: - + C&reate V&ytvořit - + &Join &Připojit se - + Join as judge - + J&oin as spectator P&řipojit se jako divák - + Join as judge spectator - + Games shown: %1 / %2 - + Games Hry @@ -4476,105 +5696,105 @@ You may have to manually download the new version. GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password heslo - + buddies only jen pro přátele - + reg. users only jen pro registrované - + open decklists - - + + can chat může chatovat - + see hands vidět do rukou - + can see hands může vidět do rukou - + not allowed nepovolené - + Room Místnost - + Age Doba od otevření - + Description Popis - - Creator - Zakladatel + + Host + - + Type Typ - + Restrictions Omezení - + Players Hráči - + Spectators Diváci @@ -4582,158 +5802,275 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + English + Česky (Czech) + + + + + + + + + Choose path Vyberte cestu - + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> + + + + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + Language settings - + Language: Jazyk: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + Version settings - + Card database - + Startup settings - + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) - + Paths Cesty - + How to help with translations - + Decks directory: Adresář s balíčky: - + Filters directory: - + Replays directory: Adresář se záznamy her - + Pictures directory: Adresář s obrázky: - + Card database: Databáze karet: - + Custom database directory: - + Token database: Databáze tokenů: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice Automaticky spustit Oracle při spouštění nové verze Cockatrice - + Show tips on startup Ukázat tip při spuštění - + Last update check on %1 (%2 days ago) @@ -4741,47 +6078,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4875,59 +6212,353 @@ You may have to manually download the new version. + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu @@ -5145,410 +6776,431 @@ You may have to manually download the new version. MainWindow - - + + Player %1 Hráč %1 - + Load replay - + About Cockatrice O Cockatrice - + Version - + Cockatrice Webpage - + Project Manager: - + Past Project Managers: - + Developers: - + Our Developers - + Help Develop! - + Translators: Překlad: - + Our Translators - + Help Translate! - + Support: - + Report an Issue - + Troubleshooting - + F.A.Q. - - + + Error Chyba - + &Connect... &Připojit... - + &Disconnect &Odpojit - + Start &local game... Spustit &lokální hru... - + &Watch replay... - + &Full screen &Celá obrazovka - + &Register to server... - + &Restore password... - + &Settings... &Nastavení... - + &Exit &Konec - + A&ctions - + &Cockatrice &Cockatrice - + C&ard Database - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + Reload card database - + Tabs - + &Help &Nápověda - + &About Cockatrice &O Cockatrice - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + + Re-run Onboarding Wizard... + + + + Show/Hide - + + Sign-in cancelled + + + + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + + Are you sure? + Jste si jisti? + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + Yes, always enable - + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - + Information - + A card database update is already running. - + Unable to run the card database updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. @@ -5753,615 +7405,615 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play - + from their graveyard - + from exile z exilnutých karet - + from their hand - + the top card of %1's library - + the top card of their library - + from the top of %1's library - + from the top of their library - + the bottom card of %1's library - + the bottom card of their library - + from the bottom of %1's library - + from the bottom of their library - + from %1's library - + from their library - + from sideboard ze sideboardu - + from the stack ze stacku - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. - + %1 is not revealing the top card %2 any longer. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. - + %1 has conceded the game. - + %1 has unconceded the game. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. - + %1 has loaded a deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). - + %1 destroys %2. - + a card kartu - + %1 gives %2 control over %3. %1 předává kontrolu hráči %2 karty %3. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1 dává kartu %2 %3 do hry. - + %1 puts %2%3 into their graveyard face down. - + %1 puts %2%3 into their graveyard. - + %1 exiles %2%3 face down. - + %1 exiles %2%3. %1 exiluje %2%3. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. %1 přesouvá %2%3 do sideboardu. - + %1 plays %2%3 face down. - + %1 plays %2%3. %1 sesílá %2%3. - + %1 moves %2%3 to custom zone '%4' face down. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. Hra byla ukončena. - + The game has started. Hra začíná. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. - + %1 is now watching the game. %1 nyní sleduje hru. - + You have been kicked out of the game. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. - + %1 sets annotation of %2 to %3. - + %1 places %2 %3%4 counter(s) on %5 (now %6). - + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + %1 failed to undo their last draw. - + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. - + %1 untaps their permanents. - + %1 taps %2. - + %1 untaps %2. - + %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -6369,115 +8021,125 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message Přidat zprávu - - + + Message: Zpráva: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - + Ignore private messages sent by non-buddy users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -6485,42 +8147,42 @@ Cockatrice will now reload the card database. MoveMenu - + Move to - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + T&able - + &Hand - + &Graveyard - + &Exile @@ -6599,148 +8261,134 @@ Cockatrice will now reload the card database. PaletteEditorDialog - - + + Reset - - - Apply - - - - - - Save && Apply - - - - + ▼ Edit Palette - - + + ▶ Edit Palette - + Palette Editor — %1 - + <b>Palette Editor</b> &nbsp;·&nbsp; %1 - - This theme ships no default palette files - - - - - Replace current colours with the theme author's defaults - - - - - Switch between the light and dark palette files + + + Save - Editing: + Delete this scheme's custom palette and revert to the theme default (or the application palette) - Show or hide the per-role colour grid for manual tweaks + Switch between the light and dark palette files + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + ↺ Revert to theme default - + Discard unsaved edits and restore the last saved palette - - Preview this palette without saving to disk + + Cannot save: this theme has no writable directory - + Write palette-%1.toml and reload the theme - - Cannot save: this theme has no directory on disk - - - - + + Save failed - + + Could not write %1 to: %2 - - - No default found - - - - - No default palette file found for the "%1" scheme. - - PaletteGridWidget - + Active - + Disabled - + Inactive - + Normal interactive state - + Widget is disabled / not interactive - + Window is in background / unfocused + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + Phase @@ -6863,30 +8511,21 @@ Cockatrice will now reload the card database. Konec kola - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - PlayerActions - - - - + + + + grave - - - - + + + + exile @@ -7005,16 +8644,272 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters + + PlaymatCollectionDialog + + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + PrintingDisabledInfoWidget @@ -7035,12 +8930,12 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelector - + Display Navigation Buttons - + Printing Selector @@ -7048,25 +8943,65 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -7097,28 +9032,28 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -7134,95 +9069,103 @@ This setting means you'll only see the default printing for each card, instead o PtMenu - + Power / toughness - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -7230,17 +9173,17 @@ This setting means you'll only see the default printing for each card, instead o QObject - + Cockatrice card database (*.xml) - + All files (*.*) - + Cockatrice replays (*.cor) @@ -7260,17 +9203,22 @@ This setting means you'll only see the default printing for each card, instead o - + + Maybeboard + + + + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7281,7 +9229,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7292,100 +9240,110 @@ Are you sure you would like to disable this feature? - + Confirm Change + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -7393,121 +9351,189 @@ Are you sure you would like to disable this feature? QuickSetupPanel - + %1% - + <b>Quick Setup</b> - + Generate all palette roles automatically from a single accent colour - + Accent: - + Primary hue. Used directly for highlights and links. At high intensity it also tints buttons and backgrounds. - + Intensity: - + Subtle - + Full colour - + 0–30 Subtle tint — only highlights and links change hue 30–70 Accented — buttons, tooltips, and borders join in 70–100 Full colour — backgrounds, everything - + 70% - - - Generate ↓ - - - - - Derive all palette roles from the accent colour above. -Fine-tune individual colours in the grid afterwards. - - RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name Jméno - + ID ID - + Upload time Průběh nahrávání + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID - + Name - + Players - + Keep - + Time started - + Duration (sec) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu @@ -7549,37 +9575,37 @@ Fine-tune individual colours in the grid afterwards. RoomSelector - + Rooms Místnosti - + Joi&n &Připojit - + Room Místnost - + Description Popis - + Permissions - + Players Hráči - + Games Hry @@ -7587,7 +9613,7 @@ Fine-tune individual colours in the grid afterwards. SayMenu - + S&ay @@ -7595,32 +9621,32 @@ Fine-tune individual colours in the grid afterwards. SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - + Invalid key - + Shortcut already in use by: - + Clear - + Restore default @@ -7653,56 +9679,92 @@ Fine-tune individual colours in the grid afterwards. + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + + Shortcut editor + + + + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -7756,12 +9818,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -7769,27 +9831,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -7797,48 +9859,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -7846,157 +9908,170 @@ Please check your shortcut settings! StableReleaseChannel - + Default - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. + + StatsTally + + + Total Power + + + + + Total Toughness + + + StorageSettingsPage - - - + + + Success - + Cached card pictures have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + In-memory (currently loaded) card pictures have been reset. - + Card Picture Loader Caching Method: - + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. - + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. - + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. - + Delete Cached Images - + Delete Saved Images - + Clear In-Memory Images - + Card Picture Loader Cache Method - + Network Cache - + Filesystem - + In-Memory Picture Cache - + Network Cache Size: - + On-disk cache for downloaded pictures - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + In-memory cache for pictures not currently on screen - + Naming scheme: - + Day(s) @@ -8004,17 +10079,33 @@ Please check your shortcut settings! TabAccount - + + + My Reports + + + + Add to Buddy List - + Add to Ignore List - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account @@ -8135,118 +10226,118 @@ Please check your shortcut settings! TabArchidekt - - - + + + Desc. - - + + AND - - + + Require ALL selected colors - - + + Deck name... - - + + Owner... - - + + Packages - - + + Advanced Filters - + Bracket: - - + + Any - - + + Contains card... - - + + Commander... - - + + Tag... - - + + Deck Size - + Cards: - - + + Asc. - + Sort by: - + Filter by: - + Display Settings - - + + Search - - + + Formats @@ -8259,42 +10350,42 @@ Please check your shortcut settings! TabCardArtRules - + Card: - + ProviderId: - + Mode: - + Reason: - + Type a card name... - + Add rule - + Remove rule - + Refresh @@ -8307,52 +10398,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info - + Deck - + Filters - + &View - + Card Database - + Printing - + Visible - + Floating - + Reset layout - + Deck: %1 @@ -8360,49 +10451,49 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - + &View - + Printing - + Visible - + Floating - + Reset layout @@ -8410,22 +10501,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -8433,139 +10524,237 @@ Please check your shortcut settings! TabDeckStorage - + Local file system Lokální systém souborů - + Server deck storage Balíčky na serveru - - + + Open in deck editor Otevřít v editoru balíčků - + Rename deck or folder - + Upload deck Nahrát balíček - + Download deck Stáhnout balíček - - - - + + + + New folder Nová složka - - + + Delete Smazat - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + + + + Error - + Rename failed - - + + + Invalid deck file - + Enter deck name Vložit jméno balíčku - + This decklist does not have a name. Please enter a name: Tentobalíček nemá jméno. Prosím vložte jméno: - + Unnamed deck Bezejmenný balíček - + Failed to upload deck to server - + Delete local file - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: Název nové složky: - + Deck Storage @@ -8573,20 +10762,271 @@ Prosím vložte jméno: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -8599,27 +11039,27 @@ Prosím vložte jméno: TabEdhRecMain - + &Cards - + Top Commanders - + Tags - + Search for a card ... - + Search @@ -8632,191 +11072,207 @@ Prosím vložte jméno: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + + Invite + + + + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases &Fáze - + &Game &Hra - + Next &phase Další &fáze - + Next phase with &action - + Next &turn Další &kolo - + Reverse turn order - + &Remove all local arrows &Odstranit všechny lokální šipky - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede - - - + + + &Concede &Ukončit hru - + &Leave game &Opustit hru - + C&lose replay - + &Focus Chat - + &Say: &Chat: - + Selected cards - + &View - + Visible - + Floating - + Reset layout - + Concede Ukončit hru - + Are you sure you want to concede this game? Opravdu chcete ukončit tuto hru? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game Opustit hru - + Are you sure you want to leave this game? Opravdu chcete opustit tuto hru? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. @@ -8832,157 +11288,157 @@ Prosím vložte jméno: TabLog - + Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error Chyba - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: - + IP Address: - + Game Name: - + GameID: - + Message: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -8990,218 +11446,558 @@ The more information you put in, the more specific your results will be. TabMessage - + Private &chat - + &Leave &Odejít - + %1 - Private chat - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from - + %1 has left the server. %1 opustil server. - + %1 has joined the server. %1 se připojil k serveru. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system - + Server replay storage - - + + Watch replay - + Rename - - + + New folder - - + + Delete - + Open replays folder - + Download replay - + Toggle expiration lock - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay @@ -9211,65 +12007,478 @@ The more information you put in, the more specific your results will be. + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - - Friends - - - - - Online - - - - - Ignored - - - - + &Say: &Chat: - + Chat Chatovat - + &Room &Místnost - + &Leave room &Opustit místnost - + &Clear chat - + Chat Settings... - + mentioned you. - + Click to view - + You are flooding the chat. Please wait a couple of seconds. Píšete příliš intenzivně. Počkejte pár sekund. @@ -9277,35 +12486,35 @@ The more information you put in, the more specific your results will be. TabServer - + Server - - - - + + + + Error Chyba - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -9313,97 +12522,173 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - + Archidekt - + Home - + &Visual Deck Storage - + Visual Database Display - + Server - + Account - + Deck Storage - + Game Replays - + Administration - + Logs - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? - + There are still open games. Are you sure you want to quit? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -9411,38 +12696,38 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) @@ -9461,6 +12746,34 @@ Please refrain from engaging in this activity or further actions may be taken ag + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -9475,6 +12788,65 @@ Please refrain from engaging in this activity or further actions may be taken ag + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -9544,57 +12916,52 @@ Please refrain from engaging in this activity or further actions may be taken ag UserCardArtSettingsDialog - + Card Art Settings - + Type a card name... - + Card name: - - Card ProviderId: + + Printing: - + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + Left margin (%): - + Right margin (%): - - Vertical offset: - - - - - Zoom: - - - - - Parameters - - - - - Preview - - - - + Remove Banner Card @@ -9602,216 +12969,266 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details - + Private &chat - + Show this user's &games - - - Add to &buddy list - - - - - Remove from &buddy list - - - - - Add to &ignore list - - - - - Remove from &ignore list - - - - - Kick from &game - - - - - Warn user - - - View user's war&n history + View this user's &public decks - Ban from &server + Add to &buddy list - View user's &ban history + Remove from &buddy list - &Promote user to moderator + Add to &ignore list - Dem&ote user from moderator + Remove from &ignore list - Promote user to &judge + Kick from &game - Demote user from judge + Report user + Warn user + + + + + View user's war&n history + + + + + Ban from &server + + + + + View user's &ban history + + + + + &Promote user to moderator + + + + + Dem&ote user from moderator + + + + + Promote user to &judge + + + + + Demote user from judge + + + + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes - - + Investigate user + + + + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - + Failed to get admin notes. - - + + Success - + Successfully promoted user. - + Successfully demoted user. - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + Kick Player - + Are you sure you want to kick this player from the game? - - - + + + Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -9849,61 +13266,61 @@ Please refrain from engaging in this activity or further actions may be taken ag Administrátor - + Moderator - + Registered user Registrovaný - - + + Unregistered user Neregistrovaný - + Judge - + Unknown - + The entered password does not match your account. - - - - + + + + Information - + User information updated. - - - - - - + + + + + - - - + + + + Error Chyba @@ -9927,283 +13344,292 @@ Please refrain from engaging in this activity or further actions may be taken ag Edit Banner Card + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + The selected card is blacklisted on this server or another error occurred. - + Banner card removed. - + Banner card updated. - + This server does not permit you to update your user informations. - - - Password changed. - - - This server does not permit you to change your password. + Password changed. - The new password is too short. + This server does not permit you to change your password. + The new password is too short. + + + + The old password is incorrect. - + Avatar updated. - + This server does not permit you to update your avatar. - + An error occured while trying to update your avatar. - - - This server does not permit you to update your user informations. - An error occured while trying to updater your avatar. - - UserInfoPopup - - + + Games - + Chat - + Open private chat - + Profile - + View user profile - + Show this user's games - + + Invite + + + + + Invite to your game + + + + − Buddy - + Remove from buddy list - + + Buddy - + Add to buddy list - + − Ignore - + Remove from ignore list - + + Ignore - + Add to ignore list - + Ban - + Ban from server - + Warn - + Warn user - + Ban log - + View ban history - + Warn log - + View warning history - + Notes - + View admin notes - + − Mod - + Demote from moderator - + + Mod - + Promote to moderator - + − Judge - + Demote from judge - + + Judge - + Promote to judge - + Join game - + Spectate - - + + Loading games… - + Could not load games. - + No active games. @@ -10211,223 +13637,344 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings Obecné - + &Double-click cards to play them (instead of single-click) &Pro zahraní karty je třeba dvojklik - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens - + Show selection count during drag selection - + Show total selection count - - Show subtype breakdown in selection tally + + + Disabled - - Use tear-off menus, allowing right click menus to persist on screen + + + Enabled - - Keep game chat focused when clicking in game (Note: disables card view search bar) - - - - - Notifications settings - - - - - Enable notifications in taskbar - - - - - Notify in the taskbar for game events while you are spectating + + + Automatic + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Use tear-off menus, allowing right click menus to persist on screen + + + + + Keep game chat focused when clicking in game (Note: disables card view search bar) + + + + + Notifications settings + + + + + Enable notifications in taskbar + + + + + Notify in the taskbar for game events while you are spectating + + + + Notify in the taskbar when users in your buddy list connect - + Animation settings Animace - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation &Animace tapnutí/odtapnutí - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings - + Open deck in new tab by default - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - - do nothing + + Do nothing - - ask to convert to .cod + + Ask to convert to .cod - - always convert to .cod + + Always convert to .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings - + Buffer time for backwards skip via shortcut: + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + Flip coin - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -10464,22 +14011,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... - + Save Filter - + Save all currently applied filters to a file - + Enter filename... @@ -10487,72 +14034,72 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterToolbarWidget - + Sort by - + Filter by - + Save and load filters - + Filter by exact card name - + Filter by card main-type - + Filter by card sub-type - + Filter by set - + Filter by format legality - + Save/Load - + Name - + Main Type - + Sub Type - + Sets - + Formats @@ -10654,7 +14201,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -10662,19 +14209,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -10720,28 +14267,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - - + + Visual - + Loading database ... - + Clear all filters - + Table @@ -10749,43 +14296,43 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat @@ -10801,12 +14348,12 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -10814,17 +14361,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -10832,7 +14379,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -10840,52 +14387,57 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + Show Color Identity - + Show Tags On Deck Previews - + + Show Upload Time + + + + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -10893,7 +14445,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -10901,22 +14453,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -10924,17 +14476,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + + Select decks to share + + + + Visual Deck Storage Settings @@ -10942,177 +14499,202 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Warn user for misconduct - - + + Error Chyba - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - - Search by set name, code, or type - - - - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - - How to use custom card art + + Search by set name, code, type, or release date + How to use custom card art + + + + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -11120,93 +14702,85 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing zamíchat po zavření - + pile view - - i18n - - - English - Česky (Czech) - - main - + Connect on startup - + Debug to file @@ -11990,6 +15564,16 @@ Please refrain from engaging in this activity or further actions may be taken ag Graveyard Hřbitov + + + Report Queue + + + + + Moderation + + diff --git a/cockatrice/translations/cockatrice_de.ts b/cockatrice/translations/cockatrice_de.ts index 51bee3cd4..265c29cdc 100644 --- a/cockatrice/translations/cockatrice_de.ts +++ b/cockatrice/translations/cockatrice_de.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... Zähler &setzen... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Zähler setzen - + New value for counter '%1': Neuer Wert für den Zähler '%1': @@ -36,66 +36,115 @@ AbstractTabDeckEditor - + Open in new tab In neuem Reiter öffnen - + Are you sure? Sind Sie sicher? - + The decklist has been modified. Do you want to save the changes? Die Deckliste wurde bearbeitet. Änderungen speichern? - - - - - - + + + + + + Error Fehler - + Could not open deck at %1 Deck an Stelle %1 konnte nicht geöffnet werden - + Could not save remote deck Remote-Deck konnte nicht gespeichert werden - - + + The deck could not be saved. Please check that the directory is writable and try again. Das Deck konnte nicht abgespeichert werden. Bitte überprüfen Sie, ob das Verzeichnis bearbeitet werden kann und versuchen Sie es erneut. - + Save deck Deck speichern - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. Das Deck konnte nicht abgespeichert werden. - + There are no cards in your deck to be exported Es gibt keine Karten in Ihrem Deck zum Exportieren + + AccountSetupPage + + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + AddAnalyticsPanelDialog @@ -107,12 +156,12 @@ Bitte überprüfen Sie, ob das Verzeichnis bearbeitet werden kann und versuchen AdminNotesDialog - + Update Notes Aktualisierungsnotizen - + Admin Notes for %1 Administrator Notizen für %1 @@ -138,218 +187,304 @@ Bitte überprüfen Sie, ob das Verzeichnis bearbeitet werden kann und versuchen AppearanceSettingsPage - + seconds Sekunden - + Error Fehler - + Could not create themes directory at '%1'. Das Themeverzeichnis '%1' konnte nicht erstellt werden. - + Theme settings Darstellungseinstellungen - + Current theme: Aktuelles Theme: - + Open themes folder Öffne Themeverzeichnis - + Home tab background source: Startseitenhintergrund-Quelle: - + Home tab background shuffle frequency: Startseitenhintergrund Wechselfrequenz: - + Disabled Deaktiviert - + Menu settings Einstellungsmenü - + Show keyboard shortcuts in right-click menus Tastatur-Kürzel anzeigen bei Rechtsklickmenü - + Show game filter toolbar above list in room tab Spiel-Filter Symbolleiste über der Liste im Raum-Tab anzeigen - + Card rendering Kartendarstellung - + Display card names on cards having a picture Kartennamen auch bei Karten mit Bildern darstellen - + Auto-Rotate cards with sideways layout Automatisches Drehen von Karten mit Seitwärts-Ausrichtung - + Override all card art with personal set preference (Pre-ProviderID change behavior) Überschreiben aller Kartenbilder mit persönlichen Set-Präferenzen (Vor-AnbieterID Wechselverhalten) - + Light - + Hell - + Dark - + Dunkel - + + System - + + Show all playmats + + + + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + Active theme palette: - + + Active theme style: + + + + + Qt widget style saved to this theme ("System" lets the application decide) + + + + Edit theme palette - + Home tab settings - + Display card name of background in bottom right - + + Dim the home tab background + + + + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + Styling settings - + Style user list - + Card printings - + Bump sets that the deck contains cards from to the top in the printing selector Priorisieren von Sets, in welchen Karten des ausgewählten Decks vorhanden sind, nach oben in der Druckauswahl - + Scale cards on mouse over Karten beim Darüberfahren mit der Maus vergrößern - + Use rounded card corners Abgerundete Kartenecken verwenden - + Card layout - + Minimum overlap percentage of cards on the stack and in vertical hand Minimaler Überschneidungsanteil der Karten auf dem Stapel und in der vertikalen Hand - + Maximum initial height for card view window: Maximale Starthöhe für das Kartenansichtsfenster - - + + rows Reihen - + Maximum expanded height for card view window: Maximale ausgeklappte Höhe für das Kartenansichtsfenster: - + Card counters Kartenzähler - + Counter %1 Zähler %1 - + Hand layout Handdarstellung - + Display hand horizontally (wastes space) Hand horizontal anzeigen (verschwendet Platz) - + Enable left justification Linksbündige Ausrichtung aktivieren - + Table grid layout Spielfeldraster - + Invert vertical coordinate Vertikale Koordinate umkehren - + Minimum player count for multi-column layout: Mindestspieleranzahl für mehrspaltige Anordnung: - + Maximum font size for information displayed on cards: Maximale Schriftgröße für die Anzeige von Informationen auf Karten: @@ -357,12 +492,12 @@ Bitte überprüfen Sie, ob das Verzeichnis bearbeitet werden kann und versuchen ArchidektApiResponseDeckDisplayWidget - + Back to results Zurück zu den Ergebnissen - + Open Deck in Deck Editor Deck im Deck-Editor öffnen @@ -388,112 +523,112 @@ Bitte überprüfen Sie, ob das Verzeichnis bearbeitet werden kann und versuchen BanDialog - + ban &user name &Benutzername - + ban &IP address &IP-Adresse - + ban client I&D Client I&D bannen - + Ban type Art des Banns - + &permanent ban &permanenter Bann - + &temporary ban &temporärer Bann - + &Days: &Tage: - + &Hours: &Stunden: - + &Minutes: &Minuten: - + Duration of the ban Länge des Banns - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Bitte geben Sie den Grund für den Bann ein. Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nicht gesehen werden. - + Please enter the reason for the ban that will be visible to the banned person. Bitte geben Sie den Grund ein, den die gebannte Person sehen kann. - + Redact all messages from this user in all rooms Entferne alle Nachrichten dieses Nutzers in allen Räumen - + &OK &OK - + &Cancel &Abbrechen - + Ban user from server Benutzer vom Server bannen - - - - + + + + Error Fehler - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Bitte wählen Sie einen Namens-, Adress- oder Client-ID-Bann oder eine Kombination aus diesen. - + You must have a value in the name ban when selecting the name ban checkbox. Wenn Sie das Namensbannkästchen auswählen, muss ein Wert für den Namen eingegeben werden. - + You must have a value in the ip ban when selecting the ip ban checkbox. Wenn Sie das Adressbannkästchen auswählen, muss ein Wert für die IP eingegeben werden. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Wenn Sie das Client-ID-Kästchen auswählen, muss ein Wert für die Client-ID eingegeben werden. @@ -501,22 +636,22 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic BetaReleaseChannel - + Beta Beta - + No reply received from the release update server. Keine Antwort vom Veröffentlichungsaktualisierungsserver erhalten. - + Invalid reply received from the release update server. Ungültige Antwort vom Veröffentlichungsaktualisierungsserver erhalten. - + No reply received from the file update server. Keine Antwort vom Dateiaktualisierungsserver erhalten. @@ -524,7 +659,20 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardArtPreviewWidget - + + Banner preview + + + + + + + + Zoom %1× + + + + No card selected @@ -532,22 +680,22 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardArtRulesModel - + Card - + ProviderId - + Mode - + Reason @@ -555,65 +703,209 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardDatabaseModel - + Name Name - + Sets Editionen - + Mana cost Manakosten - + Card type Kartentyp - + P/T S/W - + Color(s) Farbe(n) + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + CardDatabaseView - + Add to Deck - + Add to Sideboard - + Select Printing - + + Add to Zone + + + + + Create &new zone... + + + + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards @@ -723,22 +1015,22 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardInfoFrameWidget - + Image Bild - + Description Beschreibung - + Both Beide - + View transformation Transformation anzeigen @@ -746,22 +1038,22 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardInfoPictureWidget - + View related cards Zugehörige Karten ansehen - + Add card to deck Karte zum Deck hinzufügen - + Mainboard Mainboard - + Sideboard Sideboard @@ -769,27 +1061,27 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardInfoTextWidget - + Name: Name: - + Set: Set: - + Collector Number: Sammlernummer: - + Related cards: Zugehörige Karten: - + Unknown card: Unbekannte Karte: @@ -797,129 +1089,129 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardMenu - + Re&veal to... Zeigen an.... - + &All players &Alle Spieler - + View related cards Verwandte Karten ansehen - + Token: Spielstein: - + All tokens Alle Spielsteine - + &Select All &Alles auswählen - + S&elect Row Reihe auswählen - + S&elect Column Spalte auswählen - + &Play &Spielen - + &Hide &Verstecken - + Play &Face Down Verdeckt ausspielen - + &Tap / Untap Turn sideways or back again &Tappen/ Enttappen - + Skip &untapping Enttappen überspringen - + T&urn Over Turn face up/face down Umdrehen - + &Peek at card face %Kartenvorderseite ansehen - + &Clone &Kopieren - + Attac&h to card... An Karte anhängen - + Unattac&h Lösen - + &Draw arrow... &Pfeil zeichnen - + &Set annotation... &Anmerkung setzen - + Reduce life by power - + Ca&rd counters Kartenzähler - + &Add counter (%1) &Zähler hinzufügen (%1) - + &Remove counter (%1) &Zähler entfernen (%1) - + &Set counters (%1)... &Zähler setzen (%1)... @@ -940,7 +1232,7 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic CardSizeWidget - + Card Size Kartengröße @@ -1080,6 +1372,24 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic %1's individuelle Zone '%2' + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser @@ -1109,280 +1419,382 @@ Dies wird nur für Moderatoren gespeichert und kann von der gebannten Person nic + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + ConnectionController - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - - - - - - - - - - - + + + + + + + + + + - - + + + Error - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + Server timeout - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Information - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -1390,113 +1802,113 @@ To update your client, go to Help -> Check for Updates. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - + Requesting forgotten password to %1 as %2... - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. @@ -1546,7 +1958,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorCardDatabaseDockWidget - + Card Database Kartendatenbank @@ -1562,17 +1974,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) Suche nach Kartennamen (oder Suchausdrücken) - + Add card to &maindeck Karte zum &Hauptdeck hinzufügen - + Add card to &sideboard Karte zum &Sideboard hinzufügen @@ -1580,97 +1992,158 @@ To update your client, go to Help -> Check for Updates. DeckEditorDeckDockWidget - + Loading Database... Datenbank wird geladen... - + Banner Card Bannerkarte - + Main Type Haupttyp - + Mana Cost Manakosten - + Colors Farben - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing Auswahl der Auflage - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck Deck - + Deck &name: Deck&name: - + Banner Card/Tags Visibility Settings Bannerkarte/Sichtbarkeitseinstellungen von Tags - + Show banner card selection menu Bannerkartenauswahlmenü anzeigen - + Show tags selection menu Tagauswahlmenü anzeigen - + &Comments: &Kommentare: - + Group by: Gruppieren nach: - + Format: Format: - + Hash: Hash: - + &Increment number &Zahl erhöhen - + &Decrement number &Zahl verringern - + &Remove row &Reihe entfernen - + Swap card to/from sideboard Karte ins/aus dem Sideboard tauschen @@ -1678,17 +2151,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorFilterDockWidget - + Filters Filter - + &Clear all filters &Alle Filter entfernen - + Delete selected Auswahl löschen @@ -1696,114 +2169,119 @@ To update your client, go to Help -> Check for Updates. DeckEditorMenu - + &Deck Editor &Deckeditor - + &New deck &Neues Deck - + &Load deck... &Deck laden... - + Load recent deck... Laden der letzten Decks... - + Clear Alles entfernen - + &Save deck &Deck speichern - + Save deck &as... &Deck speichern unter... - + + Share deck... + + + + Load deck from cl&ipboard... Deck aus &Zwischenablage laden... - + Edit deck in clipboard Deck in Zwischenablage bearbeiten - - + + Annotated Kommentiert - - + + Not Annotated Nicht kommentiert - + Save deck to clipboard Deck in Zwischenablage speichern - + Annotated (No set info) Komentiert (keine Setinformationen) - + Not Annotated (No set info) Nicht kommentiert (keine Setinformationen) - + &Print deck... &Deck drucken... - + Load deck from online service... Deck von Onlineservice laden.... - + &Send deck to online service &Deck an Onlineservice senden - + Create decklist (decklist.org) Deckliste erstellen (decklist.org) - + Create decklist (decklist.xyz) Deckliste erstellen (decklist.xyz) - + Analyze deck (deckstats.net) Deck analysieren (deckstats.net) - + Analyze deck (tappedout.net) Deck analysieren (tappedout.net) - + &Close &Schließen @@ -1811,7 +2289,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorPrintingSelectorDockWidget - + Printing Selector Auflagenauswahl @@ -1819,110 +2297,152 @@ To update your client, go to Help -> Check for Updates. DeckEditorSettingsPage - - + + Update Spoilers Spoiler aktualisieren - + Success Erfolgreich - + Download URLs have been reset. Download-URLs wurden zurückgesetzt. - + Add URL URL hinzufügen - - + + URL: URL - - + + Edit URL URL bearbeiten - + Add New URL Neue URL hinzufügen - + Remove URL URL entfernen - + Updating... Aktualisiere... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path Pfad auswählen - + URL Download Priority URL Downloadpriorität - + Spoilers Spoiler - + Download Spoilers Automatically Lade Spoiler automatisch herunter - + Spoiler Location: Spoiler Verzeichnis: - + Last Change Letzte Änderung - + Spoilers download automatically on launch Spoiler werden beim Start automatisch heruntergeladen - + Press the button to manually update without relaunching Drücke den Knopf um manuell zu aktualisieren ohne neu zu starten - + Do not close settings until manual update is complete Schließen Sie die Einstellungen nicht solange die manuelle Aktualisierung nicht abgeschlossen ist. - + Download card pictures on the fly Kartenbilder dynamisch herunterladen - + How to add a custom URL Wie eine benutzerdefinierte URL hinzugefügt wird - + Reset Download URLs Download-URLs zurücksetzen @@ -1930,32 +2450,32 @@ To update your client, go to Help -> Check for Updates. DeckListHistoryManagerWidget - + Undo Rückgängig machen - + Redo Wiederholen - + Undo/Redo history Rückgängig machen/Wiederholen Verlauf - + Click on an entry to revert to that point in the history. Einen Eintrag anklicken um zu diesem Zeitpunkt im Verlauf zurückzukehren - + [redo] [wiederholen]  - + [undo] [rückgängig machen]  @@ -1963,27 +2483,27 @@ To update your client, go to Help -> Check for Updates. DeckListModel - + Count Anzahl - + Set Festlegen - + Number Nummer - + Provider ID ID des Anbieters - + Card Karte @@ -2004,17 +2524,22 @@ To update your client, go to Help -> Check for Updates. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match Modus: Exakte Übereinstimmung - + Mode: Includes Modus: Enthält - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) Farbidentitätsfiltermodus (UND/ODER/NICHT Verknüpfungen der Filter) @@ -2022,7 +2547,7 @@ To update your client, go to Help -> Check for Updates. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... Tags bearbeiten ... @@ -2030,62 +2555,62 @@ To update your client, go to Help -> Check for Updates. DeckPreviewTagDialog - + Deck Tags Manager Decktagsmanager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: Verwalte Decktags. Wähle Tags nach Bedarf aus oder füge neue hinzu: - + Add a new tag (e.g., Aggro️) Neuen Tag hinzufügen (bspw. Aggro) - + Add Tag Tag hinzufügen - + Filter tags... Tags filtern... - + OK OK - + Edit default tags Standardtags bearbeiten - + Cancel Abbrechen - + Invalid Input Ungültige Eingabe - + Tag name cannot be empty! Tagname kann nicht leer sein! - + Duplicate Tag Tag duplizieren - + This tag already exists. Dieser Tag existiert bereits. @@ -2093,94 +2618,109 @@ To update your client, go to Help -> Check for Updates. DeckPreviewWidget - + Banner Card Bannerkarte - + Open in deck editor Im Deckeditor öffnen - + + Share deck... + + + + Edit Tags Tags bearbeiten - + Rename Deck Deck umbenennen - + Save Deck to Clipboard Deck in die Zwischenablage speichern - + Annotated Kommentiert - + Annotated (No set info) Komentiert (keine Setinformationen) - + Not Annotated Nicht kommentiert - + Not Annotated (No set info) Nicht kommentiert (keine Setinformationen) - + Rename File Datei umbenennen - + Delete File Datei löschen - + Set Banner Card Bannerkarte setzen - - + + New name: Neuer Name: - - + + Rename deck + + + + + Rename file + + + + + Error Fehler - + Rename failed Umbenennen fehlgeschlagen - + Delete file Datei löschen - + Are you sure you want to delete the selected file? Sind Sie sicher, dass die ausgewählte Datei gelöscht werden soll? - + Delete failed Löschen fehlgeschlagen @@ -2188,57 +2728,102 @@ To update your client, go to Help -> Check for Updates. DeckStateManager - + Rename deck to "%1" from "%2" Deck umbenennen von "%1" zu "%2" - + Updated comments (was %1 chars, now %2 chars) Kommentare aktualisiert (davor %1 Zeichen, jetzt %2 Zeichen) - + Set banner card to %1 (%2) Bannerkarte setzen als %1 (%2) - + + Set playmat to %1 + + + + Tags changed Tags geändert - + Set format to %1 Format setzen zu %1 - + Added (%1): %2 (%3) %4 (%1): %2 (%3) %4 hinzugefügt - + Moved to %1 1 × "%2" (%3) Nach %1 1 × "%2" (%3) bewegt - + Removed "%1" (all copies) "%1" entfernt (alle Kopien) - + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + %1 1 × "%2" (%3) %1 1 × "%2" (%3) - + Added Hinzugefügt - + Removed Entfernt @@ -2260,88 +2845,116 @@ To update your client, go to Help -> Check for Updates. DeckViewContainer - + Load deck... Deck laden... - + Load remote deck... Deck vom Server laden... - + Load from clipboard... Aus der Zwischenablage laden... - + Load from website... Von Internetseite laden.... - + Unload deck Deck entladen - + Ready to start Bereit zum Start - + Force start Zwangsstart - + Sideboard unlocked Sideboard entsperrt - + Sideboard locked Sideboard gesperrt - - + + Error Fehler - + The selected file could not be loaded. Die gewählte Datei konnte nicht geladen werden. - + Deck is greater than maximum file size. Deck übersteigt die maximale Dateigröße. - + Are you sure you want to force start? This will kick all non-ready players from the game. Sind Sie sicher, dass sie den Start erzwingen wollen? Dies wirft alle nicht bereiten Spieler aus dem Spiel. - + Cockatrice Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion Deckformatkonvertierung - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? @@ -2350,7 +2963,7 @@ Tags können nur zu Decks im .cod Format hinzugefügt werden. Möchten sie das Deck ins .cod Format konvertieren? - + Remember and automatically apply choice in the future Auswahl speichern und automatisch in der Zukunft anwenden @@ -2363,143 +2976,143 @@ Möchten sie das Deck ins .cod Format konvertieren? Lade herunter... - + Known Hosts Bekannte Hosts - + Delete the currently selected saved server Den derzeit gespeicherten Server löschen - + Refresh the server list with known public servers Aktualisiere die Serverliste der bekannten öffentlichen Server - + New Host Neuer Host - + Name: Name: - + &Host: &Server: - + &Port: &Port: - + Player &name: Spieler&name: - + P&assword: P&asswort: - + &Save password Passwort &speichern - + A&uto connect a&utomatisch verbinden - + Automatically connect to the most recent login when Cockatrice opens Automatisch mit dem letzten Login verbinden, wenn sich Cockatrice öffnet - + If you have any trouble connecting or registering then contact the server staff for help! Falls Probleme mit der Verbindung oder Registrierung bestehen, kontaktieren Sie das Personal für Hilfe! - - + + Webpage Webseite - + Reset Password Passwort zurücksetzen - + Forgot password? Passwort vergessen? - + &Connect &Verbinden... - + Server Server - + Login Login - + Server Contact Serverkontakt - + Connect to Server Verbinde zum Server - + Server URL Server URL - + Communication Port Kommunikationsport - + Unique Server Name Einzigartiger Servername - + Connection Warning Verbindungswarnung - + You need to name your new connection profile. Sie müssen Ihr neues Verbindungsprofil benennen. - + Connect Warning Verbindungswarnung - + The player name can't be empty. Der Spielername kann nicht leer sein. @@ -2507,122 +3120,122 @@ Möchten sie das Deck ins .cod Format konvertieren? DlgCreateGame - + Re&member settings Einstellungen speichern - + &Description: &Beschreibung: - + P&layers: &Spieler: - + General Allgemeines - + Game type Spieltyp - + &Password: &Passwort: - + Only &buddies can join Nur &Freunde können teilnehmen - + Only &registered users can join Nur &registrierte Benutzer können teilnehmen - + Joining restrictions Teilnahmebedingungen - + &Spectators can watch Zuschauer erlauben - + Spectators &need a password to watch Zuschauer benötigen das Passwort - + Spectators can &chat Zuschauer können &chatten - + Spectators can see &hands Zuschauer sehen die Hände - + Create game as spectator Spiel als Zuschauer erstellen - + Spectators Zuschauer - + Starting life total: Anfangslebenspunktzahl - + Open decklists in lobby Offene Decklisten in Lobby - + Create game as judge Spiel als Richter erstellen - + Game setup options Spielaufsetzungsoptionen - + &Clear Löschen - + Create game Spiel erstellen - + Game information &Spielinformationen - + Error Fehler - + Server error. Serverfehler. @@ -2630,97 +3243,97 @@ Möchten sie das Deck ins .cod Format konvertieren? DlgCreateToken - + &Name: &Name: - + Token Spielstein - + C&olor: &Farbe: - + white weiß - + blue blau - + black schwarz - + red rot - + green grün - + multicolor mehrfarbig - + colorless farblos - + &P/T: &Kampfwerte: - + &Annotation: &Hinweis: - + &Destroy token when it leaves the table Spielstein &zerstören, wenn er das Spielfeld verlässt - + Create face-down (Only hides name) Verdeckt erstellen (versteckt nur den Namen) - + Token data Spielstein-Daten - + Show &all tokens &Alle möglichen Spielsteine zeigen - + Show tokens from this &deck Spielsteine dieses &Decks zeigen - + Choose token from list Spielstein aus Liste auswählen - + Create token Spielstein erstellen @@ -2728,53 +3341,53 @@ Möchten sie das Deck ins .cod Format konvertieren? DlgDefaultTagsEditor - + Edit Tags Tags bearbeiten - + Add Hinzufügen - + Confirm Bestätigen - + Cancel Abbrechen - + Enter a tag and press Enter Tag eingeben und Eingabetaste drücken - - + + ✖ ✖ - + Invalid Input Ungültige Eingabe - + Tag name cannot be empty! Tagname kann nicht leer sein! - + Duplicate Tag Tag duplizieren - + This tag already exists. Dieser Tag existiert bereits @@ -2783,7 +3396,7 @@ Möchten sie das Deck ins .cod Format konvertieren? DlgEditAvatar - + No image chosen. Kein Bild gewählt. @@ -2795,27 +3408,32 @@ To remove your current avatar, confirm without choosing a new image. Um Ihren derzeitigen Avatar zu entfernen, bestätigen Sie, ohne ein neues Bild zu wählen. - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... Durchsuchen... - + Change avatar Avatar wechseln - + Open Image Bild öffnen - + Image Files (*.png *.jpg *.bmp) Bild-Dateien (*.png *.jpg *.bmp) - + Invalid image chosen. Ungültiges Bild gewählt. @@ -2841,38 +3459,38 @@ Um Ihren derzeitigen Avatar zu entfernen, bestätigen Sie, ohne ein neues Bild z DlgEditPassword - + Old password: Altes Passwort: - + New password: Neues Passwort: - + Confirm new password: Bestätige neues Passwort: - + Change password Passwort ändern - - + + Error Fehler - + Your password is too short. Das Passwort ist zu kurz. - + The new passwords don't match. Die neuen Passwörter stimmen nicht überein. @@ -2880,93 +3498,93 @@ Um Ihren derzeitigen Avatar zu entfernen, bestätigen Sie, ohne ein neues Bild z DlgEditTokens - + &Name: &Name: - + C&olor: &Farbe: - + white weiß - + blue blau - + black schwarz - + red rot - + green grün - + multicolor mehrfarbig - + colorless farblos - + &P/T: &Kampfwerte: - + &Annotation: &Hinweis: - + Token data Spielstein-Daten - - + + Add token Spielstein hinzufügen - + Remove token Spielstein entfernen - + Edit custom tokens Benutzerdefinierte Spielsteine bearbeiten - + Please enter the name of the token: Bitte geben Sie den Namen des Spielsteins ein: - + Error Fehler - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Es existiert bereits eine Karte oder ein Spielstein mit dem gewählten Namen. @@ -3045,8 +3663,9 @@ Aktivieren Sie die Edition „Token" im „Editionen verwalten...“ Menü - Hide games that have started - Bereits begonnene Spiele ausblenden + Hide started games + Hide games that have started + @@ -3081,8 +3700,9 @@ Aktivieren Sie die Edition „Token" im „Editionen verwalten...“ Menü - &Creator name: - &Name des Erstellers: + &Host name: + &Creator name: + @@ -3148,52 +3768,52 @@ Aktivieren Sie die Edition „Token" im „Editionen verwalten...“ Menü DlgForgotPasswordChallenge - + Reset Password Challenge Warning Setze Passwortherausforderungswarnung zurück - + A problem has occurred. Please try to request a new password again. Ein Problem ist aufgetreten. Bitte erneut ein neues Passwort anfordern. - + Enter the information of the server and the account you'd like to request a new password for. Gebe die Informationen des Servers und Kontos, für das ein neues Passwort angefordert werden soll, an. - + &Host: &Host: - + &Port: &Port: - + Player &name: Spieler&name: - + Email: E-Mail: - + Reset Password Challenge Setze Passwortherausforderung zurück - + Reset Password Challenge Error Setze Passwortherausforderungsfehler zurück - + The email address can't be empty. Die E-Mail-Adresse kann nicht leer sein. @@ -3201,37 +3821,37 @@ Aktivieren Sie die Edition „Token" im „Editionen verwalten...“ Menü DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. Gebe die Informationen des Servers, für den ein neues Passwort angefordert werden soll, an. - + &Host: &Host: - + &Port: &Port: - + Player &name: Spieler&name: - + Reset Password Request Passwortrücksetzungsanfrage - + Reset Password Error Passwortrücksetzungsfehler - + The player name can't be empty. Der Spielername kann nicht leer sein. @@ -3239,94 +3859,127 @@ Aktivieren Sie die Edition „Token" im „Editionen verwalten...“ Menü DlgForgotPasswordReset - + Reset Password Warning Passwortrücksetzungswarnung - + A problem has occurred. Please try to request a new password again. Ein Problem ist aufgetreten. Bitte erneut ein neues Passwort anfordern. - + Enter the received token and the new password in order to set your new password. Gebe den erhaltenen Token und das neue Passwort ein, um das neue Passwort zu setzen. - + &Host: &Host: - + &Port: &Port: - + Player &name: Spieler&name: - + Token: Spielstein: - - + + New Password: Neues Passwort: - + Reset Password Passwort zurücksetzen - + The player name can't be empty. Der Spielername kann nicht leer sein. - - - - + + + + Reset Password Error Passwortrücksetzungsfehler - + The token can't be empty. Der Spielstein kann nicht leer sein. - + The new password can't be empty. Das neue Passwort kann nicht leer sein. - + Error Fehler - + Your password is too short. Das Passwort ist zu kurz. - + The passwords do not match. Die Passwörter stimmen nicht überein. + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck Deck laden @@ -3418,166 +4071,567 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLocalGameOptions - + Players: Spieler: - + General Allgemein - + Starting life total: Lebenspunkte zum Spielstart: - + Game setup options Spieleinstellungen - + Remember settings Einstellungen merken - + Local game options Lokale Spieleinstellungen + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): Kartenname (oder Suchbezeichnung) - + Number of hits: Anzahl der Treffer: - + Auto play hits Automatisches Ausspielen von Treffern - + Put top cards on stack until... Oberste Karten auf den Stapel legen bis... - + No cards matching the search expression exists in the card database. Proceed anyways? Keine Karten, die den Suchbezeichnungen entsprechen, existieren in der Kartendatenbank. Trotzdem fortfahren? - + Cockatrice Basilisk - + Invalid filter Ungültiger Filter + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. Geben Sie Ihre Informationen und die Informationen des Servers, bei dem Sie sich registrieren wollen, an. Die E-Mail-Adresse wird zur Accountverifikation genutzt. - + &Host: &Host: - + &Port: &Port: - + Player &name: Spieler&name: - + P&assword: P&asswort - + Password (again): Passwort (Wiederholung) - + Email: E-Mail: - + Email (again): E-Mail (Wiederholung) - + Country: Land: - + Undefined Undefiniert - + Real name: Echter Name: - + Register to server Auf Server registrieren - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning Registrierungswarnung - + Your password is too short. Das Passwort ist zu kurz. - + Your passwords do not match, please try again. Ihre Passwörter stimmen nicht überein, bitte versuchen Sie es erneut. - + Your email addresses do not match, please try again. Ihre Mailadressen stimmen nicht überein, bitte versuchen Sie es erneut. - + The player name can't be empty. Der Spielername kann nicht leer sein. + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice @@ -3643,46 +4697,12 @@ Die E-Mail-Adresse wird zur Accountverifikation genutzt. DlgSettings - + Unknown Error loading card database Unbekannter Fehler beim Laden der Kartendatenbank - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - Ihre Kartendatenbank ist ungültig. - -Cockatrice funktioniert möglicherweise mit einer ungültigen Kartendatenbank nicht richtig. - -Sie müssen Oracle unter Umständen nochmals ausführen um Ihre Kartendatenbank zu aktualisieren. - -Möchten Sie Ihren Speicherort der Datenbank aktualisieren?? - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - Ihre Kartendatenbank-Version ist zu alt. - -Dies kann zu Problemen beim Laden von Karteninformationen oder Bildern führen. - -Normalerweise kann dies durch einen erneuten Start von Oracle, um die Kartendatenbank zu aktualisieren, behoben werden. - -Möchten Sie Ihren Speicherort der Datenbank aktualisieren? - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3695,7 +4715,7 @@ Bitte erstellen Sie ein Ticket auf https://github.com/Cockatrice/Cockatrice/issu Möchten Sie die Einstellung des Datenbankspeicherorts ändern? - + File Error loading your card database. Would you like to change your database location setting? @@ -3704,7 +4724,7 @@ Would you like to change your database location setting? Möchten Sie Ihren Speicherort der Datenbank aktualisieren? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3713,7 +4733,7 @@ Would you like to change your database location setting? Möchten Sie Ihren Speicherort der Datenbank aktualisieren? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3726,77 +4746,208 @@ Bitte erstellen Sie ein Ticket auf https://github.com/Cockatrice/Cockatrice/issu Möchten Sie die Einstellung des Datenbankspeicherorts ändern? - - - + + + Error Fehler - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Der Pfad zu Ihrem Deckordner ist ungültig. Möchten Sie zurückgehen und den korrekten Pfad einstellen? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Der Pfad zu Ihrem Kartenbilderordner ist ungültig. Möchten Sie zurückgehen und den korrekten Pfad einstellen? - + Settings Einstellungen - + + Search settings... + + + + + OK + + + + General Allgemeines - + Appearance Erscheinungsbild - + User Interface Benutzeroberfläche - + Storage - + Card Sources Kartenquellen - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat Chat - + Sound Töne - + Shortcuts Tastaturkürzel + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check Kartenaktualisierungsüberprüfung - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. @@ -3805,27 +4956,27 @@ Bitte wählen Sie aus, wie der Kartendatenbankaktualisierer ausgeführt werden s Sie können das Verhalten jederzeit im "Allgemein"-Tab der Einstellungen ändern. - + Run in foreground Im Vordergrund ausführen - + Run in background Im Hintergrund ausführen - + Run in background and always from now on Immer im Hintergrund ausführen - + Don't prompt again and don't run Nicht erneut fragen und nicht ausführen - + Don't run this time Dieses Mal nicht ausführen @@ -3833,17 +4984,17 @@ Sie können das Verhalten jederzeit im "Allgemein"-Tab der Einstellung DlgTipOfTheDay - + Next Vor - + Previous Zurück - + Tip of the Day Tipp des Tages @@ -3851,166 +5002,176 @@ Sie können das Verhalten jederzeit im "Allgemein"-Tab der Einstellung DlgUpdate - + Current release channel Momentaner Veröffentlichungskanal - + Reinstall Neu installieren - + Cancel Download Download abbrechen - + Open Download Page Downloadseite öffnen - + Check for Client Updates Auf Clientaktualisierungen prüfen - - - - + + + + Error Fehler - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Ihre Version von Cockatrice hat keine SSL-Unterstützung, dadurch können Sie keine Aktualisierungen automatisch herunterladen! Bitte besuchen Sie die Downloadseite, um Cockatrice manuell zu aktualisieren. - + Downloading update: %1 Lade Aktualisierung herunter: %1 - + Checking for updates... Suche nach Aktualisierungen... - + Finished checking for updates Suche nach Aktualisierungen abgeschlossen - + No Update Available Keine Aktualisierung verfügbar - + Cockatrice is up to date! Cockatrice ist aktuell! - + You are already running the latest version available in the chosen release channel. Sie verwenden bereits die aktuellste Version des gewählten Veröffentlichungskanals. - + Current version Momentane Version - + Selected release channel Ausgewählter Veröffentlichungskanal - - + + Update Available Aktualisierung verfügbar - - + + A new version of Cockatrice is available! Eine neue Cockatrice-Version ist verfügbar! - - + + New version Neue Version - - + + Released Veröffentlicht - - + + Changelog Änderungsprotokoll - + Do you want to update now? Möchten Sie jetzt aktualisieren? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. Leider hat der automatische Updater keinen kompatiblen Download gefunden. Eventuell müssen sie manuell eine neue Version herunterladen. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. Bitte prüfen Sie die <a href='%1'>Veröffentlichungsseite</a> auf unserer Github-Seite und laden Sie die Version für Ihr Betriebssystem herunter. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error Aktualisierungsfehler - + An error occurred while checking for updates: Während der Suche nach Aktualisierungen ist ein Fehler aufgetreten: - + An error occurred while downloading an update: Während des Herunterladens der Aktualisierung ist ein Fehler aufgetreten: - + Installing... Installiere... - + Cockatrice is unable to open the installer. Cockatrice kann das Installationsprogramm nicht öffnen. - + Try to update manually by closing Cockatrice and running the installer. Versuchen Sie manuell zu aktualisieren, indem Sie Cockatrice beenden und das Installationsprogramm aufrufen. - + Download location Downloadverzeichnis @@ -4018,17 +5179,17 @@ Eventuell müssen sie manuell eine neue Version herunterladen. DlgViewLog - + Clear log when closing Lösche den Log beim Schließen - + Copy to clipboard In die Zwischenablage kopieren - + Debug Log Fehlerprotokoll @@ -4266,7 +5427,7 @@ Eventuell müssen sie manuell eine neue Version herunterladen. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: Salz: @@ -4274,7 +5435,7 @@ Eventuell müssen sie manuell eine neue Version herunterladen. FilterBuilder - + Type your filter here Gib hier deine Filter ein @@ -4282,45 +5443,88 @@ Eventuell müssen sie manuell eine neue Version herunterladen. FilterDisplayWidget - + Confirm Delete Löschen bestätigen - + Are you sure you want to delete the filter '%1'? Sind Sie sicher, dass Sie den Filter '%1' löschen wollen? - + Delete Failed Löschen fehlgeschlagen - + Failed to delete filter '%1'. Löschen des Filters '%1' fehlgeschlagen. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator Von Spielhost oder Moderator gekickt - + player left the game Spieler hat das Spiel verlassen - + player disconnected from server Spieler hat die Verbindung zum Server getrennt - + reason unknown Grund unbekannt @@ -4328,140 +5532,156 @@ Eventuell müssen sie manuell eine neue Version herunterladen. GameSelector - - - - - - - - - + + + + + + + + + Error Fehler - + Please join the appropriate room first. Bitte betreten Sie erst den entsprechenden Raum. - + Wrong password. Falsches Passwort. - + Spectators are not allowed in this game. In diesem Spiel sind keine Zuschauer zugelassen. - + The game is already full. Das Spiel ist bereits voll. - + The game does not exist any more. Dieses Spiel gibt es nicht mehr. - + This game is only open to registered users. Dieses Spiel kann nur von registrierten Benutzern betreten werden. - + This game is only open to its creator's buddies. Dieses Spiel kann nur von Freunden des Erstellers betreten werden. - + You are being ignored by the creator of this game. Der Ersteller dieses Spiels ignoriert Sie. - + Join Game Spiel beitreten - + Spectate Game Spiel beobachten - + Game Information Spielinformationen - + + Cop&y game link + + + + Join Game as Judge Spiel als Richter beitreten - + Spectate Game as Judge Spiel als Richter zuschauen - + + Join game Spiel beitreten - - Password: - Passwort: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. Bitte betreten Sie zuerst den entsprechenden Raum. - + &Filter games Spiele &filtern - + C&lear filter Filter &zurücksetzen - + C&reate E&rstellen - + &Join &Beitreten - + Join as judge Als Richter beitreten - + J&oin as spectator Als Zuschauer beitreten - + Join as judge spectator Als Richter-Zuschauer beitreten - + Games shown: %1 / %2 Angezeigte Spiele: %1 / %2 - + Games Spiele @@ -4502,105 +5722,105 @@ Eventuell müssen sie manuell eine neue Version herunterladen. GamesModel - + >1 day >1 Tag - + %1%2 hr short age in hours %1%2 h%1%2 h - + new neu - + %1%2 min short age in minutes %1%2 min%1%2 min - + password Passwort - + buddies only nur Freunde - + reg. users only nur reg. Benutzer - + open decklists Offene Decklisten - - + + can chat können chatten - + see hands sehen Hände - + can see hands können Hände sehen - + not allowed nicht erlaubt - + Room Raum - + Age Alter - + Description Beschreibung - - Creator - Ersteller + + Host + - + Type Typ - + Restrictions Bedingungen - + Players Spieler - + Spectators Zuschauer @@ -4608,158 +5828,275 @@ Eventuell müssen sie manuell eine neue Version herunterladen. GeneralSettingsPage - + Reset all paths Alle Pfade zurücksetzen - + All paths have been reset Alle Pfade wurden zurückgesetzt - - - - - - - + + + + English + Deutsch (German) + + + + + + + + + Choose path Pfad auswählen - + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> + + + + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + Language settings - + Language: Sprache: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + Version settings - + Card database - + Startup settings - + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) Pfade (Anpassungen im portablen Modus deaktiviert) - + Paths Pfade - + How to help with translations Wie man beim Übersetzen helfen kann - + Decks directory: Verzeichnis mit Decklisten: - + Filters directory: Filterverzeichnis: - + Replays directory: Verzeichnis mit aufgezeichneten Spielen: - + Pictures directory: Verzeichnis mit Bilddateien: - + Card database: Kartendatenbank: - + Custom database directory: Verzeichnis für benutzerdefinierte Datenbank: - + Token database: Spielsteindatenbank: - + Update channel Aktualisierungskanal - + Check for client updates on startup Nach Aktualisierungen des Klienten beim Start der Anwendung suchen - + Check for card database updates on startup Auf Kartendatenbankaktualisierungen beim Anwendungsstart prüfen - + Don't check Nicht überprüfen - + Prompt for update Zu Aktualisierungen auffordern - + Always update in the background Immer im Hintergrund aktualisieren - + Check for card database updates every Auf Kartendatenbankaktualisierung prüfen alle - + days Tage - + Notify if a feature supported by the server is missing in my client Benachrichtigung wenn ein vom Server unterstütze Funktion in meinem Client fehlt - + Automatically run Oracle when running a new version of Cockatrice Starte Oracle automatisch, wenn eine neue Version von Cockatrice gestartet wird - + Show tips on startup Zeige Tipps beim Start - + Last update check on %1 (%2 days ago) Letzte Aktualisierungsprüfung am %1 (vor %2 Tagen) @@ -4767,47 +6104,47 @@ Eventuell müssen sie manuell eine neue Version herunterladen. GraveyardMenu - + &Graveyard &Friedhof - + &View graveyard &Friedhof ansehen - + &Move graveyard to... & Friedhof nach ... bewegen - + &Top of library &Oben in der Bibliothek - + &Bottom of library &Unten in der Bibliothek - + &All players &Alle Spieler - + &Hand &Hand - + &Exile &Exil - + Reveal random card to... Zufällige Karte vorzeigen für ... @@ -4901,59 +6238,353 @@ Eventuell müssen sie manuell eine neue Version herunterladen. Zufällige Karten an ... vorzeigen + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck Neues Deck erstellen - + Browse Decks Decks durchsuchen - + Browse Card Database Kartendatenbank durchsuchen - + Browse EDHRec EDHRec durchsuchen - + Browse Archidekt Archidekt durchsuchen - + View Replays Wiederholung ansehen - + Quit Verlassen - + Connecting... Verbinden... - + Connect Verbinden - + Play Spielen + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu @@ -5171,273 +6802,294 @@ Eventuell müssen sie manuell eine neue Version herunterladen. MainWindow - - + + Player %1 Spieler %1 - + Load replay Aufgezeichnetes Spiel laden - + About Cockatrice Über Cockatrice - + Version Version - + Cockatrice Webpage Cockatrice Webseite - + Project Manager: Projektmanager: - + Past Project Managers: frühere Projektmanager: - + Developers: Entwickler: - + Our Developers unsere Entwickler - + Help Develop! Hilf bei der Entwicklung! - + Translators: Übersetzer: - + Our Translators Unsere Übersetzer - + Help Translate! Hilf bei der Übersetzung! - + Support: Unterstützung: - + Report an Issue Problem melden - + Troubleshooting Fehlersuche - + F.A.Q. F.A.Q. - - + + Error Fehler - + &Connect... &Verbinden... - + &Disconnect Verbindung &trennen - + Start &local game... &Lokales Spiel starten... - + &Watch replay... &Aufgezeichnetes Spiel abspielen... - + &Full screen &Vollbild - + &Register to server... Auf Server &registrieren... - + &Restore password... Passwort wiede&rherstellen... - + &Settings... &Einstellungen... - + &Exit &Beenden - + A&ctions Aktionen - + &Cockatrice &Cockatrice - + C&ard Database K&artendatenbank - + &Manage sets... Editionen &verwalten... - + Edit custom &tokens... Bearbeite benutzerdefinierte Spiels&teine... - + Open custom image folder Öffne den Ordner für benutzerdefinierte Kartenbilder - + Open custom sets folder Öffne den Ordner für benutzerdefinierte Sets - + Add custom sets/cards Benutzerdefinierte Karten/Sets hinzufügen - + Reload card database Neu laden der Kartendatenbank - + Tabs Reiter - + &Help &Hilfe - + &About Cockatrice &Über Cockatrice - + &Tip of the Day &Tipp des Tages - + Check for Client Updates Auf Clientaktualisierungen prüfen - + Check for Card Updates... Nach Kartenaktualisierungen suchen... - + Check for Card Updates (Automatic) Prüfe auf Kartenaktualisierungen (automatisch) - + Show Status Bar Statuszeile anzeigen - + View &Debug Log Fehlerprotokoll ansehen - + Open Settings Folder Einstellungsordner öffnen - + + Re-run Onboarding Wizard... + + + + Show/Hide Ein-/Ausblenden - + + Sign-in cancelled + + + + New Version Neue Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Herzlichen Glückwunsch zur Aktualisierung auf Cockatrice %1! Oracle wird gestartet um ihre Kartendatenbank zu aktualisieren. - + Cockatrice installed Cockatrice wurde installiert - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Herzlichen Glückwunsch, dass Sie Cockatrice %1 installiert haben! Oracle wird jetzt gestartet und installiert die initiale Kartendatenbank. - + + Are you sure? + Sind Sie sicher? + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database Kartendatenbank - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5446,29 +7098,29 @@ Möchten Sie Ihre Kartendatenbank jetzt aktualisieren? Falls Sie unsicher sind oder Cockatrice das erste Mal nutzen, wählen Sie „Ja“ - - + + Yes Ja - - + + No Nein - + Open settings Einstellungen öffnen - + New sets found Neue Editionen gefunden - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5479,22 +7131,22 @@ Setcode(s): %1 Möchten Sie es/sie aktivieren? - + Yes, always enable - + View sets Editionen ansehen - + Welcome Willkommen - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5503,90 +7155,90 @@ Alle Editionen der Kartendatenbank wurden aktiviert. Lesen Sie mehr über das Ändern der Editionsreihenfolge oder die Deaktivierung bestimmter Editionen im „Editionen verwalten...“ Fenster. - + Information Information - + A card database update is already running. Eine Datenbankaktualisierung wird bereits durchgeführt. - + Unable to run the card database updater: Kartendatenbankaktualisierung nicht ausführbar: - + Card database update running. Kartendatenbankaktualisierung läuft. - + Failed to start. The file might be missing, or permissions might be incorrect. Starten fehlgeschlagen. Die Datei könnte fehlen oder die Berechtigung fehlen. - + The process crashed some time after starting successfully. Der Prozess ist einige Zeit nach erfolgreichem Start abgestürzt. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. Zeitüberschreitung. Der Prozess hat zu lange nicht geantwortet. Zeitüberschreitung bei letzter waitFor...() Funktion. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. Ein Fehler ist beim Schreiben an den Prozess aufgetreten. Beispielsweise könnte der Prozess derzeit nicht ausgeführt werden oder er hat seinen Eingabekanal geschlossen. - + An error occurred when attempting to read from the process. For example, the process may not be running. Ein Fehler ist beim Lesen vom Prozess aufgetreten. Beispielsweise könnte der Prozess derzeit nicht ausgeführt werden. - + Unknown error occurred. Unbekannter Fehler aufgetreten. - + The card database updater exited with an error: %1 Die Kartendatenbankaktualisierung brach mit einem Fehler ab: %1 - - - - - + + + + + Load sets/cards Lade Sets/Karten - + Selected file cannot be found. Die ausgewählte Datei wurde nicht gefunden. - + You can only import XML databases at this time. Im Moment ist es nur möglich XML Datenbanken zu importieren. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Die neuen Sets/Karten wurden erfolgreich hinzugefügt. Cockatrice wird jetzt die Kartendatenbank neu laden. - + Sets/cards failed to import. Set-/Kartenimport fehlgeschlagen. @@ -5791,615 +7443,615 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. MessageLogWidget - + from play vom Spielfeld - + from their graveyard von ihrem Friedhof - + from exile aus dem Exil - + from their hand von ihrer Hand - + the top card of %1's library die oberste Karte von %1s Bibliothek - + the top card of their library die oberste Karte ihrer Bibliothek - + from the top of %1's library oben von %1s Bibliothek - + from the top of their library von ihrer Bibliothek von oben - + the bottom card of %1's library die unterste Karte von %1s Bibliothek - + the bottom card of their library die unterste Karte ihrer Bibliothek - + from the bottom of %1's library von der Unterseite von %1s Bibliothek - + from the bottom of their library von ihrer Bibliothek von unten - + from %1's library aus %1s Bibliothek - + from their library von ihrer Bibliothek - + from sideboard aus dem Sideboard - + from the stack vom Stapel - + from custom zone '%1' von der benutzerdefinierten Zone '%1' - + %1 is now keeping the top card %2 revealed. %1 lässt nun die oberste Karte %2 aufgedeckt. - + %1 is not revealing the top card %2 any longer. %1 lässt die oberste Karte %2 nicht mehr aufgedeckt. - + %1 can now look at top card %2 at any time. %1 kann nun die oberste Karte %2 jederzeit betrachten. - + %1 no longer can look at top card %2 at any time. %1 kann die oberste Karte %2 nicht mehr jederzeit betrachten. - + %1 attaches %2 to %3's %4. %1 legt %2 an %3s %4 an. - + %1 has conceded the game. %1 hat das Spiel aufgegeben. - + %1 has unconceded the game. %1 hat das Spiel doch nicht aufgegeben. - + %1 has restored connection to the game. %1 ist wieder mit dem Spiel verbunden. - + %1 has lost connection to the game. %1 hat die Verbindung zum Spiel verloren. - + %1 points from their %2 to themselves. %1 zeigt von ihrem %2 auf sich selbst. - + %1 points from their %2 to %3. %1 zeigt von ihrem %2 auf %3. - + %1 points from %2's %3 to themselves. %1 zeigt von %2s %3 auf sich selbst. - + %1 points from %2's %3 to %4. %1 zeigt von %2s %3 auf %4. - + %1 points from their %2 to their %3. %1 zeigt von ihrem %2 auf ihren %3. - + %1 points from their %2 to %3's %4. %1 zeigt von ihrem %2 auf %3s %4. - + %1 points from %2's %3 to their own %4. %1 zeigt von %2s %3 auf ihr %4. - + %1 points from %2's %3 to %4's %5. %1 zeigt von %2s %3 auf %4s %5. - + %1 creates a face down token. %1 erzeugt einen umgedrehten Spielstein. - + %1 creates token: %2%3. %1 erstellt Spielstein: %2%3. - + %1 has loaded a deck (%2). %1 hat ein Deck geladen (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 hat ein Deck mit %2 Sideboardkarten geladen (%3). - + %1 destroys %2. %1 zerstört %2. - + a card eine Karte - + %1 gives %2 control over %3. %1 überlässt %2 die Kontrolle über %3. - + %1 puts %2 into play%3 face down. %1 bringt %2%3 verdeckt ins Spiel. - + %1 puts %2 into play%3. %1 bringt %2%3 ins Spiel. - + %1 puts %2%3 into their graveyard face down. %1 legt %2%3 verdeckt in ihren Friedhof - + %1 puts %2%3 into their graveyard. %1 legt %2%3 in ihren Friedhof. - + %1 exiles %2%3 face down. %1 schickt %2%3 verdeckt ins Exil. - + %1 exiles %2%3. %1 schickt %2%3 ins Exil. - + %1 moves %2%3 to their hand. %1 bewegt %2%3 in ihre Hand. - + %1 puts %2%3 into their library. %1 legt %2%3 in ihre Bibliothek. - + %1 puts %2%3 onto the bottom of their library. %1 legt %2%3 unter die Bibliothek. - + %1 puts %2%3 on top of their library. %1 legt %2%3 auf ihre Bibliothek. - + %1 puts %2%3 into their library %4 cards from the top. %1 legt %2%3 in die Bibliothek an %4 Stelle von oben. - + %1 moves %2%3 to sideboard. %1 legt %2%3 in sein Sideboard. - + %1 plays %2%3 face down. %1 bringt %2%3 verdeckt ins Spiel. - + %1 plays %2%3. %1 spielt %2%3 aus. - + %1 moves %2%3 to custom zone '%4' face down. %1 bewegt %2%3 verdeckt zur benutzerdefinierten Zone '%4'. - + %1 moves %2%3 to custom zone '%4'. %1 bewegt %2%3 zur benutzerdefinierten Zone '%4'. - + %1 tries to draw from an empty library %1 versucht von einer leeren Bibliothek zu ziehen - + %1 draws %2 card(s). %1 zieht %2 Karte(n).%1 zieht %2 Karte(n). - + %1 is looking at %2. %1 sieht sich %2 an. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 sieht sich die %4 %3 Karte(n) %2 an.%1 sieht sich die %4 %3 Karte(n) %2 an. - + bottom untersten - + top obersten - + %1 turns %2 face-down. %1 wendet %2 auf die Rückseite. - + %1 turns %2 face-up. %1 wendet %2 auf die Vorderseite. - + The game has been closed. Das Spiel wurde geschlossen. - + The game has started. Das Spiel hat begonnen. - + You are flooding the game. Please wait a couple of seconds. Du überschwemmst das Spiel. Warte bitte einige Sekunden. - + %1 has joined the game. %1 ist dem Spiel beigetreten. - + %1 is now watching the game. %1 schaut nun dem Spiel zu. - + You have been kicked out of the game. Sie wurden aus dem Spiel geworfen. - + %1 has left the game (%2). %1 hat das Spiel verlassen (%2). - + %1 is not watching the game any more (%2). %1 schaut dem Spiel nicht mehr zu (%2). - + %1 is not ready to start the game any more. %1 ist nicht mehr bereit, das Spiel zu starten. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 mischt sein Deck und zieht eine Hand mit %2 Karte(n).%1 mischt sein Deck und zieht eine neue Hand mit %2 Karte(n). - + %1 shuffles their deck and draws a new hand. %1 mischt sein Deck und zieht eine neue Hand. - + You are watching a replay of game #%1. Sie sehen eine Aufzeichnung des Spiels #%1. - + %1 is ready to start the game. %1 ist bereit, das Spiel zu starten. - + cards an unknown amount of cards Karten - + %1 card(s) a card for singular, %1 cards for plural %1 Karte(n)%1 Karte(n) - + %1 lends %2 to %3. %1 verleiht %2 an %3. - + %1 reveals %2 to %3. %1 zeigt %3 %2. - + %1 reveals %2. %1 zeigt %2 offen vor. - + %1 randomly reveals %2%3 to %4. %1 zeigt %4 zufällig %2%3 vor. - + %1 randomly reveals %2%3. %1 zeigt zufällig %2%3 offen vor. - + %1 peeks at face down card #%2. %1 schaut sich die umgedrehte Karte #%2 an. - + %1 peeks at face down card #%2: %3. %1 schaut sich die umgedrehte Karte #%2 an: %3. - + %1 reveals %2%3 to %4. %1 zeigt %4 %2%3 vor. - + %1 reveals %2%3. %1 zeigt %2%3 offen vor. - + %1 reversed turn order, now it's %2. %1 kehrte die Zugreihenfolge um, nun ist es %2. - + reversed umgekehrt - + normal normal - + Heads Kopf - + Tails Zahl - + %1 flipped a coin. It landed as %2. %1 warf eine Münze. Es fiel %2. - + %1 rolls a %2 with a %3-sided die. %1 würfelt eine %2 mit einem %3-seitigen Würfel. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 wirft %2 Münzen. Es ergab %3 mal Kopf und %4 mal Zahl. - + %1 rolls a %2-sided dice %3 times: %4. %1 wirft einen %2-seitigen Würfel %3 mal: %4. - + %1's turn. %1 ist am Zug. - + %1 sets annotation of %2 to %3. %1 versieht %2 mit dem Hinweis %3. - + %1 places %2 %3%4 counter(s) on %5 (now %6). - + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + %1 failed to undo their last draw. - + %1 sets counter %2 to %3 (%4%5). %1 setzt Zähler %2 auf %3 (%4%5). - + %1 sets %2 to not untap normally. %1 setzt %2 auf explizites Enttappen. - + %1 sets %2 to untap normally. %1 setzt %2 auf normales Enttappen. - + %1 removes the PT of %2. %1 entfernt die Kampfwerte von %2. - + %1 changes the PT of %2 from nothing to %4. %1 ändert die Kampfwerte von %2 von nichts auf %4. - + %1 changes the PT of %2 from %3 to %4. %1 ändert die Kampfwerte von %2 von %3 auf %4. - + %1 has locked their sideboard. %1 hat ihr Sideboard gesperrt. - + %1 has unlocked their sideboard. %1 hat ihr Sideboard entsperrt. - + %1 taps their permanents. %1 tappt ihre bleibenden Karten. - + %1 untaps their permanents. %1 enttappt ihre bleibenden Karten. - + %1 taps %2. %1 tappt %2. - + %1 untaps %2. %1 enttappt %2. - + %1 shuffles %2. %1 mischt %2. - + %1 shuffles the bottom %3 cards of %2. %1 mischt die untersten %3 Karten von %2. - + %1 shuffles the top %3 cards of %2. %1 mischt die obersten %3 Karten von %2. - + %1 shuffles cards %3 - %4 of %2. %1 mischt die Karten %3 - %4 von %2. - + %1 unattaches %2. %1 löst %2 ab. - + %1 undoes their last draw. %1 legt die zuletzt gezogene Karte zurück. - + %1 undoes their last draw (%2). %1 legt die zuletzt gezogene Karte zurück (%2) @@ -6407,115 +8059,125 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. MessagesSettingsPage - + Word1 Word2 Word3 Wort1 Wort2 Wort3 - + Add New Message Neue Nachricht hinzufügen - + Edit Message Nachricht bearbeiten - + Remove Message Nachricht entfernen - + Add message Nachricht hinzufügen - - + + Message: Nachricht: - + Edit message Nachricht bearbeiten - + Chat settings Chat Einstellungen - + Custom alert words Benutzerdefinierte Benachrichtigungswörter - + Enable chat mentions Chat Erwähnungen aktivieren - + Enable mention completer Autovervollständigung aktivieren - + In-game message macros Makros für Nachrichten in Spielen - + How to use in-game message macros Anleitung zum Verwenden der Makros für Nachrichten in Spielen - + Ignore chat room messages sent by unregistered users Nachrichten von unregistrierten Benutzern im Chatroom ignorieren - + Ignore private messages sent by unregistered users Private Nachrichten von unregistrierten Benutzern ignorieren - + Ignore private messages sent by non-buddy users - - + + Invert text color Textfarbe invertieren - + Enable desktop notifications for private messages Desktop Benachrichtigungen für private Nachrichten aktivieren - + Enable desktop notification for mentions Desktop Benachrichtigungen für Erwähnungen aktivieren - + Enable room message history on join Nachrichtenverlauf beim Betreten eines Raumes aktivieren - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) (Farbcode in hexadezimal) - + Separate words with a space, alphanumeric characters only Wörter durch Leerzeichen trennen, nur alphanumerische Zeichen @@ -6523,42 +8185,42 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. MoveMenu - + Move to Bewegen nach - + &Top of library in random order &Oben auf der Bibliothek in zufälliger Reihenfolge - + X cards from the top of library... X Karten von oben in der Bibliothek... - + &Bottom of library in random order &Unter die Bibliothek in zufälliger Reihenfolge - + T&able T&isch - + &Hand &Hand - + &Graveyard &Friedhof - + &Exile &Exil @@ -6637,148 +8299,134 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. PaletteEditorDialog - - + + Reset - - - Apply - - - - - - Save && Apply - - - - + ▼ Edit Palette - - + + ▶ Edit Palette - + Palette Editor — %1 - + <b>Palette Editor</b> &nbsp;·&nbsp; %1 - - This theme ships no default palette files - - - - - Replace current colours with the theme author's defaults - - - - - Switch between the light and dark palette files + + + Save - Editing: + Delete this scheme's custom palette and revert to the theme default (or the application palette) - Show or hide the per-role colour grid for manual tweaks + Switch between the light and dark palette files + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + ↺ Revert to theme default - + Discard unsaved edits and restore the last saved palette - - Preview this palette without saving to disk + + Cannot save: this theme has no writable directory - + Write palette-%1.toml and reload the theme - - Cannot save: this theme has no directory on disk - - - - + + Save failed - + + Could not write %1 to: %2 - - - No default found - - - - - No default palette file found for the "%1" scheme. - - PaletteGridWidget - + Active - + Disabled - + Inactive - + Normal interactive state - + Widget is disabled / not interactive - + Window is in background / unfocused + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + Phase @@ -6901,30 +8549,21 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. Ende-des-Zuges-Segment - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - de - - PlayerActions - - - - + + + + grave Friedhof - - - - + + + + exile Exil @@ -7043,16 +8682,272 @@ Cockatrice wird jetzt die Kartendatenbank neu laden. PlayerMenu - + Player "%1" Spieler "%1" - + &Counters &Zähler + + PlaymatCollectionDialog + + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + PrintingDisabledInfoWidget @@ -7076,12 +8971,12 @@ Diese Einstellung bedeutet, dass sie nur den Standarddruck für jede Karte sehen PrintingSelector - + Display Navigation Buttons Navigationstasten anzeigen - + Printing Selector Druckauswahl @@ -7089,25 +8984,65 @@ Diese Einstellung bedeutet, dass sie nur den Standarddruck für jede Karte sehen PrintingSelectorCardOverlayWidget - + Preference Preferenz - + Pin Printing Version festsetzen - + Unpin Printing Version lösen - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards Zugehörige Karten anzeigen + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -7138,28 +9073,28 @@ Diese Einstellung bedeutet, dass sie nur den Standarddruck für jede Karte sehen PrintingSelectorCardSortingWidget - + Alphabetical Alphabetisch - + Preference Präferenz - + Release Date Erscheinungsdatum - - + + Descending Absteigend - + Ascending Aufsteigend @@ -7175,95 +9110,103 @@ Diese Einstellung bedeutet, dass sie nur den Standarddruck für jede Karte sehen PtMenu - + Power / toughness Stärke/Widerstandskraft - + &Increase power &Stärke erhöhen - + &Decrease power &Stärke verringern - + I&ncrease toughness &Widerstandskraft erhöhen - + D&ecrease toughness &Widerstandskraft verringern - + In&crease power and toughness Stärke und Widerstandskraft erhöhen - + Dec&rease power and toughness Stärke und Widerstandskraft verringern - + Increase power and decrease toughness Erhöhe Stärke und verringere Widerstandskraft - + Decrease power and increase toughness Verringere Stärke und erhöhe Widerstandskraft - + Set &power and toughness... Stärke und Widerstandskraft setzen... - + Reset p&ower and toughness Stärke und Widerstandskraft zurücksetzen + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services Dienste - + Hide %1 %1 ausblenden - + Hide Others Andere ausblenden - + Show All Alle anzeigen - + Preferences... Einstellungen... - + Quit %1 Schließe %1 - + About %1 Über %1 @@ -7271,17 +9214,17 @@ Diese Einstellung bedeutet, dass sie nur den Standarddruck für jede Karte sehen QObject - + Cockatrice card database (*.xml) Cockatrice Kartendatenbank (*.xml) - + All files (*.*) Alle Dateien (*.*) - + Cockatrice replays (*.cor) Aufgezeichnete Cockatrice-Spiele (*.cor) @@ -7301,17 +9244,22 @@ Diese Einstellung bedeutet, dass sie nur den Standarddruck für jede Karte sehen Spielsteine - + + Maybeboard + + + + Overwrite Existing File? Existierende Datei überschreiben? - + A .cod version of this deck already exists. Overwrite it? Eine .cod Version dieses Decks existiert bereits. Überschreiben? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7328,7 +9276,7 @@ Sie werden den Set-Manager verwenden müssen, der über Kartendatenbank -> Se Sind sie sicher, dass diese Funktion aktiviert werden soll? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7345,100 +9293,110 @@ Sie können auch den Set-Manager verwenden, um selbstständig die Sortierungsrei Sind sie sicher, dass diese Funktion deaktiviert werden soll? - + Confirm Change Änderung bestätigen + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK OK - + Save Speichern - + Save All Alle speichern - + Open Öffnen - + &Yes Ja - + Yes to &All Ja zu &Allem - + &No &Nein - + N&o to All Nein zu Allem - + Abort Abbrechen - + Retry Erneut versuchen - + Ignore Ignorieren - + Close Schließen - + Cancel Abbrechen - + Discard Verwerfen - + Help Hilfe - + Apply Anwenden - + Reset Zurücksetzen - + Restore Defaults Standard wiederherstellen @@ -7446,121 +9404,189 @@ Sind sie sicher, dass diese Funktion deaktiviert werden soll? QuickSetupPanel - + %1% - + <b>Quick Setup</b> - + Generate all palette roles automatically from a single accent colour - + Accent: - + Primary hue. Used directly for highlights and links. At high intensity it also tints buttons and backgrounds. - + Intensity: - + Subtle - + Full colour - + 0–30 Subtle tint — only highlights and links change hue 30–70 Accented — buttons, tooltips, and borders join in 70–100 Full colour — backgrounds, everything - + 70% - - - Generate ↓ - - - - - Derive all palette roles from the accent colour above. -Fine-tune individual colours in the grid afterwards. - - RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name Name - + ID ID - + Upload time Änderungsdatum + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID ID - + Name Name - + Players Spieler - + Keep Behalten - + Time started Startzeit - + Duration (sec) Länge (Sekunden) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu @@ -7602,37 +9628,37 @@ Fine-tune individual colours in the grid afterwards. RoomSelector - + Rooms Räume - + Joi&n Teil&nehmen - + Room Raum - + Description Beschreibung - + Permissions Berechtigungen - + Players Spieler - + Games Spiele @@ -7640,7 +9666,7 @@ Fine-tune individual colours in the grid afterwards. SayMenu - + S&ay S&agen @@ -7648,32 +9674,32 @@ Fine-tune individual colours in the grid afterwards. SequenceEdit - + Choose an action from the table Wähle eine Aktion aus der Tabelle - + Hit the key/combination of keys you want to set for this action Gebe die für diese Aktion gewünschte Taste/Tastenkombination ein - + Invalid key Ungültige Taste - + Shortcut already in use by: Tastenkürzel bereits in Verwendung durch: - + Clear Löschen - + Restore default Auf Standard zurücksetzen @@ -7706,56 +9732,92 @@ Fine-tune individual colours in the grid afterwards. Veröffentlichung + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts Alle Standard-Tastaturkürzel wiederherstellen - + Do you really want to restore all default shortcuts? Möchten Sie wirklich alle Standard-Tastaturkürzel wiederherstellen? - + Clear all default shortcuts Alle Standard-Tastaturkürzel entfernen - + Do you really want to clear all shortcuts? Möchten Sie wirklich alle Tastaturkürzel entfernen? - + Section: Abschnitt: - + Action: Aktion: - + Shortcut: Tastaturkürzel: - + + Shortcut editor + + + + How to set custom shortcuts Wie werden benutzerdefinierte Tastaturkürzel gesetzt - + Clear all shortcuts Alle Tastaturkürzel entfernen - + Search by shortcut name Suchen nach Name des Tastenkürzels @@ -7811,12 +9873,12 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! SideboardMenu - + &Sideboard &Nebendeck - + &View sideboard &Nebendeck ansehen @@ -7824,27 +9886,27 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! SoundSettingsPage - + Enable &sounds Töne aktivieren - + Current sounds theme: Aktuelles Ton-Theme: - + Test system sound engine Systemsound testen - + Sound settings Toneinstellungen - + Master volume Masterlautstärke @@ -7852,48 +9914,48 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! SpoilerBackgroundUpdater - + Spoilers season has ended Spoiler Saison ist beendet - + Deleting spoiler.xml. Please run Oracle spoiler.xml gelöscht. Bitte führe Oracle aus - - + + Spoilers download failed Spoiler Download fehlgeschlagen - + No internet connection Keine Internetverbindung - + Error Fehler - + Spoilers already up to date Spoiler bereits aktuell - + No new spoilers added Keine neuen Spoiler hinzugefügt - + Spoilers have been updated! Spoiler wurden aktualisiert! - + Last change: Letzte Änderung: @@ -7901,157 +9963,170 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! StableReleaseChannel - + Default Standard - + No reply received from the release update server. Keine Antwort vom Veröffentlichungsaktualisierungsserver erhalten. - + Invalid reply received from the release update server. Ungültige Antwort vom Veröffentlichungsaktualisierungsserver erhalten. - + No reply received from the tag update server. Keine Antwort vom Kennzeichnungsaktualisierungsserver. - + Invalid reply received from the tag update server. Ungültige Antwort vom Kennzeichungsaktualisierungsserver. + + StatsTally + + + Total Power + + + + + Total Toughness + + + StorageSettingsPage - - - + + + Success - + Cached card pictures have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + In-memory (currently loaded) card pictures have been reset. - + Card Picture Loader Caching Method: - + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. - + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. - + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. - + Delete Cached Images - + Delete Saved Images - + Clear In-Memory Images - + Card Picture Loader Cache Method - + Network Cache - + Filesystem - + In-Memory Picture Cache - + Network Cache Size: - + On-disk cache for downloaded pictures - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + In-memory cache for pictures not currently on screen - + Naming scheme: - + Day(s) @@ -8059,17 +10134,33 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! TabAccount - + + + My Reports + + + + Add to Buddy List Zur Freundesliste hinzufügen - + Add to Ignore List Zur Liste ignorierter Benutzer hinzufügen - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account Benutzerkonto @@ -8190,118 +10281,118 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! TabArchidekt - - - + + + Desc. Beschreibung. - - + + AND UND - - + + Require ALL selected colors Benötigt ALLE ausgewählten Farben - - + + Deck name... Deckname... - - + + Owner... Besitzer... - - + + Packages Pakete - - + + Advanced Filters Erweiterte Filter - + Bracket: Kategorie: - - + + Any Beliebig - - + + Contains card... Enthält Karte... - - + + Commander... Kommandant... - - + + Tag... Etikett... - - + + Deck Size Deckgröße - + Cards: Karten: - - + + Asc. Aufsteigend. - + Sort by: Sortieren nach: - + Filter by: Filtern nach: - + Display Settings Anzeigeeinstellungen - - + + Search Suche - - + + Formats Formate @@ -8314,42 +10405,42 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! TabCardArtRules - + Card: - + ProviderId: - + Mode: - + Reason: - + Type a card name... - + Add rule - + Remove rule - + Refresh @@ -8362,52 +10453,52 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! TabDeckEditor - + Card Info Karteninformationen - + Deck Deck - + Filters Filter - + &View Ansicht - + Card Database Kartendatenbank - + Printing Auflage - + Visible Sichtbar - + Floating Schwebend - + Reset layout Darstellung zurücksetzen - + Deck: %1 Deck: %1 @@ -8415,49 +10506,49 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! TabDeckEditorVisual - + Visual Deck: %1 Visuelles Deck: %1 - + &Visual Deck Editor &Visueller Deckeditor - - + + Card Info Karteninformation - - + + Deck Deck - + &View &Ansehen - + Printing Auflage - + Visible Sichtbar - + Floating Schwebend - + Reset layout Anordnung zurücksetzen @@ -8465,22 +10556,22 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! TabDeckEditorVisualTabWidget - + Visual Deck View Visuelle Deckansicht - + Visual Database Display Visuelle Datenbankanzeige - + Deck Analytics Deckanalysen - + Sample Hand Probehand @@ -8488,139 +10579,237 @@ Bitte überprüfen Sie die Verknüpfungseinstellungen! TabDeckStorage - + Local file system Lokales Dateisystem - + Server deck storage Deckablage auf dem Server - - + + Open in deck editor Im Deckeditor öffnen - + Rename deck or folder Deck oder Ordner umbenennen - + Upload deck Deck hochladen - + Download deck Deck herunterladen - - - - + + + + New folder Neuer Ordner - - + + Delete Löschen - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder Deckordner öffnen - + Rename local folder Lokalen Ordner umbenennen - + Rename local file Lokale Datei umbenennen - + New name: Neuer Name: - - - - + + + + + + + Error Fehler - + Rename failed Umbenennen fehlgeschlagen - - + + + Invalid deck file Ungültige Deckdatei - + Enter deck name Decknamen eingeben - + This decklist does not have a name. Please enter a name: Diese Deckliste hat keinen Namen. Bitte geben Sie einen Namen ein: - + Unnamed deck Unbenanntes Deck - + Failed to upload deck to server Hochladen des Decks zum Server fehlgeschlagen - + Delete local file Lokale Datei löschen - + Are you sure you want to delete the selected files? Sind sie sicher, dass die ausgewählten Dateien gelöscht werden sollen? - + Delete remote decks Löschen der online gespeicherten Decks - + Are you sure you want to delete the selected decks? Sind sie sicher, dass sie die ausgewählten Decks löschen wollen? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: Name für den neuen Ordner: - + Deck Storage Deckablage @@ -8628,20 +10817,271 @@ Bitte geben Sie einen Namen ein: TabDeckStorageVisual - + Visual Deck Storage Visuelle Deckablage - + Error Fehler - + Could not open deck at %1 Deck an Stelle %1 konnte nicht geöffnet werden + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -8654,27 +11094,27 @@ Bitte geben Sie einen Namen ein: TabEdhRecMain - + &Cards &Karten - + Top Commanders Beste Commander - + Tags Tags - + Search for a card ... Nach Karte suchen... - + Search Suche @@ -8687,191 +11127,207 @@ Bitte geben Sie einen Namen ein: TabGame - - - + + + Replay Wiederholung - - + + Game Spiel - - + + Player List Spielerliste - - + + + Invite + + + + + Card Info Karteninformationen - - + + Messages Nachrichten - - + + Replay Timeline Zeitleiste der Aufzeichnung - + &Phases &Phasen - + &Game Spi&el - + Next &phase Nächste &Phase - + Next phase with &action Nächste Phase mit &Aktion - + Next &turn Nächster &Zug - + Reverse turn order Kehre Zugreihenfolge um - + &Remove all local arrows &Lokale Pfeile entfernen - + Rotate View Cl&ockwise Ansicht im Uhrzeigesinn drehen - + Rotate View Co&unterclockwise Ansicht gegen den Uhrzeigersinn drehen - + Game &information &Spielinformationen - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede Aufgabe zurückziehen - - - + + + &Concede &Aufgeben - + &Leave game Spiel ver&lassen - + C&lose replay Wiederholung sch&ließen - + &Focus Chat Chat &fokussieren - + &Say: &Sagen: - + Selected cards Ausgewählte Karten - + &View Ansicht - + Visible Sichtbar - + Floating Schwebend - + Reset layout Darstellung zurücksetzen - + Concede Aufgeben - + Are you sure you want to concede this game? Sind Sie sicher, dass Sie das Spiel aufgeben möchten? - + Unconcede Doch nicht aufgeben - + You have already conceded. Do you want to return to this game? Sie haben bereits aufgegeben. Möchten Sie zu diesem Spiel zurückkehren? - + Leave game Spiel verlassen - + Are you sure you want to leave this game? Sind Sie sicher, dass Sie das Spiel verlassen möchten? - + A player has joined game #%1 Ein Spieler ist Spiel #%1 beigetreten - + %1 has joined the game %1 ist dem Spiel beigetreten - + You have been kicked out of the game. Sie wurden aus dem Spiel geworfen. @@ -8887,158 +11343,158 @@ Bitte geben Sie einen Namen ein: TabLog - + Logs Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Zeit;SenderName;SenderIP;Nachricht;ZielID;ZielName - + Room Logs Raumlogs - + Game Logs Spielelogs - + Chat Logs Chatlogs - - + + Error Fehler - + You must select at least one filter. Sie müssen mindestens einen Filter auswählen. - + You have to select a valid number of days to locate. Sie müssen eine gültige Anzahl an zu lokalisierenden Tagen auswählen. - + Username: Benutzername: - + IP Address: IP-Adresse: - + Game Name: Spielname: - + GameID: Spiel-ID: - + Message: Nachricht: - + Main Room Hauptraum - + Game Room Spielraum - + Private Chat Privater Chat - + Past X Days: Die letzten X Tage: - + Today Heute - + Last Hour Letzte Stunde - + Maximum Results: Maximale Ergebnisse: - + At least one filter is required. The more information you put in, the more specific your results will be. Mindestens ein Filter ist notwendig. Je mehr Informationen Sie angeben, desto spezifischer fallen die Ergebnisse aus. - + Get User Logs Benutzerlogs anzeigen - + Clear Filters Filter entfernen - + Filters Filter - + Log Locations Logstandorte - + Date Range Datumsbereich - + Maximum Results Maximale Ergebnisse - - + + Message History Nachrichtenverlauf - + Failed to collect message history information. Das Sammeln von Nachrichtenverlaufsinformationen ist fehlgeschlagen. - + There are no messages for the selected filters. Keine Nachrichten für die gewählten Filter. @@ -9046,219 +11502,559 @@ Je mehr Informationen Sie angeben, desto spezifischer fallen die Ergebnisse aus. TabMessage - + Private &chat Privater &Chat - + &Leave Ver&lassen - + %1 - Private chat %1 - privater Chat - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. Dieser Benutzer ignoriert dich. Er kann deine Nachrichten im Hauptchat nicht sehen und du kannst seinen Spielen nicht beitreten. - + Private message from Private Nachricht von - + %1 has left the server. %1 hat den Server verlassen. - + %1 has joined the server. %1 hat den Server betreten. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system Lokales Dateisystem - + Server replay storage Wiederholungsablage auf dem Server - - + + Watch replay Wiederholung abspielen - + Rename Umbenennen - - + + New folder Neuer Ordner - - + + Delete Löschen - + Open replays folder Wiederholungsorder öffnen - + Download replay Wiederholung herunterladen - + Toggle expiration lock automatische Löschung umschalten - + Get replay share code Wiederholungscode zum Teilen erhalten - - + + Look up replay by share code Wiederholung durch einen Code zum Teilen nachschauen - + Rename local folder Umbenennen des lokalen Ordners - + Rename local file Umbenennen der lokalen Datei - + New name: Neuer Name: - + Error Fehler - + Rename failed Umbenennen fehlgeschlagen - + Name of new folder: Name des neuen Ordners: - + Delete local file Lokale Datei löschen - + Are you sure you want to delete the selected files? Sind sie sicher, dass die ausgewählten Dateien gelöscht werden sollen? - + Are you sure you want to delete the selected replays? Sind sie sicher, dass die ausgewählten Wiederholungen gelöscht werden sollen? - + Failed to get code Erhalten des Codes fehlgeschlagen - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. Entweder unterstützt dieser Server das Teilen von Wiederholungen nicht, oder erlaubt dir das Teilen von Wiederholungen nicht. - - - + + + Failed Fehlgeschlagen - + Could not get replay code Wiederholungs-Code konnte nicht erhalten werden - + Replay Share Code Wiederholungscode zum Teilen - + Others can use this code to add the replay to their list of remote replays: %1 Andere können diesen Code benutzen um diese Wiederholung zu ihrer Liste von Fremd-Wiederholungen hinzuzufügen: %1 - + Copy to clipboard In die Zwischenablage kopieren - + Replay share code Wiederholungscode zum Teilen - + Replay code found Wiederholungscode gefunden - + Replay was added, or you already had access to it. Wiederholung wurde hinzugefügt, oder du hattest bereits Zugang dazu. - + Replay code not found Wiederholungscode nicht gefunden - + Failed to submit code Einreichen des Codes fehlgeschlagen - + Unexpected error Unerwarteter Fehler - + Delete remote replay Wiederholung vom Server löschen @@ -9268,65 +12064,478 @@ Je mehr Informationen Sie angeben, desto spezifischer fallen die Ergebnisse aus. Aufgezeichnete Spiele + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - - Friends - - - - - Online - - - - - Ignored - - - - + &Say: &Sagen: - + Chat Chat - + &Room &Raum - + &Leave room Raum ver&lassen - + &Clear chat Chat leeren - + Chat Settings... Chat Einstellungen - + mentioned you. hat dich erwähnt. - + Click to view Zum Anzeigen klicken. - + You are flooding the chat. Please wait a couple of seconds. Sie überfluten den Chatraum. Bitte warten Sie ein paar Sekunden. @@ -9334,35 +12543,35 @@ Je mehr Informationen Sie angeben, desto spezifischer fallen die Ergebnisse aus. TabServer - + Server Server - - - - + + + + Error Fehler - + Failed to join the server room: it doesn't exist on the server. Serverraumbeitritt fehlgeschlagen: der Raum existiert auf dem Server nicht. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. Der Server denkt Sie seien im Serverraum, allerdings kann Ihr Client diesen nicht anzeigen. Versuchen Sie, Ihren Client neu zu starten. - + You do not have the required permission to join this server room. Sie haben nicht die erforderlichen Berechtigungen, um diesem Serverraum beizutreten. - + Failed to join the server room due to an unknown error: %1. Beitritt des Serverraums aufgrund eines unbekannten Fehlers fehlgeschlagen: %1. @@ -9370,97 +12579,173 @@ Je mehr Informationen Sie angeben, desto spezifischer fallen die Ergebnisse aus. TabSupervisor - + Deck Editor Deckeditor - + Visual Deck Editor Visueller Deckeditor - + EDHRec EDHRec - + Archidekt Archidekt - + Home Zuhause - + &Visual Deck Storage &Visuelle Deckablage - + Visual Database Display Visuelle Datenbankanzeige - + Server Server - + Account Benutzerkonto - + Deck Storage Deckablage - + Game Replays Aufgezeichnete Spiele - + Administration Administration - + Logs Logs - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? Sind Sie sicher? - + There are still open games. Are you sure you want to quit? Es sind noch Spiele offen. Wollen Sie das Programm trotzdem beenden? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view Zum Anzeigen klicken - + Your buddy %1 has signed on! Dein Freund %1 hat sich eingeloggt! - + Unknown Event Unbekanntes Ereignis - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -9471,39 +12756,39 @@ Dies könnte bedeuten, dass eine neue Version von Cockatrice verfügbar ist oder Um Ihren Client zu aktualisieren, navigieren Sie zu Hilfe -> Auf Aktualisierungen prüfen. - + Idle Timeout Inaktivitätszeitüberschreitung - + You are about to be logged out due to inactivity. Sie werden wegen Inaktivität abgemeldet. - + Promotion Beförderung - + You have been promoted. Please log out and back in for changes to take effect. Sie wurden befördert. Bitte loggen Sie sich aus und wieder ein, damit die Änderungen in Kraft treten. - + Warned Gewarnt - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Sie haben eine Warnung erhalten aufgrund von %1. Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie eingeleitet. Sollten Sie Fragen haben, stellen Sie diese bitte über eine private Nachricht an einen Moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) Sie haben die folgende Nachricht vom Server erhalten. @@ -9523,6 +12808,34 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Visuelle Datenbankanzeige + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -9537,6 +12850,65 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Deckanalyse fehlgeschlagen. + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -9608,57 +12980,52 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie UserCardArtSettingsDialog - + Card Art Settings - + Type a card name... - + Card name: - - Card ProviderId: + + Printing: - + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + Left margin (%): - + Right margin (%): - - Vertical offset: - - - - - Zoom: - - - - - Parameters - - - - - Preview - - - - + Remove Banner Card @@ -9666,216 +13033,266 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie UserContextMenu - + User &details Benutzer&details - + Private &chat Privater &Chat - + Show this user's &games Spiele dieses &Benutzers anzeigen - + + View this user's &public decks + + + + Add to &buddy list Zur &Freundesliste hinzufügen - + Remove from &buddy list Von &Freundesliste entfernen - + Add to &ignore list &Ignorieren - + Remove from &ignore list Nicht mehr &ignorieren - + Kick from &game Aus dem &Spiel werfen - + + Report user + + + + Warn user Nutzer warnen - + View user's war&n history Verwar&nungsverlauf des Nutzers ansehen - + Ban from &server Vom &Server bannen - + View user's &ban history &Bannverlauf des Nutzers ansehen - + &Promote user to moderator Nutzer zum Moderator befördern - + Dem&ote user from moderator Moderator degradieren - + Promote user to &judge Nutzer zum Richter befördern - + Demote user from judge Richter degradieren - + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes Administratornotizen ansehen - - + Investigate user + + + + + + Error Fehler - + This user does not exist. Dieser Benutzer existiert nicht. - + You are being ignored by %1 and can't see their games. Du wirst ignoriert von %1 und kannst ihre Spiele nicht sehen. - + Could not get %1's games. Konnte Spiele von %1 nicht erhalten. - + %1's games %1s Spiele - - - + + + Ban History Bannverlauf - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Bannzeit;Moderator;Banndauer;Banngrund;Sichtbarer Grund - + User has never been banned. Nutzer wurde bisher nie gebannt. - + Failed to collect ban information. Sammeln von Informationen über Bans fehlgeschlagen. - - - + + + Warning History Warnungsverlauf - + Warning Time;Moderator;User Name;Reason Warnungszeit;Moderator;Nutzername;Grund - + User has never been warned. Nutzer wurde bisher nie gewarnt. - + Failed to collect warning information. Sammeln von Informationen über Verwarnungen fehlgeschlagen. - + Failed to get admin notes. Administratornotizen konnten nicht abgerufen werden. - - + + Success Erfolgreich - + Successfully promoted user. Nutzer erfolgreich befördert. - + Successfully demoted user. Nutzer erfolgreich degradiert. - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + Kick Player - + Are you sure you want to kick this player from the game? - - - + + + Failed Fehlgeschlagen - + Failed to promote user. Beförderung des Nutzers fehlgeschlagen. - + Failed to demote user. Degradierung des Nutzers fehlgeschlagen. - + Copy hash to clipboard Kopiere Hash in die Zwischenablage - + Remove this user's messages Nachrichten dieses Nutzers entfernen @@ -9913,61 +13330,61 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Administrator - + Moderator Moderator - + Registered user Registrierter Benutzer - - + + Unregistered user Unregistrierter Benutzer - + Judge Richter - + Unknown Unbekannt - + The entered password does not match your account. Das eingegebene Passwort stimmt nicht mit dem Konto überein. - - - - + + + + Information Information - + User information updated. Benutzerinformationen aktualisiert. - - - - - - + + + + + - - - + + + + Error Fehler @@ -9991,283 +13408,292 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Edit Banner Card + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) %n Jahre,%n Jahre, - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format %10%n Tage %20%10%n Tage %20 - + Enter Password Passwort eingeben - + Password verification is required in order to change your email address Passwortverifikation benötigt, um die E-Mail-Adresse zu ändern - - - + + + An error occurred while trying to update your user information. Ein Fehler ist während der Aktualisierung Ihrer Benutzerinformationen aufgetreten. - + The selected card is blacklisted on this server or another error occurred. - + Banner card removed. - + Banner card updated. - + This server does not permit you to update your user informations. Dieser Server erlaubt Ihnen nicht, Ihre Benutzerinformationen zu aktualisieren. - + Password changed. Passwort geändert. - + This server does not permit you to change your password. Dieser Server erlaubt Ihnen nicht, Ihr Passwort zu ändern. - + The new password is too short. Das neue Passwort ist zu kurz. - + The old password is incorrect. Altes Passwort inkorrekt. - + Avatar updated. Avatar gewechselt. - + This server does not permit you to update your avatar. Dieser Server erlaubt Ihnen nicht, Ihren Avatar zu ändern. - + An error occured while trying to update your avatar. - - - This server does not permit you to update your user informations. - An error occured while trying to updater your avatar. - Dieser Server erlaubt Ihnen nicht, Ihre Benutzerinformationen zu aktualisieren. - UserInfoPopup - - + + Games - + Chat - + Open private chat - + Profile - + View user profile - + Show this user's games - + + Invite + + + + + Invite to your game + + + + − Buddy - + Remove from buddy list - + + Buddy - + Add to buddy list - + − Ignore - + Remove from ignore list - + + Ignore - + Add to ignore list - + Ban - + Ban from server - + Warn - + Warn user - + Ban log - + View ban history - + Warn log - + View warning history - + Notes - + View admin notes - + − Mod - + Demote from moderator - + + Mod - + Promote to moderator - + − Judge - + Demote from judge - + + Judge - + Promote to judge - + Join game - + Spectate - - + + Loading games… - + Could not load games. - + No active games. @@ -10275,223 +13701,344 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie UserInterfaceSettingsPage - + General interface settings Allgemeine Bedienung - + &Double-click cards to play them (instead of single-click) Karten durch &Doppelklick ausspielen (statt Einzelklick) - + &Clicking plays all selected cards (instead of just the clicked card) &Anklicken spielt alle ausgewählten Karten aus (anstatt nur der angeklickten Karte) - + &Play all nonlands onto the stack (not the battlefield) by default Alle Nichtländer standardmäßig über den Stapel spielen (anstatt direkt auf das Spielfeld) - + Do not delete &arrows inside of subphases Keine &Pfeile löschen in Unterphasen - + Close card view window when last card is removed Schließe Kartenanzeigefenster, wenn die letzte Karte entfernt wird - + Auto focus search bar when card view window is opened Sucheingabe automatisch in den Fokus nehmen, wenn das Kartenansichtsfenster geöffnet wird - + Annotate card text on tokens Kartentext auf Spielsteinen anzeigen - + Show selection count during drag selection - + Show total selection count - - Show subtype breakdown in selection tally + + + Disabled - + + + Enabled + + + + + + Automatic + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + Use tear-off menus, allowing right click menus to persist on screen Benutze Abreißmenüs, erlaubt Kontextmenüs auf dem Bildschirm zu verbleiben - + Keep game chat focused when clicking in game (Note: disables card view search bar) - + Notifications settings Benachrichtigungseinstellungen - + Enable notifications in taskbar Benachrichtigungen in der Taskleiste aktivieren - + Notify in the taskbar for game events while you are spectating Benachrichtigungen für Spielereignisse auch beim Zuschauen anderer Spiele in der Taskbar anzeigen - + Notify in the taskbar when users in your buddy list connect Benachrichtige in der Taskleiste wenn sich Benutzer aus der Freundesliste anmelden - + Animation settings Animationseinstellungen - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation Animiertes &Tappen/Enttappen - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings Deckeditor/-ablage Einstellungen - + Open deck in new tab by default Decks in neuem Tab öffnen als Standard - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby Visuelle Deckablage in der Spielelobby verwenden - + Use selection animation for Visual Deck Storage Auswahlanimation für Visuellen Deckspeicher verwenden - + When adding a tag in the visual deck storage to a .txt deck: Wenn ein Tag im Visuellen Deckspeicher zu einem .txt Deck hinzugefügt wird: - - do nothing - tue nichts + + Do nothing + - - ask to convert to .cod - frage, ob zu .cod konvertiert werden soll + + Ask to convert to .cod + - - always convert to .cod - immer nach .cod konvertieren + + Always convert to .cod + - + Default deck editor type Standarddeckeditortyp - + Classic Deck Editor Klassischer Deckeditor - + Visual Deck Editor Visueller Deckeditor - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings Wiederholungsoptionen - + Buffer time for backwards skip via shortcut: Pufferzeit für Rückwärtsüberspringen durch Tastenkürzel + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 Mit dem Server verbundene Nutzer: %1 - + Users in this room: %1 Benutzer in diesem Raum: %1 - + Buddies online: %1 / %2 Freunde online: %1 / %2 - + Ignored users online: %1 / %2 Ignorierte Benutzer online: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters Alle Kartenzähler erhöhen - + &Untap all permanents &Enttappe alle bleibenden Karten - + R&oll die... &Würfeln... - + Flip coin - + &Create token... &Spielstein erzeugen... - + C&reate another token Einen weiteren Spielstein erzeugen - + Cr&eate predefined token &Vordefinierten Spielstein erzeugen @@ -10528,22 +14075,22 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... Suchfilter.... - + Save Filter Filter speichern - + Save all currently applied filters to a file Alle derzeit aktiven Filter in einer Datei speichern - + Enter filename... Dateinamen eingeben... @@ -10551,72 +14098,72 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDatabaseDisplayFilterToolbarWidget - + Sort by Sortieren nach - + Filter by Filtern nach - + Save and load filters Filter speichern und laden - + Filter by exact card name Nach exaktem Kartennamen filtern - + Filter by card main-type Nach Kartenhaupttyp filtern - + Filter by card sub-type Nach Kartenuntertyp filtern - + Filter by set Nach Set filtern - + Filter by format legality Nach Formatslegalität filtern - + Save/Load Speichern/Laden - + Name Name - + Main Type Haupttyp - + Sub Type Untertyp - + Sets Editionen - + Formats Formate @@ -10718,7 +14265,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets Filtern nach aktuellsten Sets @@ -10726,19 +14273,19 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDatabaseDisplaySetFilterWidget - + Search sets... Sets durchsuchen... - - + + Mode: Exact Match Modus: Exakte Übereinstimmung - - + + Mode: Includes Modus: Enthält @@ -10784,28 +14331,28 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDatabaseDisplayWidget - + Search by card name (or search expressions) Suche nach Kartennamen (oder Suchausdrücken) - - + + Visual Visuell - + Loading database ... Datenbank wird geladen... - + Clear all filters Alle Filter entfernen - + Table Tisch @@ -10813,43 +14360,43 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDeckDisplayOptionsWidget - + Group by: Gruppieren nach: - + Change how cards are divided into categories/groups. Wie Karten innerhalb ihrer Kategorien/Gruppierungen sortiert werden ändern - + Sort by: Sortieren nach: - + Click and drag to change the sort order within the groups Klicken und ziehen, um die Sortierungsreihenfolge innerhalb von Gruppen zu ändern - + Configure how cards are sorted within their groups Wie Karten innerhalb ihrer Gruppierung sortiert werden einstellen - - + + Toggle Layout: Overlap Umschalten des Layouts: Überlappung - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) Wie Karten innerhalb von Zonen angezeigt werden ändern ( z.B. überlappend oder vollständig sichtbar) - + Toggle Layout: Flat Umschalten des Layouts: Flach @@ -10865,12 +14412,12 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDeckEditorSampleHandWidget - + Draw a new sample hand Eine neue Probehand ziehen - + Sample hand size Probehandgröße @@ -10878,17 +14425,17 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDeckEditorWidget - + Type a card name here for suggestions from the database... Hier einen Kartennahmen eingeben für Vorschläge aus der Datenbank... - + Quick search and add card Schnellsuche und Karte hinzufügen - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter Nach bester Entsprechung in der Datenbank suchen (mit automatischen Vorschlägen) und die bevorzugte Auflage beim Betätigen der Eingabetaste dem Deck hinzufügen @@ -10896,7 +14443,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDeckStorageFolderDisplayWidget - + Deck Storage Deckablage @@ -10904,52 +14451,57 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDeckStorageQuickSettingsWidget - + Show Folders Ordner anzeigen - + Show Tag Filter Tagfilter anzeigen - + Show Color Identity Farbidentität anzeigen - + Show Tags On Deck Previews Tags auf Deckvorschauen anzeigen - + + Show Upload Time + + + + Show Banner Card Selection Option Bannerkartenauswahloption anzeigen - + Draw unused Color Identities Ungenutzte Farbidentitäten ziehen - + Unused Color Identities Opacity Deckkraft ungenutzter Farbidentitäten - + Deck tooltip: Decktooltip: - + None Keine - + Filepath Dateipfad @@ -10957,7 +14509,7 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDeckStorageSearchWidget - + Search by filename (or search expression) Suche nach Dateinamen (oder Suchausdruck) @@ -10965,22 +14517,22 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) Alphabetisch sortieren (Deckname) - + Sort Alphabetically (Filename) Alphabetisch sortieren (Dateiname) - + Sort by Last Modified Nach Änderungsdatum sortieren - + Sort by Last Loaded Nach letztem Laden sortieren @@ -10988,17 +14540,22 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie VisualDeckStorageWidget - + Loading database ... Datenbank wird geladen ... - + Refresh loaded files Geladene Dateien aktualisieren - + + Select decks to share + + + + Visual Deck Storage Settings Einstellungen des Visuellen Deckeditors @@ -11006,177 +14563,202 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie WarningDialog - + Which warning would you like to send? Welche Warnung möchten Sie versenden? - + Redact all messages from this user in all rooms Entferne alle Nachrichten dieses Nutzers in allen Räumen - + &OK &OK - + &Cancel Abbre&chen - + Warn user for misconduct Nutzer aufgrund von Fehlverhalten warnen - - + + Error Fehler - + User name to send a warning to can not be blank, please specify a user to warn. Der zu warnende Nutzername kann nicht leer gelassen werden, bitte geben Sie einen Nutzernamen an. - + Warning to use can not be blank, please select a valid warning to send. Warnungsart kann nicht leer gelassen werden, bitte wählen Sie die zu sendende Warnungsart. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top Ausgewählte Edition an die Spitze verschieben - + Move selected set up nach oben verschieben - + Move selected set down nach unten verschieben - + Move selected set to the bottom Ausgewählte Edition ans Ende verschieben - - Search by set name, code, or type - Suche nach Setname, Code oder Typ - - - + Default order Standardreihenfolge - + Restore original art priority order Stelle die ursprüngliche Reihenfolge für Bilderprioritäten wieder her - + Enable all sets Alle Editionen aktivieren - + Disable all sets Alle Editionen deaktivieren - + Enable selected set(s) Ausgewählte Edition(en) aktivieren - + Disable selected set(s) Ausgewählte Edition(en) deaktivieren - + Deck Editor Deckeditor - + Use CTRL+A to select all sets in the view. Benutzen Sie Strg+A, um alle Sets in der Ansicht auszuwählen. - + Only cards in enabled sets will appear in the card list of the deck editor. Nur Karten aus aktivierten Sets werden in der Kartenliste des Deckeditors angezeigt. - + Image priority is decided in the following order: Bildpriorität wird auf folgende Reihenfolge festgelegt: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki zuerst der BENUTZERDEFINIERTE Ordner (%1), dann die aktivierten Sets aus diesem Dialog (von oben nach unten) - + Include cards rebalanced for Alchemy [requires restart] Karten, die für Alchemy augeglichen wurden, einschließen [erfordert Neustart] - + Card Art Kartenzeichnung - + + Search by set name, code, type, or release date + + + + How to use custom card art Wie benutzerdefinierte Kartenbilder verwendet werden - + Hints Tipps - + Note Notiz - + Sorting by column allows you to find a set while not changing set priority. Nach einer Spalte sortieren erlaubt, eine Edition zu finden, ohne die Editionspriorität zu verändern. - + To enable ordering again, click the column header until this message disappears. Um die Sortierung erneut zu aktivieren, klicken Sie auf den Spaltenkopf bis diese Nachricht verschwindet. - + Use the current sorting as the set priority instead Benutze die momentane Sortierung als Editionspriorität. - + Sorts the set priority using the same column Sortiert die Editionspriorität mit der selben Spalte - + Manage sets Editionen verwalten @@ -11184,93 +14766,85 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie ZoneViewWidget - + Search by card name (or search expressions) Suche nach Kartennamen (oder Suchausdrücken) - + Ungrouped Ungruppiert - + Group by Type Gruppieren nach Kartentyp - + Group by Mana Value Gruppieren nach Manabetrag - + Group by Color Gruppieren nach Farbe - + Unsorted Unsortiert - + Sort by Name Sortieren nach Name - + Sort by Type Sortieren nach Kartentyp - + Sort by Mana Cost Sortieren nach Manakosten - + Sort by Colors Sortieren nach Farben - + Sort by P/T Sortieren nach P/T - + Sort by Set Sortieren nach Auflage - + shuffle when closing beim Schließen mischen - + pile view Stapelansicht - - i18n - - - English - Deutsch (German) - - main - + Connect on startup Verbinde beim Start - + Debug to file Debugge in Datei @@ -12054,6 +15628,16 @@ Bitte unterlassen Sie diese Aktivitäten oder weitere Schritte werden gegen Sie Graveyard Friedhof + + + Report Queue + + + + + Moderation + + diff --git a/cockatrice/translations/cockatrice_el.ts b/cockatrice/translations/cockatrice_el.ts index be66756d9..9e2ec6910 100644 --- a/cockatrice/translations/cockatrice_el.ts +++ b/cockatrice/translations/cockatrice_el.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Ρύθμιση μετρητή... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Ρύθμιση μετρητή - + New value for counter '%1': Νέα τιμή για τον μετρητή '%1' @@ -36,64 +36,113 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. - + There are no cards in your deck to be exported + + AccountSetupPage + + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + AddAnalyticsPanelDialog @@ -105,12 +154,12 @@ Please check that the directory is writable and try again. AdminNotesDialog - + Update Notes - + Admin Notes for %1 @@ -136,218 +185,304 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error Σφάλμα - + Could not create themes directory at '%1'. - + Theme settings Ρυθμίσεις θέματος - + Current theme: Τρέχον θέμα: - + Open themes folder Άνοιγμα φακέλου θεμάτων - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering Απόδοση κάρτας - + Display card names on cards having a picture Εμφανίστε τα ονόματα καρτών σε κάρτες με εικόνα - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Light - + Dark - + + System - + + Show all playmats + + + + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + Active theme palette: - + + Active theme style: + + + + + Qt widget style saved to this theme ("System" lets the application decide) + + + + Edit theme palette - + Home tab settings - + Display card name of background in bottom right - + + Dim the home tab background + + + + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + Styling settings - + Style user list - + Card printings - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Αύξηση της κλίμακας των καρτών με την επιλογή του ποντικιού - + Use rounded card corners - + Card layout - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Διάταξη χειρός - + Display hand horizontally (wastes space) Εμφάνιση χεριού οριζόντια (σπατάλη χώρου) - + Enable left justification Ενεργοποιήστε την αριστερή αιτιολόγηση - + Table grid layout Διάταξη πλέγματος πίνακα - + Invert vertical coordinate Ανατροπή κάθετης συντεταγμένης - + Minimum player count for multi-column layout: Ελάχιστος αριθμός παικτών για διάταξη πολλαπλών στηλών: - + Maximum font size for information displayed on cards: Μέγιστο μέγεθος γραμματοσειράς για πληροφορίες που εμφανίζονται στις κάρτες: @@ -355,12 +490,12 @@ Please check that the directory is writable and try again. ArchidektApiResponseDeckDisplayWidget - + Back to results - + Open Deck in Deck Editor @@ -386,112 +521,112 @@ Please check that the directory is writable and try again. BanDialog - + ban &user name απαγόρευση και όνομα χρήστη - + ban &IP address απαγόρευση & διεύθυνση IP - + ban client I&D απαγόρευση εξυπηρετητή I&D - + Ban type Τύπος απαγόρευσης - + &permanent ban &μόνιμη απαγόρευση - + &temporary ban &προσωρινή απαγόρευση - + &Days: &Μέρες: - + &Hours: &Ώρες: - + &Minutes: &Λεπτά: - + Duration of the ban Διάρκεια της απαγόρευσης - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Παρακαλώ καταχωρίστε τον λόγο της απαγόρευσης. Αυτό αποθηκεύεται μόνο για τους επόπτες και δεν μπορεί να το δει το αποκλεισμένο άτομο. - + Please enter the reason for the ban that will be visible to the banned person. Παρακαλώ καταχωρίστε τον λόγο της απαγόρευσης που θα είναι ορατή στο αποκλεισμένο άτομο. - + Redact all messages from this user in all rooms - + &OK &Εντάξει - + &Cancel &Άκυρο - + Ban user from server Απαγόρευση χρήστη από τον διακομιστή - - - - + + + + Error Σφάλμα - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Για την απαγόρευση πρέπει να επιλέξετε ένα όνομα, μία IP, ένα clientId, ή κάποιο συνδυασμό των τριών. - + You must have a value in the name ban when selecting the name ban checkbox. Πρέπει να έχετε μια τιμή στην απαγόρευση ονόματος όταν επιλέγετε το πλαίσιο ελέγχου απαγόρευσης ονόματος. - + You must have a value in the ip ban when selecting the ip ban checkbox. Πρέπει να έχετε μια τιμή στην απαγόρευση ip όταν επιλέγετε το πλαίσιο ελέγχου απαγόρευσης ip. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Πρέπει να έχετε μια τιμή στην απαγόρευση clientid όταν επιλέγετε το πλαίσιο ελέγχου απαγόρευσης clientid. @@ -499,22 +634,22 @@ This is only saved for moderators and cannot be seen by the banned person. BetaReleaseChannel - + Beta - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the file update server. @@ -522,7 +657,20 @@ This is only saved for moderators and cannot be seen by the banned person. CardArtPreviewWidget - + + Banner preview + + + + + + + + Zoom %1× + + + + No card selected @@ -530,22 +678,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardArtRulesModel - + Card - + ProviderId - + Mode - + Reason @@ -553,65 +701,209 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name Όνομα - + Sets Σετς - + Mana cost Το κόστος του Μάνα - + Card type Είδος κάρτας - + P/T P/T - + Color(s) Χρώμα(τα) + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + CardDatabaseView - + Add to Deck - + Add to Sideboard - + Select Printing - + + Add to Zone + + + + + Create &new zone... + + + + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards @@ -721,22 +1013,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoFrameWidget - + Image - + Description - + Both - + View transformation @@ -744,22 +1036,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -767,27 +1059,27 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoTextWidget - + Name: - + Set: - + Collector Number: - + Related cards: - + Unknown card: @@ -795,129 +1087,129 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Reduce life by power - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -938,7 +1230,7 @@ This is only saved for moderators and cannot be seen by the banned person. CardSizeWidget - + Card Size @@ -1078,6 +1370,24 @@ This is only saved for moderators and cannot be seen by the banned person. + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser @@ -1107,280 +1417,382 @@ This is only saved for moderators and cannot be seen by the banned person. + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + ConnectionController - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - - - - - - - - - - - + + + + + + + + + + - - + + + Error - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + Server timeout - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Information - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -1388,113 +1800,113 @@ To update your client, go to Help -> Check for Updates. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - + Requesting forgotten password to %1 as %2... - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. @@ -1544,7 +1956,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1560,17 +1972,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add card to &maindeck - + Add card to &sideboard @@ -1578,97 +1990,158 @@ To update your client, go to Help -> Check for Updates. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + Format: - + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1676,17 +2149,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1694,114 +2167,119 @@ To update your client, go to Help -> Check for Updates. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + + Share deck... + + + + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1809,7 +2287,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1817,110 +2295,152 @@ To update your client, go to Help -> Check for Updates. DeckEditorSettingsPage - - + + Update Spoilers - + Success Επιτυχία - + Download URLs have been reset. - + Add URL - - + + URL: - - + + Edit URL - + Add New URL - + Remove URL - + Updating... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Reset Download URLs @@ -1928,32 +2448,32 @@ To update your client, go to Help -> Check for Updates. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1961,27 +2481,27 @@ To update your client, go to Help -> Check for Updates. DeckListModel - + Count - + Set - + Number Αριθμός - + Provider ID - + Card Κάρτα @@ -2002,17 +2522,22 @@ To update your client, go to Help -> Check for Updates. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -2020,7 +2545,7 @@ To update your client, go to Help -> Check for Updates. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -2028,62 +2553,62 @@ To update your client, go to Help -> Check for Updates. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2091,94 +2616,109 @@ To update your client, go to Help -> Check for Updates. DeckPreviewWidget - + Banner Card - + Open in deck editor - + + Share deck... + + + + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Rename deck + + + + + Rename file + + + + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -2186,57 +2726,102 @@ To update your client, go to Help -> Check for Updates. DeckStateManager - + Rename deck to "%1" from "%2" - + Updated comments (was %1 chars, now %2 chars) - + Set banner card to %1 (%2) - + + Set playmat to %1 + + + + Tags changed - + Set format to %1 - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + %1 1 × "%2" (%3) - + Added - + Removed @@ -2258,94 +2843,122 @@ To update your client, go to Help -> Check for Updates. DeckViewContainer - + Load deck... Φόρτωση deck... - + Load remote deck... Φόρτωση απομακρυσμένου deck... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start - + Force start - + Sideboard unlocked - + Sideboard locked - - + + Error Σφάλμα - + The selected file could not be loaded. Δεν ήταν δυνατή η φόρτωση του επιλεγμένου αρχείου. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? - + Remember and automatically apply choice in the future @@ -2358,143 +2971,143 @@ This will kick all non-ready players from the game. - + Known Hosts Γνωστοί Hosts - + Delete the currently selected saved server - + Refresh the server list with known public servers - + New Host Νέος Host - + Name: Όνομα: - + &Host: &Host: - + &Port: &Θύρα: - + Player &name: Όνομα &παίχτη: - + P&assword: Κωδικός: - + &Save password &Αποθήκευση Κωδικού - + A&uto connect Α&υτόματη σύνδεση - + Automatically connect to the most recent login when Cockatrice opens Συνδεθείτε αυτόματα στην πιο πρόσφατη σύνδεση όταν ανοίγει το Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage - + Reset Password - + Forgot password? - + &Connect - + Server Διακομιστής - + Login Σύνδεση - + Server Contact - + Connect to Server - + Server URL - + Communication Port - + Unique Server Name - + Connection Warning Προειδοποίηση σύνδεσης - + You need to name your new connection profile. Πρέπει να ονομάσετε το νέο προφίλ σύνδεσης. - + Connect Warning Προειδοποίηση σύνδεσης - + The player name can't be empty. Το όνομα παίκτη δεν μπορεί να είναι άδειο. @@ -2502,122 +3115,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings Θυ&μηθείτε τις ρυθμίσεις - + &Description: &Περιγραφή: - + P&layers: Π&αίχτες: - + General - + Game type Είδος παιχνιδιού - + &Password: &Κωδικός: - + Only &buddies can join Μόνο &φίλοι μπορούν να συμμετάσχουν - + Only &registered users can join Μόνο &εγγεγραμμένοι χρήστες μπορούν να συμμετάσχουν - + Joining restrictions Περιορισμοί συμμετοχής - + &Spectators can watch Οι &θεατές μπορούν να παρακολουθήσουν - + Spectators &need a password to watch Οι θεατές &χρειάζονται κωδικό για να παρακολουθήσουν - + Spectators can &chat Οι θεατές μπορούν να &συζητήσουν - + Spectators can see &hands Οι θεατές μπορούν να δουν τα &χέρια - + Create game as spectator - + Spectators Θεατές - + Starting life total: - + Open decklists in lobby - + Create game as judge - + Game setup options - + &Clear &Καθάρισε - + Create game Δημιουργία παιχνιδιού - + Game information Πληροφορίες παιχνιδιού - + Error Σφάλμα - + Server error. Σφάλμα Διακομιστή. @@ -2625,97 +3238,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Όνομα: - + Token Token - + C&olor: Χ&ρώμα: - + white άσπρο - + blue μπλε - + black μαύρο - + red κόκκινο - + green πράσινο - + multicolor πολύχρωμο - + colorless άχρωμο - + &P/T: &P/T: - + &Annotation: &Σχόλιο: - + &Destroy token when it leaves the table &κατέστρεψε το token όταν φύγει από το τραπέζι - + Create face-down (Only hides name) - + Token data Στοιχεία token - + Show &all tokens Εμφάνισε &όλα τα tokens - + Show tokens from this &deck Εμφάνιση tokens από αυτό το &deck - + Choose token from list Επέλεξε token από τη λίστα - + Create token Δημιουργία token @@ -2723,53 +3336,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + ✖ - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2778,7 +3391,7 @@ This will kick all non-ready players from the game. DlgEditAvatar - + No image chosen. Δεν έχει επιλεγεί εικόνα. @@ -2790,27 +3403,32 @@ To remove your current avatar, confirm without choosing a new image. Για να καταργήσετε το τρέχον avatar, επιβεβαιώστε χωρίς να επιλέξετε μια νέα εικόνα. - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... Εξέτασε... - + Change avatar Αλλαγή avatar - + Open Image Άνοιγμα εικόνας - + Image Files (*.png *.jpg *.bmp) Αρχεία εικόνας (*.png *.jpg *.bmp) - + Invalid image chosen. Επιλέχθηκε μη έγκυρη εικόνα. @@ -2836,38 +3454,38 @@ To remove your current avatar, confirm without choosing a new image. DlgEditPassword - + Old password: Παλιός κωδικός: - + New password: Νέος κωδικός: - + Confirm new password: Επιβεβαιώστε τον καινούριο σας κωδικό: - + Change password Αλλαγή κωδικού - - + + Error Σφάλμα - + Your password is too short. Ο κωδικός σας είναι πολύ μικρός. - + The new passwords don't match. Οι νέοι κωδικοί πρόσβασης δεν ταιριάζουν. @@ -2875,93 +3493,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: &Όνομα: - + C&olor: Χ&ρώμα: - + white άσπρο - + blue μπλε - + black μαύρο - + red κόκκινο - + green πράσινο - + multicolor πολύχρωμο - + colorless άχρωμο - + &P/T: &P/T: - + &Annotation: &Σχόλιο: - + Token data Στοιχεία token - - + + Add token Προσθήκη token - + Remove token Αφαίρεση token - + Edit custom tokens - + Please enter the name of the token: Παρακαλώ εισάγετε το όνομα του token: - + Error Σφάλμα - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. @@ -3039,7 +3657,8 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games that have started + Hide started games + Hide games that have started @@ -3075,8 +3694,9 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - &Creator name: - &Όνομα δημιουργού: + &Host name: + &Creator name: + @@ -3142,52 +3762,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning - + A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. - + &Host: &Host: - + &Port: &Θύρα: - + Player &name: Όνομα &παίχτη: - + Email: Email: - + Reset Password Challenge - + Reset Password Challenge Error - + The email address can't be empty. Η διεύθυνση e-mail δεν μπορεί να είναι άδεια @@ -3195,37 +3815,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. - + &Host: &Host: - + &Port: &Θύρα: - + Player &name: Όνομα &παίχτη: - + Reset Password Request - + Reset Password Error - + The player name can't be empty. Το όνομα παίκτη δεν μπορεί να είναι άδειο. @@ -3233,94 +3853,127 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning - + A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. - + &Host: &Host: - + &Port: &Θύρα: - + Player &name: Όνομα &παίχτη: - + Token: Token: - - + + New Password: Νέος κωδικός: - + Reset Password - + The player name can't be empty. Το όνομα παίκτη δεν μπορεί να είναι άδειο. - - - - + + + + Reset Password Error - + The token can't be empty. - + The new password can't be empty. - + Error Σφάλμα - + Your password is too short. - + The passwords do not match. Οι κωδικοί πρόσβασης δεν ταιριάζουν. + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck @@ -3404,165 +4057,566 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLocalGameOptions - + Players: - + General - + Starting life total: - + Game setup options - + Remember settings - + Local game options + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: &Host: - + &Port: &Θύρα: - + Player &name: Όνομα &παίχτη: - + P&assword: - + Password (again): Κωδικός (ξανά): - + Email: Email: - + Email (again): Διεύθυνση e-mail (ξανά): - + Country: Χώρα: - + Undefined Απροσδιόριστο - + Real name: Πραγματικό όνομα: - + Register to server Εγγραφή σε διακομιστή - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning Προειδοποίηση Εγγραφής - + Your password is too short. Ο κωδικός σας είναι πολύ μικρός. - + Your passwords do not match, please try again. Οι κωδικοί δεν ταιριάζουν, παρακαλώ προσπαθήστε ξανά - + Your email addresses do not match, please try again. - + The player name can't be empty. Το όνομα παίκτη δεν μπορεί να είναι άδειο. + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice @@ -3628,40 +4682,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - Η έκδοση της βάσης δεδομένων των καρτών είναι πολύ παλιά. - -Αυτό μπορεί να προκαλέσει προβλήματα στη φόρτωση πληροφοριών για τις κάρτες ή τις εικόνες - -Συνήθως αυτό φτιάχνεται ξανατρέχοντας το oracle για να επικαιροποιήσετε τη βάση δεδομένων σας. - -Θα θέλατε να αλλάξετε τη ρυθμισμένη θέση της βάσης δεδομένων σας; - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3670,21 +4696,21 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3693,104 +4719,235 @@ Would you like to change your database location setting? - - - + + + Error Σφάλμα - + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings Ρυθμίσεις - + + Search settings... + + + + + OK + + + + General Γενικά - + Appearance Εμφάνιση - + User Interface Διεπαφή Χρήστη - + Storage - + Card Sources - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat - + Sound Ήχος - + Shortcuts Συντομεύσεις + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3798,17 +4955,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next - + Previous - + Tip of the Day @@ -3816,164 +4973,174 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel Τρέχον κανάλι έκδοσης - + Reinstall Επανεγκατάσταση - + Cancel Download Ακύρωση Κατεβάσματος - + Open Download Page Άνοιγμα Σελίδας Κατεβάσματος - + Check for Client Updates - - - - + + + + Error Σφάλμα - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - + Downloading update: %1 - + Checking for updates... - + Finished checking for updates Ο έλεγχος για ενημερώσεις ολοκληρώθηκε - + No Update Available Καμία Διαθέσιμη Ενημέρωση - + Cockatrice is up to date! Το Cockatrice είναι πλήρως ενημερωμένο! - + You are already running the latest version available in the chosen release channel. Ήδη τρέχετε την πιό πρόσφατη έκδοση διαθέσιμη σε αυτό το κανάλι. - + Current version Τρέχουσα έκδοση - + Selected release channel Επιλεγμένο κανάλι έκδοσης - - + + Update Available Υπάρχει Διαθέσιμη Ενημέρωση - - + + A new version of Cockatrice is available! Μία νέα έκδοση του Cockatrice είναι διαθέσιμη! - - + + New version Νέα έκδοση - - + + Released - - + + Changelog Αρχείο Αλλαγών - + Do you want to update now? Θέλετε να ενημερώσετε τώρα; - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Installing... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -3981,17 +5148,17 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing - + Copy to clipboard - + Debug Log @@ -4229,7 +5396,7 @@ You may have to manually download the new version. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -4237,7 +5404,7 @@ You may have to manually download the new version. FilterBuilder - + Type your filter here @@ -4245,45 +5412,88 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -4291,140 +5501,156 @@ You may have to manually download the new version. GameSelector - - - - - - - - - + + + + + + + + + Error Σφάλμα - + Please join the appropriate room first. - + Wrong password. Λάθος κωδικός. - + Spectators are not allowed in this game. - + The game is already full. - + The game does not exist any more. - + This game is only open to registered users. - + This game is only open to its creator's buddies. - + You are being ignored by the creator of this game. - + Join Game - + Spectate Game - + Game Information - + + Cop&y game link + + + + Join Game as Judge - + Spectate Game as Judge - + + Join game - - Password: - Κωδικός: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. - + &Filter games - + C&lear filter Καθάρισμα Φίλτρου - + C&reate Δημιουργία - + &Join Είσοδος - + Join as judge - + J&oin as spectator Είσοδος ως θεατής - + Join as judge spectator - + Games shown: %1 / %2 - + Games @@ -4465,105 +5691,105 @@ You may have to manually download the new version. GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password κωδικός - + buddies only μόνο φίλοι - + reg. users only μόνο εγγεγραμμένοι χρήστες - + open decklists - - + + can chat μπορούν να συνομιλήσουν - + see hands βλέπουν τα χέρια - + can see hands μπορούν να δούν τα χέρια - + not allowed δεν επιτρέπεται - + Room Δωμάτιο - + Age Ηλικία - + Description Περιγραφή - - Creator - Δημιουργός + + Host + - + Type Τύπος - + Restrictions Περιορισμοί - + Players Παίκτες - + Spectators Θεατές @@ -4571,158 +5797,275 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + English + Ελληνικά (Greek) + + + + + + + + + Choose path Επιλέξτε θέση - + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> + + + + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + Language settings - + Language: Γλώσσα: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + Version settings - + Card database - + Startup settings - + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) - + Paths - + How to help with translations - + Decks directory: Φάκελος deck: - + Filters directory: - + Replays directory: Φάκελος replay: - + Pictures directory: Φάκελος εικόνων: - + Card database: Βάση δεδομένων καρτών - + Custom database directory: - + Token database: Βάση δεδομένων token: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup - + Last update check on %1 (%2 days ago) @@ -4730,47 +6073,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4864,59 +6207,353 @@ You may have to manually download the new version. + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu @@ -5134,410 +6771,431 @@ You may have to manually download the new version. MainWindow - - + + Player %1 - + Load replay - + About Cockatrice - + Version Έκδοση - + Cockatrice Webpage - + Project Manager: - + Past Project Managers: - + Developers: - + Our Developers - + Help Develop! - + Translators: - + Our Translators Οι μεταφραστές μας - + Help Translate! - + Support: Υποστήριξη: - + Report an Issue Αναφορά Προβλήματος - + Troubleshooting Αντιμετώπιση Προβλημάτων - + F.A.Q. Συχνές Ερωτήσεις - - + + Error Σφάλμα - + &Connect... Σύνδεση... - + &Disconnect Αποσύνδεση - + Start &local game... Έναρξη τοπικού παιχνιδιού... - + &Watch replay... - + &Full screen Πλήρης οθόνη - + &Register to server... Εγγραφή σε σέρβερ... - + &Restore password... - + &Settings... Ρυθμίσεις... - + &Exit Έξοδος - + A&ctions - + &Cockatrice &Cockatrice - + C&ard Database Βάση δεδομένων καρτών - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + Reload card database - + Tabs - + &Help Βοήθεια - + &About Cockatrice Σχετικά με το Cockatrice - + &Tip of the Day - + Check for Client Updates Έλεγχος για ενημερώσεις - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + + Re-run Onboarding Wizard... + + + + Show/Hide - + + Sign-in cancelled + + + + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + + Are you sure? + + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database Βάση δεδομένων καρτών - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes Ναι - - + + No Όχι - + Open settings Άνοιγμα ρυθμίσεων - + New sets found Βρέθηκαν νέα σετ - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + Yes, always enable - + View sets Εμφάνιση σετ - + Welcome Καλωσήρθατε - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - + Information Πληροφορίες - + A card database update is already running. - + Unable to run the card database updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - - - - - + + + + + Load sets/cards Φόρτωση σετ/καρτών - + Selected file cannot be found. Το επιλεγμένο αρχείο δε βρέθηκε. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. @@ -5742,615 +7400,615 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play από το παιχνίδι - + from their graveyard - + from exile από την εξορία - + from their hand - + the top card of %1's library την πρώτη κάρτα της βιβλιοθήκης του/της %1 - + the top card of their library - + from the top of %1's library από την κορυφή της βιβλιοθήκης του/της %1 - + from the top of their library - + the bottom card of %1's library την τελευταία κάρτα της βιβλιοθήκης του/της %1 - + the bottom card of their library - + from the bottom of %1's library από το τέλος της βιβλιοθήκης του/της %1 - + from the bottom of their library - + from %1's library από τη βιβλιοθήκη του/της %1 - + from their library - + from sideboard από το sideboard - + from the stack από το stack - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. - + %1 is not revealing the top card %2 any longer. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. - + %1 has conceded the game. - + %1 has unconceded the game. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. - + %1 has loaded a deck (%2). Ο/Η %1 φόρτωσε ένα deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). Ο/Η %1 φόρτωσε ένα deck με %2 κάτρες στο sideboard (%3). - + %1 destroys %2. - + a card μία κάρτα - + %1 gives %2 control over %3. ο/η %1 δίνει στον/στην %2 τον έλεγχο του %3 . - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. ο/η %1 βάζει στο παιχνίδι το %2%3. - + %1 puts %2%3 into their graveyard face down. - + %1 puts %2%3 into their graveyard. - + %1 exiles %2%3 face down. - + %1 exiles %2%3. ο/η %1 εξορίζει το %2%3. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. - + %1 plays %2%3 face down. - + %1 plays %2%3. - + %1 moves %2%3 to custom zone '%4' face down. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. Το παιχνίδι έχει κλείσει - + The game has started. Το παιχνίδι ξεκίνησε. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. - + %1 is now watching the game. Ο/Η %1 παρακολουθεί το παιχνίδι. - + You have been kicked out of the game. Σας απέβαλαν από το παιχνίδι. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. - + %1 sets annotation of %2 to %3. - + %1 places %2 %3%4 counter(s) on %5 (now %6). - + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + %1 failed to undo their last draw. - + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. - + %1 untaps their permanents. - + %1 taps %2. - + %1 untaps %2. - + %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -6358,115 +8016,125 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message - - + + Message: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - + Ignore private messages sent by non-buddy users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -6474,42 +8142,42 @@ Cockatrice will now reload the card database. MoveMenu - + Move to - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + T&able - + &Hand - + &Graveyard - + &Exile @@ -6588,148 +8256,134 @@ Cockatrice will now reload the card database. PaletteEditorDialog - - + + Reset - - - Apply - - - - - - Save && Apply - - - - + ▼ Edit Palette - - + + ▶ Edit Palette - + Palette Editor — %1 - + <b>Palette Editor</b> &nbsp;·&nbsp; %1 - - This theme ships no default palette files - - - - - Replace current colours with the theme author's defaults - - - - - Switch between the light and dark palette files + + + Save - Editing: + Delete this scheme's custom palette and revert to the theme default (or the application palette) - Show or hide the per-role colour grid for manual tweaks + Switch between the light and dark palette files + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + ↺ Revert to theme default - + Discard unsaved edits and restore the last saved palette - - Preview this palette without saving to disk + + Cannot save: this theme has no writable directory - + Write palette-%1.toml and reload the theme - - Cannot save: this theme has no directory on disk - - - - + + Save failed - + + Could not write %1 to: %2 - - - No default found - - - - - No default palette file found for the "%1" scheme. - - PaletteGridWidget - + Active - + Disabled - + Inactive - + Normal interactive state - + Widget is disabled / not interactive - + Window is in background / unfocused + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + Phase @@ -6852,30 +8506,21 @@ Cockatrice will now reload the card database. - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - PlayerActions - - - - + + + + grave - - - - + + + + exile @@ -6994,16 +8639,272 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters + + PlaymatCollectionDialog + + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + PrintingDisabledInfoWidget @@ -7024,12 +8925,12 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelector - + Display Navigation Buttons - + Printing Selector @@ -7037,25 +8938,65 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -7086,28 +9027,28 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -7123,95 +9064,103 @@ This setting means you'll only see the default printing for each card, instead o PtMenu - + Power / toughness - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -7219,17 +9168,17 @@ This setting means you'll only see the default printing for each card, instead o QObject - + Cockatrice card database (*.xml) - + All files (*.*) - + Cockatrice replays (*.cor) @@ -7249,17 +9198,22 @@ This setting means you'll only see the default printing for each card, instead o - + + Maybeboard + + + + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7270,7 +9224,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7281,100 +9235,110 @@ Are you sure you would like to disable this feature? - + Confirm Change + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -7382,121 +9346,189 @@ Are you sure you would like to disable this feature? QuickSetupPanel - + %1% - + <b>Quick Setup</b> - + Generate all palette roles automatically from a single accent colour - + Accent: - + Primary hue. Used directly for highlights and links. At high intensity it also tints buttons and backgrounds. - + Intensity: - + Subtle - + Full colour - + 0–30 Subtle tint — only highlights and links change hue 30–70 Accented — buttons, tooltips, and borders join in 70–100 Full colour — backgrounds, everything - + 70% - - - Generate ↓ - - - - - Derive all palette roles from the accent colour above. -Fine-tune individual colours in the grid afterwards. - - RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name - + ID - + Upload time + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID - + Name - + Players - + Keep - + Time started - + Duration (sec) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu @@ -7538,37 +9570,37 @@ Fine-tune individual colours in the grid afterwards. RoomSelector - + Rooms - + Joi&n - + Room - + Description - + Permissions - + Players - + Games @@ -7576,7 +9608,7 @@ Fine-tune individual colours in the grid afterwards. SayMenu - + S&ay @@ -7584,32 +9616,32 @@ Fine-tune individual colours in the grid afterwards. SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - + Invalid key - + Shortcut already in use by: - + Clear - + Restore default @@ -7642,56 +9674,92 @@ Fine-tune individual colours in the grid afterwards. + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + + Shortcut editor + + + + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -7745,12 +9813,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -7758,27 +9826,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -7786,48 +9854,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error Σφάλμα - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -7835,157 +9903,170 @@ Please check your shortcut settings! StableReleaseChannel - + Default - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. + + StatsTally + + + Total Power + + + + + Total Toughness + + + StorageSettingsPage - - - + + + Success - + Cached card pictures have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + In-memory (currently loaded) card pictures have been reset. - + Card Picture Loader Caching Method: - + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. - + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. - + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. - + Delete Cached Images - + Delete Saved Images - + Clear In-Memory Images - + Card Picture Loader Cache Method - + Network Cache - + Filesystem - + In-Memory Picture Cache - + Network Cache Size: - + On-disk cache for downloaded pictures - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + In-memory cache for pictures not currently on screen - + Naming scheme: - + Day(s) @@ -7993,17 +10074,33 @@ Please check your shortcut settings! TabAccount - + + + My Reports + + + + Add to Buddy List - + Add to Ignore List - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account @@ -8124,118 +10221,118 @@ Please check your shortcut settings! TabArchidekt - - - + + + Desc. - - + + AND - - + + Require ALL selected colors - - + + Deck name... - - + + Owner... - - + + Packages - - + + Advanced Filters - + Bracket: - - + + Any - - + + Contains card... - - + + Commander... - - + + Tag... - - + + Deck Size - + Cards: - - + + Asc. - + Sort by: - + Filter by: - + Display Settings - - + + Search - - + + Formats @@ -8248,42 +10345,42 @@ Please check your shortcut settings! TabCardArtRules - + Card: - + ProviderId: - + Mode: - + Reason: - + Type a card name... - + Add rule - + Remove rule - + Refresh @@ -8296,52 +10393,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info - + Deck - + Filters - + &View - + Card Database - + Printing - + Visible - + Floating - + Reset layout - + Deck: %1 @@ -8349,49 +10446,49 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - + &View - + Printing - + Visible - + Floating - + Reset layout @@ -8399,22 +10496,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -8422,138 +10519,236 @@ Please check your shortcut settings! TabDeckStorage - + Local file system - + Server deck storage - - + + Open in deck editor - + Rename deck or folder - + Upload deck - + Download deck - - - - + + + + New folder - - + + Delete - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + + + + Error Σφάλμα - + Rename failed - - + + + Invalid deck file - + Enter deck name - + This decklist does not have a name. Please enter a name: - + Unnamed deck - + Failed to upload deck to server - + Delete local file - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: - + Deck Storage @@ -8561,20 +10756,271 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -8587,27 +11033,27 @@ Please enter a name: TabEdhRecMain - + &Cards - + Top Commanders - + Tags - + Search for a card ... - + Search @@ -8620,191 +11066,207 @@ Please enter a name: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + + Invite + + + + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases - + &Game - + Next &phase - + Next phase with &action - + Next &turn - + Reverse turn order - + &Remove all local arrows - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede - - - + + + &Concede - + &Leave game - + C&lose replay - + &Focus Chat - + &Say: - + Selected cards - + &View - + Visible - + Floating - + Reset layout - + Concede - + Are you sure you want to concede this game? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game - + Are you sure you want to leave this game? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. @@ -8820,157 +11282,157 @@ Please enter a name: TabLog - + Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error Σφάλμα - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: - + IP Address: - + Game Name: - + GameID: - + Message: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -8978,218 +11440,558 @@ The more information you put in, the more specific your results will be. TabMessage - + Private &chat - + &Leave - + %1 - Private chat - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from - + %1 has left the server. - + %1 has joined the server. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system - + Server replay storage - - + + Watch replay - + Rename - - + + New folder - - + + Delete - + Open replays folder - + Download replay - + Toggle expiration lock - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay @@ -9199,65 +12001,478 @@ The more information you put in, the more specific your results will be. + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - - Friends - - - - - Online - - - - - Ignored - - - - + &Say: - + Chat - + &Room - + &Leave room - + &Clear chat - + Chat Settings... - + mentioned you. - + Click to view - + You are flooding the chat. Please wait a couple of seconds. @@ -9265,35 +12480,35 @@ The more information you put in, the more specific your results will be. TabServer - + Server - - - - + + + + Error Σφάλμα - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -9301,97 +12516,173 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - + Archidekt - + Home - + &Visual Deck Storage - + Visual Database Display - + Server - + Account - + Deck Storage - + Game Replays - + Administration - + Logs - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? - + There are still open games. Are you sure you want to quit? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -9399,38 +12690,38 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) @@ -9449,6 +12740,34 @@ Please refrain from engaging in this activity or further actions may be taken ag + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -9463,6 +12782,65 @@ Please refrain from engaging in this activity or further actions may be taken ag + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -9532,57 +12910,52 @@ Please refrain from engaging in this activity or further actions may be taken ag UserCardArtSettingsDialog - + Card Art Settings - + Type a card name... - + Card name: - - Card ProviderId: + + Printing: - + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + Left margin (%): - + Right margin (%): - - Vertical offset: - - - - - Zoom: - - - - - Parameters - - - - - Preview - - - - + Remove Banner Card @@ -9590,216 +12963,266 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details - + Private &chat - + Show this user's &games - - - Add to &buddy list - - - - - Remove from &buddy list - - - - - Add to &ignore list - - - - - Remove from &ignore list - - - - - Kick from &game - - - - - Warn user - - - View user's war&n history + View this user's &public decks - Ban from &server + Add to &buddy list - View user's &ban history + Remove from &buddy list - &Promote user to moderator + Add to &ignore list - Dem&ote user from moderator + Remove from &ignore list - Promote user to &judge + Kick from &game - Demote user from judge + Report user + Warn user + + + + + View user's war&n history + + + + + Ban from &server + + + + + View user's &ban history + + + + + &Promote user to moderator + + + + + Dem&ote user from moderator + + + + + Promote user to &judge + + + + + Demote user from judge + + + + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes - - + Investigate user + + + + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - + Failed to get admin notes. - - + + Success - + Successfully promoted user. - + Successfully demoted user. - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + Kick Player - + Are you sure you want to kick this player from the game? - - - + + + Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -9837,61 +13260,61 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Moderator - + Registered user - - + + Unregistered user - + Judge - + Unknown - + The entered password does not match your account. - - - - + + + + Information - + User information updated. - - - - - - + + + + + - - - + + + + Error Σφάλμα @@ -9915,283 +13338,292 @@ Please refrain from engaging in this activity or further actions may be taken ag Edit Banner Card + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password Εισάγετε κωδικό - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + The selected card is blacklisted on this server or another error occurred. - + Banner card removed. - + Banner card updated. - + This server does not permit you to update your user informations. - - - Password changed. - - - This server does not permit you to change your password. + Password changed. - The new password is too short. + This server does not permit you to change your password. + The new password is too short. + + + + The old password is incorrect. - + Avatar updated. - + This server does not permit you to update your avatar. - + An error occured while trying to update your avatar. - - - This server does not permit you to update your user informations. - An error occured while trying to updater your avatar. - - UserInfoPopup - - + + Games - + Chat - + Open private chat - + Profile - + View user profile - + Show this user's games - + + Invite + + + + + Invite to your game + + + + − Buddy - + Remove from buddy list - + + Buddy - + Add to buddy list - + − Ignore - + Remove from ignore list - + + Ignore - + Add to ignore list - + Ban - + Ban from server - + Warn - + Warn user - + Ban log - + View ban history - + Warn log - + View warning history - + Notes - + View admin notes - + − Mod - + Demote from moderator - + + Mod - + Promote to moderator - + − Judge - + Demote from judge - + + Judge - + Promote to judge - + Join game - + Spectate - - + + Loading games… - + Could not load games. - + No active games. @@ -10199,223 +13631,344 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings - + &Double-click cards to play them (instead of single-click) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens - + Show selection count during drag selection - + Show total selection count - - Show subtype breakdown in selection tally + + + Disabled - - Use tear-off menus, allowing right click menus to persist on screen + + + Enabled - - Keep game chat focused when clicking in game (Note: disables card view search bar) - - - - - Notifications settings - - - - - Enable notifications in taskbar - - - - - Notify in the taskbar for game events while you are spectating + + + Automatic - Notify in the taskbar when users in your buddy list connect - - - - - Animation settings + CommanderSpellbook bracket names + Official Commander bracket names (approximate) + + + + + Use tear-off menus, allowing right click menus to persist on screen + + + + + Keep game chat focused when clicking in game (Note: disables card view search bar) + + + + + Notifications settings + + + + + Enable notifications in taskbar + + + + + Notify in the taskbar for game events while you are spectating + + + + + Notify in the taskbar when users in your buddy list connect + + + + + Animation settings + + + + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings - + Open deck in new tab by default - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - - do nothing + + Do nothing - - ask to convert to .cod + + Ask to convert to .cod - - always convert to .cod + + Always convert to .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings - + Buffer time for backwards skip via shortcut: + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + Flip coin - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -10452,22 +14005,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... - + Save Filter - + Save all currently applied filters to a file - + Enter filename... @@ -10475,72 +14028,72 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterToolbarWidget - + Sort by - + Filter by - + Save and load filters - + Filter by exact card name - + Filter by card main-type - + Filter by card sub-type - + Filter by set - + Filter by format legality - + Save/Load - + Name - + Main Type - + Sub Type - + Sets - + Formats @@ -10642,7 +14195,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -10650,19 +14203,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -10708,28 +14261,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - - + + Visual - + Loading database ... - + Clear all filters - + Table @@ -10737,43 +14290,43 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat @@ -10789,12 +14342,12 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -10802,17 +14355,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -10820,7 +14373,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -10828,52 +14381,57 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + Show Color Identity - + Show Tags On Deck Previews - + + Show Upload Time + + + + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -10881,7 +14439,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -10889,22 +14447,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -10912,17 +14470,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + + Select decks to share + + + + Visual Deck Storage Settings @@ -10930,177 +14493,202 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Warn user for misconduct - - + + Error Σφάλμα - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - - Search by set name, code, or type - - - - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - - How to use custom card art + + Search by set name, code, type, or release date + How to use custom card art + + + + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -11108,93 +14696,85 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing - + pile view - - i18n - - - English - Ελληνικά (Greek) - - main - + Connect on startup - + Debug to file @@ -11978,6 +15558,16 @@ Please refrain from engaging in this activity or further actions may be taken ag Graveyard + + + Report Queue + + + + + Moderation + + diff --git a/cockatrice/translations/cockatrice_en@pirate.ts b/cockatrice/translations/cockatrice_en@pirate.ts index 40aa82530..64663db61 100644 --- a/cockatrice/translations/cockatrice_en@pirate.ts +++ b/cockatrice/translations/cockatrice_en@pirate.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Set piece o' eight... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Set piece o' eight - + New value for counter '%1': Freshly formed value for piece o' eight '%1': @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,79 +36,130 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. - + There are no cards in your deck to be exported + + + AccountSetupPage - - No deck was selected to be exported. + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + + + AddAnalyticsPanelDialog + + + Add Analytics Panel AdminNotesDialog - + Update Notes - + Admin Notes for %1 @@ -116,202 +167,339 @@ Please check that the directory is writable and try again. AllZonesCardAmountWidget - + Mainboard - + Sideboard + + + Tokens + + AppearanceSettingsPage - + seconds - + Error - + Could not create themes directory at '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - - Confirm Change - - - - + Theme settings Theme settin's - + Current theme: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings - + Show keyboard shortcuts in right-click menus - + + Show game filter toolbar above list in room tab + + + + Card rendering C-arrr-d Scribblin' - + Display card names on cards having a picture - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + + Light + + + + + Dark + + + + + + System + + + + + Show all playmats + + + + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + + Active theme palette: + + + + + Active theme style: + + + + + Qt widget style saved to this theme ("System" lets the application decide) + + + + + Edit theme palette + + + + + Home tab settings + + + + + Display card name of background in bottom right + + + + + Dim the home tab background + + + + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + + Styling settings + + + + + Style user list + + + + + Card printings + + + + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over - + Use rounded card corners - + + Card layout + + + + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout - + Display hand horizontally (wastes space) - + Enable left justification - + Table grid layout - + Invert vertical coordinate - + Minimum player count for multi-column layout: - + Maximum font size for information displayed on cards: + + ArchidektApiResponseDeckDisplayWidget + + + Back to results + + + + + Open Deck in Deck Editor + + + BackgroundSources @@ -333,112 +521,112 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name Curse da landlubber's name - + ban &IP address Curse da landlubbers port o call - + ban client I&D Curse da landlubber's Jolly Roger - + Ban type Curse type - + &permanent ban - + &temporary ban - + &Days: - + &Hours: - + &Minutes: - + Duration of the ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Me hearty, enter t' reason for t' ban. This be only saved for captains and cannot be seen by t' brutally banned scallywag. - + Please enter the reason for the ban that will be visible to the banned person. Me hearty, enter t' reason for t' ban that be visible to t' brutally banned scallywag. - + Redact all messages from this user in all rooms - + &OK Aye - + &Cancel Belay that - + Ban user from server Banish the scurvy dog from the ship! - - - - + + + + Error Cap'n? Thar be a problem - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. - + You must have a value in the name ban when selecting the name ban checkbox. - + You must have a value in the ip ban when selecting the ip ban checkbox. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. @@ -446,152 +634,378 @@ This be only saved for captains and cannot be seen by t' brutally banned sc BetaReleaseChannel - + Beta - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the file update server. + + CardArtPreviewWidget + + + Banner preview + + + + + + + + Zoom %1× + + + + + No card selected + + + + + CardArtRulesModel + + + Card + + + + + ProviderId + + + + + Mode + + + + + Reason + + + CardDatabaseModel - + Name Name - + Sets Fleets - + Mana cost Magic tax - + Card type Type 'o magic - + P/T P/T - + Color(s) Colour(z) + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + + + CardDatabaseView + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Add to Zone + + + + + Create &new zone... + + + + + Show on EDHRec (Commander) + + + + + Show on EDHRec (Card) + + + + + Show Related cards + + + CardFilter - + AND Logical conjunction operator used in card filter AND - + OR Logical disjunction operator used in card filter OR - + AND NOT Negated logical conjunction operator used in card filter AND NOT - + OR NOT Negated logical disjunction operator used in card filter OR NOT - + Name - + + Name (Exact) + + + + Type - + Color - + Text - + Set - + Mana Cost - + Mana Value - + Rarity - + Power - + Toughness - + Loyalty - + Format - + Main Type - + Sub Type @@ -622,22 +1036,22 @@ This be only saved for captains and cannot be seen by t' brutally banned sc CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -645,17 +1059,27 @@ This be only saved for captains and cannot be seen by t' brutally banned sc CardInfoTextWidget - + Name: + Set: + + + + + Collector Number: + + + + Related cards: - + Unknown card: @@ -663,132 +1087,150 @@ This be only saved for captains and cannot be seen by t' brutally banned sc CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - - Toggle &normal untapping + + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + + Reduce life by power + + + + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... + + CardPictureLoaderCacheMethod + + + Network Cache + + + + + Filesystem + + + CardSizeWidget - + Card Size @@ -796,142 +1238,160 @@ This be only saved for captains and cannot be seen by t' brutally banned sc CardZoneLogic - + their hand nominative - + %1's hand nominative - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - + their graveyard nominative - + %1's graveyard nominative - + their exile nominative - + %1's exile nominative - + their sideboard look at zone - + %1's sideboard look at zone - + their sideboard nominative - + %1's sideboard nominative - + their custom zone '%1' nominative - + %1's custom zone '%2' nominative + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser - + Parse error at line %1 col %2: @@ -939,29 +1399,572 @@ This be only saved for captains and cannot be seen by t' brutally banned sc CockatriceXml4Parser - + Parse error at line %1 col %2: + + ColorButton + + + Click to pick a color + + + + + Pick colour + + + + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + + + ConnectionController + + + + The server has reached its maximum user capacity, please check back later. + + + + + There are too many concurrent connections from your address. + + + + + Banned by moderator + + + + + Expected end time: %1 + + + + + This ban lasts indefinitely. + + + + + Scheduled server shutdown. + + + + + + Invalid username. + + + + + You have been logged out due to logging in at another location. + + + + + Connection closed + + + + + The server has terminated your connection. +Reason: %1 + + + + + The server is going to be restarted in %n minute(s). +All running games will be lost. +Reason for shutdown: %1 + + + + + Scheduled server shutdown + + + + + Failed Login + + + + + Your client seems to be missing features this server requires for connection. + + + + + To update your client, go to 'Help -> Check for Client Updates'. + + + + + + + + + + + + + + + + + + + + + + Error + + + + + Incorrect username or password. Please check your authentication information and try again. + + + + + There is already an active session using this user name. +Please close that session first and re-login. + + + + + + You are banned until %1. + + + + + + You are banned indefinitely. + + + + + This server requires user registration. Do you want to register now? + + + + + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. +Please close and reopen your client to try again. + + + + + An internal error has occurred, please close and reopen Cockatrice before trying again. +If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. + + + + + Account activation + + + + + Your account has not been activated yet. +You need to provide the activation token received in the activation email. + + + + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + + Server Full + + + + + Unknown login error: %1 + + + + + + +This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. + + + + + + + + + + Registration denied + + + + + Registration is currently disabled on this server + + + + + There is already an existing account with the same user name. + + + + + It's mandatory to specify a valid email address when registering. + + + + + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. + + + + + Password too short. + + + + + Registration failed for a technical problem on the server. + + + + + The connection to the server has been lost. + + + + + Unknown registration error: %1 + + + + + Account activation failed + + + + + Socket error: %1 + + + + + Server timeout + + + + + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. +Local version is %1, remote version is %2. + + + + + Your Cockatrice client is obsolete. Please update your Cockatrice version. +Local version is %1, remote version is %2. + + + + + + Success + + + + + Registration accepted. +Will now login. + + + + + Account activation accepted. +Will now login. + + + + + Information + + + + + This server supports additional features that your client doesn't have. +This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. + +To update your client, go to Help -> Check for Updates. + + + + + + + Reset Password + + + + + Your password has been reset successfully, you can now log in using the new credentials. + + + + + Failed to reset user account password, please contact the server operator to reset your password. + + + + + Activation request received, please check your email for an activation token. + + + + + Connecting to %1... + + + + + Registering to %1 as %2... + + + + + Disconnected + + + + + Connected, logging in at %1 + + + + + Requesting forgotten password to %1 as %2... + + + + + Your username must respect these rules: + + + + + is %1 - %2 characters long + + + + + can %1 contain lowercase characters + + + + + + + + NOT + + + + + can %1 contain uppercase characters + + + + + can %1 contain numeric characters + + + + + can contain the following punctuation: %1 + + + + + first character can %1 be a punctuation mark + + + + + no unacceptable language as specified by these server rules: + note that the following lines will not be translated + + + + + can not contain any of the following words: %1 + + + + + can not match any of the following expressions: %1 + + + + + You may only use A-Z, a-z, 0-9, _, ., and - in your username. + + + CustomZoneMenu - + C&ustom Zones - - + + View custom zone '%1' + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + Include Sideboard + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget - + Card Info @@ -969,47 +1972,17 @@ This be only saved for captains and cannot be seen by t' brutally banned sc DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - - Add to Deck - - - - - Add to Sideboard - - - - - Select Printing - - - - - Show on EDHRec (Commander) - - - - - Show on EDHRec (Card) - - - - - Show Related cards - - - - + Add card to &maindeck - + Add card to &sideboard @@ -1017,87 +1990,158 @@ This be only saved for captains and cannot be seen by t' brutally banned sc DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card - + Main Type - + Mana Cost - + Colors - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + + Format: + + + + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1105,17 +2149,17 @@ This be only saved for captains and cannot be seen by t' brutally banned sc DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1123,114 +2167,119 @@ This be only saved for captains and cannot be seen by t' brutally banned sc DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - - - Load deck from cl&ipboard... - - - - - Edit deck in clipboard - - - - - - Annotated - - - - - - Not Annotated - - - Save deck to clipboard + Share deck... - Annotated (No set info) + Load deck from cl&ipboard... - Not Annotated (No set info) + Edit deck in clipboard + + + + + + Annotated - &Print deck... + + Not Annotated - Load deck from online service... - - - - - &Send deck to online service + Save deck to clipboard - Create decklist (decklist.org) - - - - - Create decklist (decklist.xyz) + Annotated (No set info) + Not Annotated (No set info) + + + + + &Print deck... + + + + + Load deck from online service... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1238,7 +2287,7 @@ This be only saved for captains and cannot be seen by t' brutally banned sc DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1246,194 +2295,213 @@ This be only saved for captains and cannot be seen by t' brutally banned sc DeckEditorSettingsPage - - + + Update Spoilers - - + Success - + Download URLs have been reset. - - Downloaded card pictures have been reset. - - - - - Error - - - - - One or more downloaded card pictures could not be cleared. - - - - + Add URL - - + + URL: - - + + Edit URL - - Network Cache Size: - - - - - Redirect Cache TTL: - - - - - How long cached redirects for urls are valid for. - - - - - Picture Cache Size: - - - - + Add New URL - + Remove URL - - Day(s) - - - - + Updating... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path Map yer scurvy way - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - - Delete Downloaded Images - - - - + Reset Download URLs + + + DeckListHistoryManagerWidget - - On-disk cache for downloaded pictures + + Undo - - In-memory cache for pictures not currently on screen + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] DeckListModel - + Count - + Set - + Number - + Provider ID - + Card @@ -1441,12 +2509,12 @@ This be only saved for captains and cannot be seen by t' brutally banned sc DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1454,17 +2522,22 @@ This be only saved for captains and cannot be seen by t' brutally banned sc DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -1472,7 +2545,7 @@ This be only saved for captains and cannot be seen by t' brutally banned sc DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -1480,62 +2553,62 @@ This be only saved for captains and cannot be seen by t' brutally banned sc DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -1543,98 +2616,216 @@ This be only saved for captains and cannot be seen by t' brutally banned sc DeckPreviewWidget - + Banner Card - + Open in deck editor - + + Share deck... + + + + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Rename deck + + + + + Rename file + + + + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Set playmat to %1 + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1652,94 +2843,122 @@ This be only saved for captains and cannot be seen by t' brutally banned sc DeckViewContainer - + Load deck... - + Load remote deck... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start - + Force start - + Sideboard unlocked - + Sideboard locked - - + + Error Cap'n? Thar be a problem - + The selected file could not be loaded. Cap'n yer file can't be loaded. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? - + Remember and automatically apply choice in the future @@ -1752,143 +2971,143 @@ This will kick all non-ready players from the game. - + Known Hosts - + Delete the currently selected saved server - + Refresh the server list with known public servers - + New Host - + Name: - + &Host: &Cap'n - + &Port: - + Player &name: Matey &name: - + P&assword: &A stupendously secret code: - + &Save password Record t' &stupendously secret code - + A&uto connect - + Automatically connect to the most recent login when Cockatrice opens - + If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage - + Reset Password - + Forgot password? - + &Connect - + Server - + Login - + Server Contact - + Connect to Server - + Server URL - + Communication Port - + Unique Server Name - + Connection Warning - + You need to name your new connection profile. - + Connect Warning - + The player name can't be empty. @@ -1981,32 +3200,37 @@ This will kick all non-ready players from the game. - + + Create game as judge + + + + Game setup options - + &Clear - + Create game - + Game information - + Error Cap'n? Thar be a problem - + Server error. Cap'n? Thar be a problem wi' t' ship. @@ -2014,97 +3238,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Name: - + Token - + C&olor: C&olor - + white pure white - + blue sea blue - + black deathly black - + red blood red - + green emerald green - + multicolor multicolor - + colorless colorless - + &P/T: &P/T: - + &Annotation: - + &Destroy token when it leaves the table - + Create face-down (Only hides name) - + Token data - + Show &all tokens - + Show tokens from this &deck - + Choose token from list - + Create token @@ -2112,53 +3336,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + ✖ - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2166,39 +3390,44 @@ This will kick all non-ready players from the game. DlgEditAvatar - - + + No image chosen. No pic be picked! - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... - + Change avatar Change yer pic - + Open Image Open Pic - + Image Files (*.png *.jpg *.bmp) - + Invalid image chosen. Invalid pic be picked! @@ -2206,17 +3435,17 @@ To remove your current avatar, confirm without choosing a new image. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2224,38 +3453,38 @@ To remove your current avatar, confirm without choosing a new image. DlgEditPassword - + Old password: Old stupendously secret code: - + New password: Freshly formed stupendously secret code: - + Confirm new password: Confirm freshly formed stupendously secret code: - + Change password Change t' stupendously secret code - - + + Error Cap'n? Thar be a problem - + Your password is too short. - + The new passwords don't match. T' freshly formed stupendously secret codes be failin' to match! @@ -2263,83 +3492,83 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: &Name: - + C&olor: C&olor - + white pure white - + blue sea blue - + black deathly black - + red blood red - + green emerald green - + multicolor multicolor - + colorless colorless - + &P/T: &P/T: - + &Annotation: - + Token data - - + + Add token - + Remove token - + Edit custom tokens - + Please enter the name of the token: @@ -2373,12 +3602,12 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - + Real name: - + Edit user profile @@ -2416,112 +3645,115 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - + Hide 'buddies only' games - + Hide full games - - Hide games that have started + + Hide started games + Hide games that have started - + Hide password protected games - + Hide 'ignored user' games - - Hide games not created by buddy + + Hide games not created by buddies + Hide games not created by buddy - + Hide games with forced open decklists - + &Newer than: - + Game &description: - - &Creator name: - &Cap'n name: + + &Host name: + &Creator name: + - + General - + &Game types - + at &least: - + at &most: - + Maximum player count - + Restrictions - + Show games only if &spectators can watch - + Show spectator password p&rotected games - + Show only if spectators can ch&at - + Show only if spectators can see &hands - + Spectators - + Filter games @@ -2704,10 +3936,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck @@ -2715,17 +3980,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard Find a flagship from t' clipplank - + Error Cap'n? Thar be a problem - + Invalid deck list. Invalid flagship list. @@ -2733,43 +3998,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2783,155 +4048,589 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck Find a flagship + + DlgLocalGameOptions + + + Players: + + + + + General + + + + + Starting life total: + + + + + Game setup options + + + + + Remember settings + + + + + Local game options + + + + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: &Cap'n - + &Port: - + Player &name: Matey &name: - + P&assword: &A stupendously secret code: - + Password (again): - + Email: - + Email (again): - + Country: - + Undefined - + Real name: - + Register to server Board t' ship - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning - + Your password is too short. - + Your passwords do not match, please try again. - + Your email addresses do not match, please try again. - + The player name can't be empty. + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice - + Number of sides: - + Number of dice: - + Roll Dice @@ -2939,62 +4638,55 @@ Your email will be used to verify your account. DlgSelectSetForCards - + Unmodified Cards: - + Modified Cards: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. - + Clear all set information - + Set all to preferred + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database Cap'n? Thar be a unknown problem wi' t' dock - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3003,21 +4695,21 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3026,99 +4718,235 @@ Would you like to change your database location setting? - - - + + + Error Cap'n? Thar be a problem - + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings Cap'n's orders - + + Search settings... + + + + + OK + + + + General - + Appearance - + User Interface - + + Storage + + + + Card Sources - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat - + Sound - + Shortcuts + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3144,164 +4972,174 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel - + Reinstall - + Cancel Download - + Open Download Page - + Check for Client Updates - - - - + + + + Error - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - + Downloading update: %1 - + Checking for updates... - + Finished checking for updates - + No Update Available - + Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. - + Current version - + Selected release channel - - + + Update Available - - + + A new version of Cockatrice is available! - - + + New version - - + + Released - - + + Changelog - + Do you want to update now? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Installing... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -3309,21 +5147,153 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing - + Copy to clipboard - + Debug Log + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3393,33 +5363,39 @@ You may have to manually download the new version. + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos - + Average Deck - - - Game Changers - - - - - Budget - - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -3427,7 +5403,7 @@ You may have to manually download the new version. FilterBuilder - + Type your filter here @@ -3435,45 +5411,88 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -3481,226 +5500,295 @@ You may have to manually download the new version. GameSelector - - - - - - - - - + + + + + + + + + Error Cap'n? Thar be a problem - + Please join the appropriate room first. - + Wrong password. Ye don't know the stupendously secret code! - + Spectators are not allowed in this game. Liliy-livered landlubbers - get off me ship! - + The game is already full. T' ship be full now. - + The game does not exist any more. T' ship be vanishin'! - + This game is only open to registered users. - + This game is only open to its creator's buddies. T' ship be only open for the cap'n's first mates. - + You are being ignored by the creator of this game. T' cap'n be ignorin' ye. - + Join Game - + Spectate Game - + Game Information - + + Cop&y game link + + + + + Join Game as Judge + + + + + Spectate Game as Judge + + + + + Join game Board ship - - Password: - Stupendously secret code: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. - + &Filter games - + C&lear filter - + C&reate - + &Join - + + Join as judge + + + + J&oin as spectator B&oard as landlubber - + + Join as judge spectator + + + + Games shown: %1 / %2 - + Games Ships + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password stupendously secret code - + buddies only first mates only - + reg. users only - + open decklists - - + + can chat - + see hands - + can see hands - + not allowed - + Room - + Age - + Description - - Creator - Cap'n + + Host + - + Type - + Restrictions - + Players Mateys - + Spectators Landlubbers @@ -3708,143 +5796,275 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + English + English, arr! (Pirate English) + + + + + + + + + Choose path - - Personal settings - Cap'n's orders for t' cap'n + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> + - + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + + Language settings + + + + Language: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + + Version settings + + + + + Card database + + + + + Startup settings + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) - + Paths - + How to help with translations - + Decks directory: - + Filters directory: - + Replays directory: - + Pictures directory: - + Card database: Dock: - + Custom database directory: - + Token database: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup - + Last update check on %1 (%2 days ago) @@ -3852,47 +6072,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -3900,305 +6120,649 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - - &Sort hand + + Sort hand by... - - Take &mulligan + + Name - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - Reveal r&andom card to... + + + All players - - - &All players + + Reveal r&andom card to... + + + + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + + Browse Archidekt + + + + View Replays - + Quit - + Connecting... - + Connect - + Play + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu - + &Library - + &View library - + View &top cards of library... - + View bottom cards of library... - + Reveal &library to... - + Lend library to... - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always reveal top card - + &Always look at top card - + &Open deck in deck editor - + &Draw card - + D&raw cards... - + &Undo last draw - + Shuffle - + &Play top card - + Play top card &face down - + Put top card on &bottom - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... - + + Move top cards to exile face down... + + + + Put top cards on stack &until... - + Shuffle top cards... - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top - + Shuffle bottom cards... - - + + &All players - + Reveal top cards of library - + Number of cards: (max. %1) @@ -4206,1416 +6770,1244 @@ You may have to manually download the new version. MainWindow - - - The server has reached its maximum user capacity, please check back later. - - - - - There are too many concurrent connections from your address. - - - - - Banned by moderator - Cap'n's thrown ye outta tha ship! - - - - Expected end time: %1 - - - - - This ban lasts indefinitely. - - - - - Scheduled server shutdown. - - - - - - Invalid username. - Yarrr, that ain't ye name ye scurvy landlubber! - - - - You have been logged out due to logging in at another location. - - - - - Connection closed - - - - - The server has terminated your connection. -Reason: %1 - - - - - The server is going to be restarted in %n minute(s). -All running games will be lost. -Reason for shutdown: %1 - - - - - Scheduled server shutdown - - - - - - Success - - - - - Registration accepted. -Will now login. - - - - - Account activation accepted. -Will now login. - - - - - Number of players - Number o' mateys - - - - Please enter the number of players. - Me hearty, enter t' number o' mateys - - - - + + Player %1 Matey %1 - + Load replay - + About Cockatrice - + Version - + Cockatrice Webpage - + Project Manager: - + Past Project Managers: - + Developers: - + Our Developers - + Help Develop! - + Translators: - + Our Translators - + Help Translate! - + Support: - + Report an Issue - + Troubleshooting - + F.A.Q. - - - - - - - - - - - - - - - - - - - - + + Error Cap'n? Thar be a problem - - Server timeout - T' ship be timin' out! - - - - Failed Login - - - - - Your client seems to be missing features this server requires for connection. - - - - - To update your client, go to 'Help -> Check for Client Updates'. - - - - - Incorrect username or password. Please check your authentication information and try again. - - - - - There is already an active session using this user name. -Please close that session first and re-login. - - - - - - You are banned until %1. - - - - - - You are banned indefinitely. - - - - - This server requires user registration. Do you want to register now? - - - - - This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. -Please close and reopen your client to try again. - - - - - An internal error has occurred, please close and reopen Cockatrice before trying again. -If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - - - - - Account activation - - - - - Your account has not been activated yet. -You need to provide the activation token received in the activation email. - - - - - Server Full - - - - - Unknown login error: %1 - - - - - - -This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - - Your username must respect these rules: - - - - - is %1 - %2 characters long - - - - - can %1 contain lowercase characters - - - - - - - - NOT - - - - - can %1 contain uppercase characters - - - - - can %1 contain numeric characters - - - - - can contain the following punctuation: %1 - - - - - first character can %1 be a punctuation mark - - - - - no unacceptable language as specified by these server rules: - note that the following lines will not be translated - - - - - can not contain any of the following words: %1 - - - - - can not match any of the following expressions: %1 - - - - - You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - - - - - Registration denied - - - - - Registration is currently disabled on this server - - - - - There is already an existing account with the same user name. - - - - - It's mandatory to specify a valid email address when registering. - - - - - It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - - - - - Password too short. - The code be too piddly to be stupendously secret. - - - - Registration failed for a technical problem on the server. - - - - - The connection to the server has been lost. - - - - - Unknown registration error: %1 - - - - - Account activation failed - - - - - Socket error: %1 - - - - - You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. -Local version is %1, remote version is %2. - - - - - Your Cockatrice client is obsolete. Please update your Cockatrice version. -Local version is %1, remote version is %2. - - - - - Connecting to %1... - Boarding %1 - - - - Registering to %1 as %2... - - - - - Disconnected - Disembarked - - - - Connected, logging in at %1 - - - - - - - Requesting forgotten password to %1 as %2... - - - - + &Connect... &Board ye ship... - + &Disconnect &Disembark - + Start &local game... Built &local ship - + &Watch replay... - + &Full screen - + &Register to server... - + &Restore password... - + &Settings... Cap'n'&s orders - + &Exit Dis&embark - + A&ctions - + &Cockatrice - + C&ard Database - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + Reload card database - + Tabs - + &Help - + &About Cockatrice - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + + Re-run Onboarding Wizard... + + + + Show/Hide - + + Sign-in cancelled + + + + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + + Are you sure? + + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes AYE! - - + + No NAY! - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + + Yes, always enable + + + + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + Information - + A card database update is already running. A dock update be runnin' already! - + Unable to run the card database updater: Cap'n! We can't run the dock updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - - This server supports additional features that your client doesn't have. -This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. - -To update your client, go to Help -> Check for Updates. - - - - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. + + + ManaBaseConfigDialog - - - - Reset Password + + Mana Base Configuration - - Your password has been reset successfully, you can now log in using the new credentials. + + Display type: - - Failed to reset user account password, please contact the server operator to reset your password. + + pie - - Activation request received, please check your email for an activation token. + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel ManaBaseWidget - - + Mana Base + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play - + from their graveyard - + from exile from the darkest depths o' the lass's Davy Jones' Locker - + from their hand - + the top card of %1's library the top card o' %1's library - + the top card of their library - + from the top of %1's library from the top of %1's library - + from the top of their library - + the bottom card of %1's library the bottom card o' %1's library - + the bottom card of their library - + from the bottom of %1's library from the bottom o' %1's library - + from the bottom of their library - + from %1's library from %1's library - + from their library - + from sideboard from sideplank - + from the stack - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. - + %1 is not revealing the top card %2 any longer. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. - + %1 has conceded the game. - + %1 has unconceded the game. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. - + %1 has loaded a deck (%2). %1 be loadin' a flagship (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 be loadin' a flagship wi' %2 sideplank cards (%3). - + %1 destroys %2. - + a card - + %1 gives %2 control over %3. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. - + + %1 exiles %2%3 face down. + + + + %1 exiles %2%3. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. T' ship has been sunk! - + The game has started. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. - + %1 is now watching the game. - + You have been kicked out of the game. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. Yer mate %1 wants a duel! - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. - + %1 sets annotation of %2 to %3. - - %1 places %2 "%3" counter(s) on %4 (now %5). + + %1 places %2 %3%4 counter(s) on %5 (now %6). - - %1 removes %2 "%3" counter(s) from %4 (now %5). + + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + + %1 failed to undo their last draw. + + + + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. - + %1 untaps their permanents. - + %1 taps %2. - + %1 untaps %2. - + %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -5623,110 +8015,125 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message - - + + Message: - + Edit message - + Chat settings Cap'n's orders for t' chat - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Ignore private messages sent by non-buddy users + + + + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -5734,37 +8141,42 @@ Cockatrice will now reload the card database. MoveMenu - + Move to - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + + T&able + + + + &Hand - + &Graveyard - + &Exile @@ -5777,47 +8189,47 @@ Cockatrice will now reload the card database. - + Mana Value - + Color(s) - + Loyalty - + Main Card Type - + Mana Cost - + P/T - + Side - + Layout - + Color Identity @@ -5840,65 +8252,197 @@ Cockatrice will now reload the card database. + + PaletteEditorDialog + + + + Reset + + + + + ▼ Edit Palette + + + + + + ▶ Edit Palette + + + + + Palette Editor — %1 + + + + + <b>Palette Editor</b> &nbsp;·&nbsp; %1 + + + + + + Save + + + + + Delete this scheme's custom palette and revert to the theme default (or the application palette) + + + + + Switch between the light and dark palette files + + + + + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + + ↺ Revert to theme default + + + + + Discard unsaved edits and restore the last saved palette + + + + + Cannot save: this theme has no writable directory + + + + + Write palette-%1.toml and reload the theme + + + + + + Save failed + + + + + + Could not write %1 to: +%2 + + + + + PaletteGridWidget + + + Active + + + + + Disabled + + + + + Inactive + + + + + Normal interactive state + + + + + Widget is disabled / not interactive + + + + + Window is in background / unfocused + + + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + + Phase - + Unknown Phase - + Untap - + Upkeep - + Draw - + First Main - + Beginning of Combat - + Declare Attackers - + Declare Blockers - + Combat Damage - + End of Combat - + Second Main - + End/Cleanup @@ -5906,253 +8450,552 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step Step where ye be untappin' - + Upkeep step Step where ye be upkeepin' - + Draw step Step where ye be drawin' - + First main phase First main phase - + Beginning of combat step Step where ye begin fightin' - + Declare attackers step Step where ye be orderin' ye men to board - + Declare blockers step Step where ye be orderin' ye men to repel boarders - + Combat damage step Step where people get hurt - + End of combat step Step where ye stop fightin' - + Second main phase Second main phase - + End of turn step Step where ye be endin' ye turn - PictureLoader + PlayerActions - - en - code for scryfall's language property, not available for all languages + + + + + grave + + + + + + + + exile - PlayerActions + PlayerDialogs - + View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - - Move top cards to grave + + Move top cards to %1 - - Move top cards to exile + + Move bottom cards to %1 - - Move bottom cards to grave - - - - - Move bottom cards to exile - - - - + Draw bottom cards - - - C&reate another %1 token - - - - + Create tokens - - + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - - - Set counters - - PlayerMenu - + Player "%1" - + &Counters + + + PlaymatCollectionDialog - - S&ay + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + PrintingDisabledInfoWidget + + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again PrintingSelector - + Display Navigation Buttons + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -6165,17 +9008,17 @@ Cockatrice will now reload the card database. PrintingSelectorCardSelectionWidget - + Previous Card in Deck - + Bulk Selection - + Next Card in Deck @@ -6183,124 +9026,140 @@ Cockatrice will now reload the card database. PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu - + Power / toughness - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -6308,224 +9167,401 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) - + All files (*.*) - + Cockatrice replays (*.cor) - + Maindeck - + Sideboard - + Tokens - + + Maybeboard + + + + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults + + QuickSetupPanel + + + %1% + + + + + <b>Quick Setup</b> + + + + + Generate all palette roles automatically from a single accent colour + + + + + Accent: + + + + + Primary hue. Used directly for highlights and links. +At high intensity it also tints buttons and backgrounds. + + + + + Intensity: + + + + + Subtle + + + + + Full colour + + + + + 0–30 Subtle tint — only highlights and links change hue +30–70 Accented — buttons, tooltips, and borders join in +70–100 Full colour — backgrounds, everything + + + + + 70% + + + RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name Name - + ID - + Upload time + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID - + Name Name - + Players - + Keep - + Time started - + Duration (sec) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu - + &Exile - + &View exile - + &Move exile to... - + &Top of library - + &Bottom of library - + &Hand - + &Graveyard @@ -6533,70 +9569,78 @@ Cockatrice will now reload the card database. RoomSelector - + Rooms - + Joi&n - + Room - + Description - + Permissions - + Players - + Games + + SayMenu + + + S&ay + + + SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - + Invalid key - + Shortcut already in use by: - + Clear - + Restore default @@ -6604,81 +9648,117 @@ Cockatrice will now reload the card database. SetsModel - + Enabled - + Set type - + Set code - + Long name - + Release date + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + + Shortcut editor + + + + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -6686,12 +9766,12 @@ Cockatrice will now reload the card database. ShortcutTreeView - + Action - + Shortcut @@ -6699,13 +9779,13 @@ Cockatrice will now reload the card database. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -6732,12 +9812,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -6745,27 +9825,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings Cap'n's orders for t' shanties - + Master volume @@ -6773,48 +9853,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -6822,45 +9902,204 @@ Please check your shortcut settings! StableReleaseChannel - + Default - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. + + StatsTally + + + Total Power + + + + + Total Toughness + + + + + StorageSettingsPage + + + + + Success + + + + + Cached card pictures have been reset. + + + + + Downloaded card pictures have been reset. + + + + + Error + + + + + One or more downloaded card pictures could not be cleared. + + + + + In-memory (currently loaded) card pictures have been reset. + + + + + Card Picture Loader Caching Method: + + + + + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. + + + + + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. + + + + + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. + + + + + Delete Cached Images + + + + + Delete Saved Images + + + + + Clear In-Memory Images + + + + + Card Picture Loader Cache Method + + + + + Network Cache + + + + + Filesystem + + + + + In-Memory Picture Cache + + + + + Network Cache Size: + + + + + On-disk cache for downloaded pictures + + + + + Redirect Cache TTL: + + + + + How long cached redirects for urls are valid for. + + + + + Picture Cache Size: + + + + + In-memory cache for pictures not currently on screen + + + + + Naming scheme: + + + + + Day(s) + + + TabAccount - + + + My Reports + + + + Add to Buddy List - + Add to Ignore List - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account @@ -6978,56 +10217,227 @@ Please check your shortcut settings! + + TabArchidekt + + + + + Desc. + + + + + + AND + + + + + + Require ALL selected colors + + + + + + Deck name... + + + + + + Owner... + + + + + + Packages + + + + + + Advanced Filters + + + + + Bracket: + + + + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + + Search + + + + + + Formats + + + + + Archidekt: + + + + + TabCardArtRules + + + Card: + + + + + ProviderId: + + + + + Mode: + + + + + Reason: + + + + + Type a card name... + + + + + Add rule + + + + + Remove rule + + + + + Refresh + + + + + Card Art Rules + + + TabDeckEditor - + Card Info - + Deck - + Filters - + &View - + + Card Database + + + + Printing - - - - + Visible - - - - + Floating - + Reset layout - + Deck: %1 Flagship: %1 @@ -7035,61 +10445,49 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - - Filters - - - - + &View - + Printing - - - - + Visible - - - - + Floating - + Reset layout @@ -7097,22 +10495,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7120,138 +10518,236 @@ Please check your shortcut settings! TabDeckStorage - + Local file system - + Server deck storage O'er ship's flagship port - - + + Open in deck editor Open in dock - + Rename deck or folder - + Upload deck - + Download deck - - - - + + + + New folder - - + + Delete - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + + + + Error - + Rename failed - - + + + Invalid deck file - + Enter deck name Enter flagship name - + This decklist does not have a name. Please enter a name: - + Unnamed deck Unnamed flagship - + Failed to upload deck to server - + Delete local file - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: - + Deck Storage @@ -7259,20 +10755,271 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -7285,32 +11032,32 @@ Please enter a name: TabEdhRecMain - + &Cards - + Top Commanders - + Tags - + Search for a card ... - + Search - + EDHRec: @@ -7318,197 +11065,207 @@ Please enter a name: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + + Invite + + + + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases - + &Game - + Next &phase - + Next phase with &action - + Next &turn - + Reverse turn order - + &Remove all local arrows - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede - - - + + + &Concede - + &Leave game - + C&lose replay - + &Focus Chat - + &Say: - + Selected cards - + &View - - - - + Visible - - - - + Floating - + Reset layout - + Concede - + Are you sure you want to concede this game? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game - + Are you sure you want to leave this game? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. @@ -7516,7 +11273,7 @@ Please enter a name: TabHome - + Home @@ -7529,152 +11286,152 @@ Please enter a name: - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: - + IP Address: - + Game Name: - + GameID: - + Message: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -7682,218 +11439,558 @@ The more information you put in, the more specific your results will be. TabMessage - + Private &chat - + &Leave - + %1 - Private chat - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from - + %1 has left the server. - + %1 has joined the server. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system - + Server replay storage - - + + Watch replay - + Rename - - + + New folder - - + + Delete - + Open replays folder - + Download replay - + Toggle expiration lock - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay @@ -7903,50 +12000,478 @@ The more information you put in, the more specific your results will be. + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - + &Say: - + Chat - + &Room - + &Leave room - + &Clear chat - + Chat Settings... Cap'n's orders for t' chat... - + mentioned you. - + Click to view - + You are flooding the chat. Please wait a couple of seconds. @@ -7954,35 +12479,35 @@ The more information you put in, the more specific your results will be. TabServer - + Server - - - - + + + + Error - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -7990,92 +12515,173 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - + + Archidekt + + + + Home - + &Visual Deck Storage - + Visual Database Display - + Server - + Account - + Deck Storage - + Game Replays - + Administration - + Logs - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? - + There are still open games. Are you sure you want to quit? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8083,38 +12689,38 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) @@ -8122,12 +12728,45 @@ Please refrain from engaging in this activity or further actions may be taken ag TabVisualDatabaseDisplay + + + Database Display + + Visual Database Display + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -8142,6 +12781,65 @@ Please refrain from engaging in this activity or further actions may be taken ag + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -8160,42 +12858,42 @@ Please refrain from engaging in this activity or further actions may be taken ag TranslateCounterName - + Life - + White - + Blue - + Black - + Red - + Green - + Colorless - + Other @@ -8203,214 +12901,327 @@ Please refrain from engaging in this activity or further actions may be taken ag UpdateDownloader - + Could not open the file for reading. + + UserCardArtSettingsDialog + + + Card Art Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Remove Banner Card + + + UserContextMenu - + User &details - + Private &chat - + Show this user's &games - - - Add to &buddy list - - - - - Remove from &buddy list - - - - - Add to &ignore list - - - - - Remove from &ignore list - - - Kick from &game + View this user's &public decks - Warn user + Add to &buddy list - View user's war&n history + Remove from &buddy list - Ban from &server + Add to &ignore list - View user's &ban history + Remove from &ignore list - &Promote user to moderator + Kick from &game - Dem&ote user from moderator + Report user - Promote user to &judge + Warn user - Demote user from judge + View user's war&n history + Ban from &server + + + + + View user's &ban history + + + + + &Promote user to moderator + + + + + Dem&ote user from moderator + + + + + Promote user to &judge + + + + + Demote user from judge + + + + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes - - - - Error - - - - - This user does not exist. - - - - - You are being ignored by %1 and can't see their games. + + Investigate user + + + Error + + + + + This user does not exist. + + + + + You are being ignored by %1 and can't see their games. + + + + Could not get %1's games. - + %1's games - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - + Failed to get admin notes. - - + + Success - + Successfully promoted user. - + Successfully demoted user. - - - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + + Kick Player + + + + + Are you sure you want to kick this player from the game? + + + + + + Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -8418,48 +13229,48 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInfoBox - + Location: - + Account Age: - + Edit - + Change password - + Change avatar - + Administrator - + Moderator - + Registered user - - + + Unregistered user @@ -8469,408 +13280,913 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Unknown - + The entered password does not match your account. - - - + + + + Information - + User information updated. - - - - - - - - - - + + + + + + + + + + + Error - + User Information - + Real Name: - + User Level: + + + Edit Banner Card + + + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + + The selected card is blacklisted on this server or another error occurred. + + + + + Banner card removed. + + + + + Banner card updated. + + + + This server does not permit you to update your user informations. - + Password changed. - + This server does not permit you to change your password. - + The new password is too short. - + The old password is incorrect. - + Avatar updated. - + This server does not permit you to update your avatar. - - An error occured while trying to updater your avatar. + + An error occured while trying to update your avatar. + + + + + UserInfoPopup + + + + Games + + + + + Chat + + + + + Open private chat + + + + + Profile + + + + + View user profile + + + + + Show this user's games + + + + + Invite + + + + + Invite to your game + + + + + − Buddy + + + + + Remove from buddy list + + + + + + Buddy + + + + + Add to buddy list + + + + + − Ignore + + + + + Remove from ignore list + + + + + + Ignore + + + + + Add to ignore list + + + + + Ban + + + + + Ban from server + + + + + Warn + + + + + Warn user + + + + + Ban log + + + + + View ban history + + + + + Warn log + + + + + View warning history + + + + + Notes + + + + + View admin notes + + + + + − Mod + + + + + Demote from moderator + + + + + + Mod + + + + + Promote to moderator + + + + + − Judge + + + + + Demote from judge + + + + + + Judge + + + + + Promote to judge + + + + + Join game + + + + + Spectate + + + + + + Loading games… + + + + + Could not load games. + + + + + No active games. UserInterfaceSettingsPage - + General interface settings - + &Double-click cards to play them (instead of single-click) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default - + + Do not delete &arrows inside of subphases + + + + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens - + + Show selection count during drag selection + + + + + Show total selection count + + + + + + Disabled + + + + + + Enabled + + + + + + Automatic + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + Use tear-off menus, allowing right click menus to persist on screen - + + Keep game chat focused when clicking in game (Note: disables card view search bar) + + + + Notifications settings - + Enable notifications in taskbar - + Notify in the taskbar for game events while you are spectating - + Notify in the taskbar when users in your buddy list connect - + Animation settings Cap'n's orders for t' things that be movin' - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings - + Open deck in new tab by default - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - - do nothing + + Do nothing - - ask to convert to .cod + + Ask to convert to .cod - - always convert to .cod + + Always convert to .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings - + Buffer time for backwards skip via shortcut: + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + + Flip coin + + + + &Create token... - + C&reate another token - + Cr&eate predefined token + + + C&reate another %1 token + + VisualDatabaseDisplayColorFilterWidget - Mode: Exact Match + Exact match - - Mode: Includes + + Includes - - Mode: Include/Exclude + + Include / Exclude + Mode: Includes - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter - + Save all currently applied filters to a file - + Enter filename... + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Show formats with at least: + + + + + cards + + + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes VisualDatabaseDisplayNameFilterWidget - - - Filter by name... - - - - - Load from Deck - - - Apply all card names in currently loaded deck as exact match name filters + Filter by name... (Exact match) - Load from Clipboard + Load from Deck + Apply all card names in currently loaded deck as exact match name filters + + + + + Load from Clipboard + + + + Apply all card names in clipboard as exact match name filters @@ -8878,7 +14194,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -8886,19 +14202,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -8906,27 +14222,37 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -8934,50 +14260,93 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - + + + Visual + + + + Loading database ... - + Clear all filters - - Save and load filters + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: - - Filter by exact card name + + Change how cards are divided into categories/groups. - - Filter by card sub-type + + Sort by: - - Filter by set + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + + + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -8985,46 +14354,25 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter - - - Configure how cards are sorted within their groups - - - - - - Overlap Layout - - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - - Flat Layout - - VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -9032,47 +14380,57 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + + Show Upload Time + + + + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -9080,7 +14438,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -9088,22 +14446,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -9111,17 +14469,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + + Select decks to share + + + + Visual Deck Storage Settings @@ -9129,46 +14492,71 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Warn user for misconduct - - + + Error - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets @@ -9192,11 +14580,6 @@ Please refrain from engaging in this activity or further actions may be taken ag Move selected set to the bottom Move selected fleet to the bottom - - - Search by set name, code, or type - - Default order @@ -9208,98 +14591,103 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Enable all sets Enable all fleets - + Disable all sets Disable all fleets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - + + Search by set name, code, type, or release date + + + + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -9307,93 +14695,85 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing - + pile view - - i18n - - - English - English, arr! (Pirate English) - - main - + Connect on startup - + Debug to file @@ -9401,1005 +14781,1088 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window - - + + Deck Editor - + Game Lobby - + Card Counters - + Player Counters - + Power and Toughness - + Game Phases - + Playing Area - + Move Selected Card - + View - + Move Top Card - + Move Bottom Card - + Gameplay - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - - + + Replays - + Tabs - + Check for Card Updates... - + Connect... - + Disconnect - + Exit - + Full screen - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - + Analyze Deck (deckstats.net) - + Analyze Deck (tappedout.net) - + Clear All Filters - + Clear Selected Filter - + Close - + Remove Card - + Manage Sets... - + Edit Custom Tokens... - + Export Deck (decklist.org) - + Export Deck (decklist.xyz) - + Add Card - + Load Deck... - - + + Load Deck from Clipboard... - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck - + Open Custom Pictures Folder - + Print Deck... - + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard - + Save Deck to Clipboard (No Set Info) - + Load Local Deck... - + Load Remote Deck... - + Set Ready to Start - + Toggle Sideboard Lock - + Add Green Counter - + Remove Green Counter - + Set Green Counters... - + Add Red Counter - + Remove Red Counter - + Set Red Counters... - + Add Life Counter - + Show Status Bar - + + Load deck from online service... + + + + + Load from website... + + + + Unload Deck - + Force Start - + Add Card Counter (F) - + Remove Card Counter (F) - + Set Card Counters (F)... - + Add Card Counter (E) - + Remove Card Counter (E) - + Set Card Counters (E)... - + Add Card Counter(D) - + Remove Card Counter (D) - + Set Card Counters (D)... - + Add Card Counter (C) - + Remove Card Counter (C) - + Set Card Counters (C)... - + Add Card Counter (B) - + Remove Card Counter (B) - + Set Card Counters (B)... - + Add Card Counter (A) - + Remove Card Counter (A) - + Set Card Counters (A)... - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Increment all card counters - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap - + Upkeep - + Draw - + First Main Phase - + Start Combat - + Attack - + Block - + Damage - + End Combat - + Second Main Phase - + End - + Next Phase - + Next Phase Action - + Next Turn - + Hide Card in Reveal Window - + Tap / Untap Card - + Untap All - - Toggle Untap + + Toggle Skip Untapping + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + + Play Card, Face Down + + + + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - + + Reduce Life by Power + + + + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library - - - - + + + + Exile - - - - + + + + Graveyard - - + + Report Queue + + + + + Moderation + + + + + + Hand - - + + Top of Library - - - + + Battlefield, Face Down - + Battlefield - - Sort Hand - - - - + Library - + Sideboard - + Top Cards of Library - + Bottom Cards of Library - + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) - + + + Exile (Multiple), Face Down + + + + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede Parley - + Roll Dice... - + + Flip Coin + + + + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_en_US.ts b/cockatrice/translations/cockatrice_en_US.ts index 00075c8c3..ba1bab28f 100644 --- a/cockatrice/translations/cockatrice_en_US.ts +++ b/cockatrice/translations/cockatrice_en_US.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Set counter... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Set counter - + New value for counter '%1': New value for counter '%1': @@ -36,66 +36,115 @@ AbstractTabDeckEditor - + Open in new tab Open in new tab - + Are you sure? Are you sure? - + The decklist has been modified. Do you want to save the changes? The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error Error - + Could not open deck at %1 Could not open deck at %1 - + Could not save remote deck Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. The deck could not be saved. Please check that the directory is writable and try again. - + Save deck Save deck - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. The deck could not be saved. - + There are no cards in your deck to be exported There are no cards in your deck to be exported + + AccountSetupPage + + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + AddAnalyticsPanelDialog @@ -107,12 +156,12 @@ Please check that the directory is writable and try again. AdminNotesDialog - + Update Notes Update Notes - + Admin Notes for %1 Admin Notes for %1 @@ -138,218 +187,304 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds seconds - + Error Error - + Could not create themes directory at '%1'. Could not create themes directory at '%1'. - + Theme settings Theme settings - + Current theme: Current theme: - + Open themes folder Open themes folder - + Home tab background source: Home tab background source: - + Home tab background shuffle frequency: Home tab background shuffle frequency: - + Disabled Disabled - + Menu settings Menu settings - + Show keyboard shortcuts in right-click menus Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab Show game filter toolbar above list in room tab - + Card rendering Card rendering - + Display card names on cards having a picture Display card names on cards having a picture - + Auto-Rotate cards with sideways layout Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) Override all card art with personal set preference (Pre-ProviderID change behavior) - + Light - + Dark - + + System - + + Show all playmats + + + + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + Active theme palette: - + + Active theme style: + + + + + Qt widget style saved to this theme ("System" lets the application decide) + + + + Edit theme palette - + Home tab settings - + Display card name of background in bottom right - + + Dim the home tab background + + + + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + Styling settings - + Style user list - + Card printings - + Bump sets that the deck contains cards from to the top in the printing selector Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Scale cards on mouse over - + Use rounded card corners Use rounded card corners - + Card layout - + Minimum overlap percentage of cards on the stack and in vertical hand Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: Maximum initial height for card view window: - - + + rows rows - + Maximum expanded height for card view window: Maximum expanded height for card view window: - + Card counters Card counters - + Counter %1 Counter %1 - + Hand layout Hand layout - + Display hand horizontally (wastes space) Display hand horizontally (wastes space) - + Enable left justification Enable left justification - + Table grid layout Table grid layout - + Invert vertical coordinate Invert vertical coordinate - + Minimum player count for multi-column layout: Minimum player count for multi-column layout: - + Maximum font size for information displayed on cards: Maximum font size for information displayed on cards: @@ -357,12 +492,12 @@ Please check that the directory is writable and try again. ArchidektApiResponseDeckDisplayWidget - + Back to results Back to results - + Open Deck in Deck Editor Open Deck in Deck Editor @@ -388,112 +523,112 @@ Please check that the directory is writable and try again. BanDialog - + ban &user name ban &user name - + ban &IP address ban &IP address - + ban client I&D ban client I&D - + Ban type Ban type - + &permanent ban &permanent ban - + &temporary ban &temporary ban - + &Days: &Days: - + &Hours: &Hours: - + &Minutes: &Minutes: - + Duration of the ban Duration of the ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. - + Please enter the reason for the ban that will be visible to the banned person. Please enter the reason for the ban that will be visible to the banned person. - + Redact all messages from this user in all rooms Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Cancel - + Ban user from server Ban user from server - - - - + + + + Error Error - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. - + You must have a value in the name ban when selecting the name ban checkbox. You must have a value in the name ban when selecting the name ban checkbox. - + You must have a value in the ip ban when selecting the ip ban checkbox. You must have a value in the ip ban when selecting the ip ban checkbox. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. You must have a value in the clientid ban when selecting the clientid ban checkbox. @@ -501,22 +636,22 @@ This is only saved for moderators and cannot be seen by the banned person. BetaReleaseChannel - + Beta Beta - + No reply received from the release update server. No reply received from the release update server. - + Invalid reply received from the release update server. Invalid reply received from the release update server. - + No reply received from the file update server. No reply received from the file update server. @@ -524,7 +659,20 @@ This is only saved for moderators and cannot be seen by the banned person. CardArtPreviewWidget - + + Banner preview + + + + + + + + Zoom %1× + + + + No card selected @@ -532,22 +680,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardArtRulesModel - + Card - + ProviderId - + Mode - + Reason @@ -555,65 +703,209 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name Name - + Sets Sets - + Mana cost Mana cost - + Card type Card type - + P/T P/T - + Color(s) Color(s) + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + CardDatabaseView - + Add to Deck - + Add to Sideboard - + Select Printing - + + Add to Zone + + + + + Create &new zone... + + + + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards @@ -723,22 +1015,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoFrameWidget - + Image Image - + Description Description - + Both Both - + View transformation View transformation @@ -746,22 +1038,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards View related cards - + Add card to deck Add card to deck - + Mainboard Mainboard - + Sideboard Sideboard @@ -769,27 +1061,27 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoTextWidget - + Name: Name: - + Set: Set: - + Collector Number: Collector Number: - + Related cards: Related cards: - + Unknown card: Unknown card: @@ -797,129 +1089,129 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... Re&veal to... - + &All players &All players - + View related cards View related cards - + Token: Token: - + All tokens All tokens - + &Select All &Select All - + S&elect Row S&elect Row - + S&elect Column S&elect Column - + &Play &Play - + &Hide &Hide - + Play &Face Down Play &Face Down - + &Tap / Untap Turn sideways or back again &Tap / Untap - + Skip &untapping Skip &untapping - + T&urn Over Turn face up/face down T&urn Over - + &Peek at card face &Peek at card face - + &Clone &Clone - + Attac&h to card... Attac&h to card... - + Unattac&h Unattac&h - + &Draw arrow... &Draw arrow... - + &Set annotation... &Set annotation... - + Reduce life by power - + Ca&rd counters Ca&rd counters - + &Add counter (%1) &Add counter (%1) - + &Remove counter (%1) &Remove counter (%1) - + &Set counters (%1)... &Set counters (%1)... @@ -940,7 +1232,7 @@ This is only saved for moderators and cannot be seen by the banned person. CardSizeWidget - + Card Size Card Size @@ -1080,6 +1372,24 @@ This is only saved for moderators and cannot be seen by the banned person.%1's custom zone '%2' + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser @@ -1109,280 +1419,382 @@ This is only saved for moderators and cannot be seen by the banned person. + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + ConnectionController - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - - - - - - - - - - - + + + + + + + + + + - - + + + Error - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + Server timeout - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Information - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -1390,113 +1802,113 @@ To update your client, go to Help -> Check for Updates. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - + Requesting forgotten password to %1 as %2... - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. @@ -1546,7 +1958,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorCardDatabaseDockWidget - + Card Database Card Database @@ -1562,17 +1974,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) Search by card name (or search expressions) - + Add card to &maindeck Add card to &maindeck - + Add card to &sideboard Add card to &sideboard @@ -1580,97 +1992,158 @@ To update your client, go to Help -> Check for Updates. DeckEditorDeckDockWidget - + Loading Database... Loading Database... - + Banner Card Banner Card - + Main Type Main Type - + Mana Cost Mana Cost - + Colors Colors - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing Select Printing - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck Deck - + Deck &name: Deck &name: - + Banner Card/Tags Visibility Settings Banner Card/Tags Visibility Settings - + Show banner card selection menu Show banner card selection menu - + Show tags selection menu Show tags selection menu - + &Comments: &Comments: - + Group by: Group by: - + Format: Format: - + Hash: Hash: - + &Increment number &Increment number - + &Decrement number &Decrement number - + &Remove row &Remove row - + Swap card to/from sideboard Swap card to/from sideboard @@ -1678,17 +2151,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorFilterDockWidget - + Filters Filters - + &Clear all filters &Clear all filters - + Delete selected Delete selected @@ -1696,114 +2169,119 @@ To update your client, go to Help -> Check for Updates. DeckEditorMenu - + &Deck Editor &Deck Editor - + &New deck &New deck - + &Load deck... &Load deck... - + Load recent deck... Load recent deck... - + Clear Clear - + &Save deck &Save deck - + Save deck &as... Save deck &as... - + + Share deck... + + + + Load deck from cl&ipboard... Load deck from cl&ipboard... - + Edit deck in clipboard Edit deck in clipboard - - + + Annotated Annotated - - + + Not Annotated Not Annotated - + Save deck to clipboard Save deck to clipboard - + Annotated (No set info) Annotated (No set info) - + Not Annotated (No set info) Not Annotated (No set info) - + &Print deck... &Print deck... - + Load deck from online service... Load deck from online service... - + &Send deck to online service &Send deck to online service - + Create decklist (decklist.org) Create decklist (decklist.org) - + Create decklist (decklist.xyz) Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) Analyze deck (tappedout.net) - + &Close &Close @@ -1811,7 +2289,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorPrintingSelectorDockWidget - + Printing Selector Printing Selector @@ -1819,110 +2297,152 @@ To update your client, go to Help -> Check for Updates. DeckEditorSettingsPage - - + + Update Spoilers Update Spoilers - + Success Success - + Download URLs have been reset. Download URLs have been reset. - + Add URL Add URL - - + + URL: URL: - - + + Edit URL Edit URL - + Add New URL Add New URL - + Remove URL Remove URL - + Updating... Updating... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path Choose path - + URL Download Priority URL Download Priority - + Spoilers Spoilers - + Download Spoilers Automatically Download Spoilers Automatically - + Spoiler Location: Spoiler Location: - + Last Change Last Change - + Spoilers download automatically on launch Spoilers download automatically on launch - + Press the button to manually update without relaunching Press the button to manually update without relaunching - + Do not close settings until manual update is complete Do not close settings until manual update is complete - + Download card pictures on the fly Download card pictures on the fly - + How to add a custom URL How to add a custom URL - + Reset Download URLs Reset Download URLs @@ -1930,32 +2450,32 @@ To update your client, go to Help -> Check for Updates. DeckListHistoryManagerWidget - + Undo Undo - + Redo Redo - + Undo/Redo history Undo/Redo history - + Click on an entry to revert to that point in the history. Click on an entry to revert to that point in the history. - + [redo] [redo] - + [undo] [undo] @@ -1963,27 +2483,27 @@ To update your client, go to Help -> Check for Updates. DeckListModel - + Count Count - + Set Set - + Number Number - + Provider ID Provider ID - + Card Card @@ -2004,17 +2524,22 @@ To update your client, go to Help -> Check for Updates. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match Mode: Exact Match - + Mode: Includes Mode: Includes - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -2022,7 +2547,7 @@ To update your client, go to Help -> Check for Updates. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... Edit tags ... @@ -2030,62 +2555,62 @@ To update your client, go to Help -> Check for Updates. DeckPreviewTagDialog - + Deck Tags Manager Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) Add a new tag (e.g., Aggro️) - + Add Tag Add Tag - + Filter tags... Filter tags... - + OK OK - + Edit default tags Edit default tags - + Cancel Cancel - + Invalid Input Invalid Input - + Tag name cannot be empty! Tag name cannot be empty! - + Duplicate Tag Duplicate Tag - + This tag already exists. This tag already exists. @@ -2093,94 +2618,109 @@ To update your client, go to Help -> Check for Updates. DeckPreviewWidget - + Banner Card Banner Card - + Open in deck editor Open in deck editor - + + Share deck... + + + + Edit Tags Edit Tags - + Rename Deck Rename Deck - + Save Deck to Clipboard Save Deck to Clipboard - + Annotated Annotated - + Annotated (No set info) Annotated (No set info) - + Not Annotated Not Annotated - + Not Annotated (No set info) Not Annotated (No set info) - + Rename File Rename File - + Delete File Delete File - + Set Banner Card Set Banner Card - - + + New name: New name: - - + + Rename deck + + + + + Rename file + + + + + Error Error - + Rename failed Rename failed - + Delete file Delete file - + Are you sure you want to delete the selected file? Are you sure you want to delete the selected file? - + Delete failed Delete failed @@ -2188,57 +2728,102 @@ To update your client, go to Help -> Check for Updates. DeckStateManager - + Rename deck to "%1" from "%2" Rename deck to "%1" from "%2" - + Updated comments (was %1 chars, now %2 chars) Updated comments (was %1 chars, now %2 chars) - + Set banner card to %1 (%2) Set banner card to %1 (%2) - + + Set playmat to %1 + + + + Tags changed Tags changed - + Set format to %1 Set format to %1 - + Added (%1): %2 (%3) %4 Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) Removed "%1" (all copies) - + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + %1 1 × "%2" (%3) %1 1 × "%2" (%3) - + Added Added - + Removed Removed @@ -2260,88 +2845,116 @@ To update your client, go to Help -> Check for Updates. DeckViewContainer - + Load deck... Load deck... - + Load remote deck... Load remote deck... - + Load from clipboard... Load from clipboard... - + Load from website... Load from website... - + Unload deck Unload deck - + Ready to start Ready to start - + Force start Force start - + Sideboard unlocked Sideboard unlocked - + Sideboard locked Sideboard locked - - + + Error Error - + The selected file could not be loaded. The selected file could not be loaded. - + Deck is greater than maximum file size. Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion Deck Format Conversion - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? @@ -2350,7 +2963,7 @@ This will kick all non-ready players from the game. Do you want to convert the deck to the .cod format? - + Remember and automatically apply choice in the future Remember and automatically apply choice in the future @@ -2363,143 +2976,143 @@ This will kick all non-ready players from the game. Downloading... - + Known Hosts Known Hosts - + Delete the currently selected saved server Delete the currently selected saved server - + Refresh the server list with known public servers Refresh the server list with known public servers - + New Host New Host - + Name: Name: - + &Host: &Host: - + &Port: &Port: - + Player &name: Player &name: - + P&assword: P&assword: - + &Save password &Save password - + A&uto connect A&uto connect - + Automatically connect to the most recent login when Cockatrice opens Automatically connect to the most recent login when Cockatrice opens - + If you have any trouble connecting or registering then contact the server staff for help! If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage Webpage - + Reset Password Reset Password - + Forgot password? Forgot password? - + &Connect &Connect - + Server Server - + Login Login - + Server Contact Server Contact - + Connect to Server Connect to Server - + Server URL Server URL - + Communication Port Communication Port - + Unique Server Name Unique Server Name - + Connection Warning Connection Warning - + You need to name your new connection profile. You need to name your new connection profile. - + Connect Warning Connect Warning - + The player name can't be empty. The player name can't be empty. @@ -2507,122 +3120,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings Re&member settings - + &Description: &Description: - + P&layers: P&layers: - + General General - + Game type Game type - + &Password: &Password: - + Only &buddies can join Only &buddies can join - + Only &registered users can join Only &registered users can join - + Joining restrictions Joining restrictions - + &Spectators can watch &Spectators can watch - + Spectators &need a password to watch Spectators &need a password to watch - + Spectators can &chat Spectators can &chat - + Spectators can see &hands Spectators can see &hands - + Create game as spectator Create game as spectator - + Spectators Spectators - + Starting life total: Starting life total: - + Open decklists in lobby Open decklists in lobby - + Create game as judge Create game as judge - + Game setup options Game setup options - + &Clear &Clear - + Create game Create game - + Game information Game information - + Error Error - + Server error. Server error. @@ -2630,97 +3243,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Name: - + Token Token - + C&olor: C&olor: - + white white - + blue blue - + black black - + red red - + green green - + multicolor multicolor - + colorless colorless - + &P/T: &P/T: - + &Annotation: &Annotation: - + &Destroy token when it leaves the table &Destroy token when it leaves the table - + Create face-down (Only hides name) Create face-down (Only hides name) - + Token data Token data - + Show &all tokens Show &all tokens - + Show tokens from this &deck Show tokens from this &deck - + Choose token from list Choose token from list - + Create token Create token @@ -2728,53 +3341,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags Edit Tags - + Add Add - + Confirm Confirm - + Cancel Cancel - + Enter a tag and press Enter Enter a tag and press Enter - - + + ✖ ✖ - + Invalid Input Invalid Input - + Tag name cannot be empty! Tag name cannot be empty! - + Duplicate Tag Duplicate Tag - + This tag already exists. This tag already exists. @@ -2783,7 +3396,7 @@ This will kick all non-ready players from the game. DlgEditAvatar - + No image chosen. No image chosen. @@ -2795,27 +3408,32 @@ To remove your current avatar, confirm without choosing a new image. To remove your current avatar, confirm without choosing a new image. - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... Browse... - + Change avatar Change avatar - + Open Image Open Image - + Image Files (*.png *.jpg *.bmp) Image Files (*.png *.jpg *.bmp) - + Invalid image chosen. Invalid image chosen. @@ -2841,38 +3459,38 @@ To remove your current avatar, confirm without choosing a new image. DlgEditPassword - + Old password: Old password: - + New password: New password: - + Confirm new password: Confirm new password: - + Change password Change password - - + + Error Error - + Your password is too short. Your password is too short. - + The new passwords don't match. The new passwords don't match. @@ -2880,93 +3498,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: &Name: - + C&olor: C&olor: - + white white - + blue blue - + black black - + red red - + green green - + multicolor multicolor - + colorless colorless - + &P/T: &P/T: - + &Annotation: &Annotation: - + Token data Token data - - + + Add token Add token - + Remove token Remove token - + Edit custom tokens Edit custom tokens - + Please enter the name of the token: Please enter the name of the token: - + Error Error - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. The chosen name conflicts with an existing card or token. @@ -3045,8 +3663,9 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games that have started - Hide games that have started + Hide started games + Hide games that have started + @@ -3081,8 +3700,9 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - &Creator name: - &Creator name: + &Host name: + &Creator name: + @@ -3148,52 +3768,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning Reset Password Challenge Warning - + A problem has occurred. Please try to request a new password again. A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. Enter the information of the server and the account you'd like to request a new password for. - + &Host: &Host: - + &Port: &Port: - + Player &name: Player &name: - + Email: Email: - + Reset Password Challenge Reset Password Challenge - + Reset Password Challenge Error Reset Password Challenge Error - + The email address can't be empty. The email address can't be empty. @@ -3201,37 +3821,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. Enter the information of the server you'd like to request a new password for. - + &Host: &Host: - + &Port: &Port: - + Player &name: Player &name: - + Reset Password Request Reset Password Request - + Reset Password Error Reset Password Error - + The player name can't be empty. The player name can't be empty. @@ -3239,94 +3859,127 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning Reset Password Warning - + A problem has occurred. Please try to request a new password again. A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. Enter the received token and the new password in order to set your new password. - + &Host: &Host: - + &Port: &Port: - + Player &name: Player &name: - + Token: Token: - - + + New Password: New Password: - + Reset Password Reset Password - + The player name can't be empty. The player name can't be empty. - - - - + + + + Reset Password Error Reset Password Error - + The token can't be empty. The token can't be empty. - + The new password can't be empty. The new password can't be empty. - + Error Error - + Your password is too short. Your password is too short. - + The passwords do not match. The passwords do not match. + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck Load Deck @@ -3418,166 +4071,567 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLocalGameOptions - + Players: Players: - + General General - + Starting life total: Starting life total: - + Game setup options Game setup options - + Remember settings Remember settings - + Local game options Local game options + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): Card name (or search expressions): - + Number of hits: Number of hits: - + Auto play hits Auto play hits - + Put top cards on stack until... Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice Cockatrice - + Invalid filter Invalid filter + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: &Host: - + &Port: &Port: - + Player &name: Player &name: - + P&assword: P&assword: - + Password (again): Password (again): - + Email: Email: - + Email (again): Email (again): - + Country: Country: - + Undefined Undefined - + Real name: Real name: - + Register to server Register to server - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning Registration Warning - + Your password is too short. Your password is too short. - + Your passwords do not match, please try again. Your passwords do not match, please try again. - + Your email addresses do not match, please try again. Your email addresses do not match, please try again. - + The player name can't be empty. The player name can't be empty. + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice @@ -3643,46 +4697,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Unknown Error loading card database - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3695,7 +4715,7 @@ Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with you Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3704,7 +4724,7 @@ Would you like to change your database location setting? Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3713,7 +4733,7 @@ Would you like to change your database location setting? Would you like to change your database location setting? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3726,77 +4746,208 @@ Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues Would you like to change your database location setting? - - - + + + Error Error - + The path to your deck directory is invalid. Would you like to go back and set the correct path? The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings Settings - + + Search settings... + + + + + OK + + + + General General - + Appearance Appearance - + User Interface User Interface - + Storage - + Card Sources Card Sources - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat Chat - + Sound Sound - + Shortcuts Shortcuts + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. @@ -3805,27 +4956,27 @@ Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground Run in foreground - + Run in background Run in background - + Run in background and always from now on Run in background and always from now on - + Don't prompt again and don't run Don't prompt again and don't run - + Don't run this time Don't run this time @@ -3833,17 +4984,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next Next - + Previous Previous - + Tip of the Day Tip of the Day @@ -3851,166 +5002,176 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel Current release channel - + Reinstall Reinstall - + Cancel Download Cancel Download - + Open Download Page Open Download Page - + Check for Client Updates Check for Client Updates - - - - + + + + Error Error - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - + Downloading update: %1 Downloading update: %1 - + Checking for updates... Checking for updates... - + Finished checking for updates Finished checking for updates - + No Update Available No Update Available - + Cockatrice is up to date! Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. You are already running the latest version available in the chosen release channel. - + Current version Current version - + Selected release channel Selected release channel - - + + Update Available Update Available - - + + A new version of Cockatrice is available! A new version of Cockatrice is available! - - + + New version New version - - + + Released Released - - + + Changelog Changelog - + Do you want to update now? Do you want to update now? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error Update Error - + An error occurred while checking for updates: An error occurred while checking for updates: - + An error occurred while downloading an update: An error occurred while downloading an update: - + Installing... Installing... - + Cockatrice is unable to open the installer. Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. Try to update manually by closing Cockatrice and running the installer. - + Download location Download location @@ -4018,17 +5179,17 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing Clear log when closing - + Copy to clipboard Copy to clipboard - + Debug Log Debug Log @@ -4266,7 +5427,7 @@ You may have to manually download the new version. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: Salt: @@ -4274,7 +5435,7 @@ You may have to manually download the new version. FilterBuilder - + Type your filter here Type your filter here @@ -4282,45 +5443,88 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete Confirm Delete - + Are you sure you want to delete the filter '%1'? Are you sure you want to delete the filter '%1'? - + Delete Failed Delete Failed - + Failed to delete filter '%1'. Failed to delete filter '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator kicked by game host or moderator - + player left the game player left the game - + player disconnected from server player disconnected from server - + reason unknown reason unknown @@ -4328,140 +5532,156 @@ You may have to manually download the new version. GameSelector - - - - - - - - - + + + + + + + + + Error Error - + Please join the appropriate room first. Please join the appropriate room first. - + Wrong password. Wrong password. - + Spectators are not allowed in this game. Spectators are not allowed in this game. - + The game is already full. The game is already full. - + The game does not exist any more. The game does not exist any more. - + This game is only open to registered users. This game is only open to registered users. - + This game is only open to its creator's buddies. This game is only open to its creator's buddies. - + You are being ignored by the creator of this game. You are being ignored by the creator of this game. - + Join Game Join Game - + Spectate Game Spectate Game - + Game Information Game Information - + + Cop&y game link + + + + Join Game as Judge Join Game as Judge - + Spectate Game as Judge Spectate Game as Judge - + + Join game Join game - - Password: - Password: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. Please join the respective room first. - + &Filter games &Filter games - + C&lear filter C&lear filter - + C&reate C&reate - + &Join &Join - + Join as judge Join as judge - + J&oin as spectator J&oin as spectator - + Join as judge spectator Join as judge spectator - + Games shown: %1 / %2 Games shown: %1 / %2 - + Games Games @@ -4502,105 +5722,105 @@ You may have to manually download the new version. GamesModel - + >1 day >1 day - + %1%2 hr short age in hours %1%2 hr%1%2 hrs - + new new - + %1%2 min short age in minutes %1%2 min%1%2 mins - + password password - + buddies only buddies only - + reg. users only reg. users only - + open decklists open decklists - - + + can chat can chat - + see hands see hands - + can see hands can see hands - + not allowed not allowed - + Room Room - + Age Age - + Description Description - - Creator - Creator + + Host + - + Type Type - + Restrictions Restrictions - + Players Players - + Spectators Spectators @@ -4608,158 +5828,275 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths Reset all paths - + All paths have been reset All paths have been reset - - - - - - - + + + + English + English + + + + + + + + + Choose path Choose path - + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> + + + + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + Language settings - + Language: Language: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + Version settings - + Card database - + Startup settings - + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) Paths (editing disabled in portable mode) - + Paths Paths - + How to help with translations How to help with translations - + Decks directory: Decks directory: - + Filters directory: Filters directory: - + Replays directory: Replays directory: - + Pictures directory: Pictures directory: - + Card database: Card database: - + Custom database directory: Custom database directory: - + Token database: Token database: - + Update channel Update channel - + Check for client updates on startup Check for client updates on startup - + Check for card database updates on startup Check for card database updates on startup - + Don't check Don't check - + Prompt for update Prompt for update - + Always update in the background Always update in the background - + Check for card database updates every Check for card database updates every - + days days - + Notify if a feature supported by the server is missing in my client Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup Show tips on startup - + Last update check on %1 (%2 days ago) Last update check on %1 (%2 days ago) @@ -4767,47 +6104,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard &Graveyard - + &View graveyard &View graveyard - + &Move graveyard to... &Move graveyard to... - + &Top of library &Top of library - + &Bottom of library &Bottom of library - + &All players &All players - + &Hand &Hand - + &Exile &Exile - + Reveal random card to... Reveal random card to... @@ -4901,59 +6238,353 @@ You may have to manually download the new version. Reveal r&andom card to... + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck Create New Deck - + Browse Decks Browse Decks - + Browse Card Database Browse Card Database - + Browse EDHRec Browse EDHRec - + Browse Archidekt Browse Archidekt - + View Replays View Replays - + Quit Quit - + Connecting... Connecting... - + Connect Connect - + Play Play + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu @@ -5171,273 +6802,294 @@ You may have to manually download the new version. MainWindow - - + + Player %1 Player %1 - + Load replay Load replay - + About Cockatrice About Cockatrice - + Version Version - + Cockatrice Webpage Cockatrice Webpage - + Project Manager: Project Manager: - + Past Project Managers: Past Project Managers: - + Developers: Developers: - + Our Developers Our Developers - + Help Develop! Help Develop! - + Translators: Translators: - + Our Translators Our Translators - + Help Translate! Help Translate! - + Support: Support: - + Report an Issue Report an Issue - + Troubleshooting Troubleshooting - + F.A.Q. F.A.Q. - - + + Error Error - + &Connect... &Connect... - + &Disconnect &Disconnect - + Start &local game... Start &local game... - + &Watch replay... &Watch replay... - + &Full screen &Full screen - + &Register to server... &Register to server... - + &Restore password... &Restore password... - + &Settings... &Settings... - + &Exit &Exit - + A&ctions A&ctions - + &Cockatrice &Cockatrice - + C&ard Database C&ard Database - + &Manage sets... &Manage sets... - + Edit custom &tokens... Edit custom &tokens... - + Open custom image folder Open custom image folder - + Open custom sets folder Open custom sets folder - + Add custom sets/cards Add custom sets/cards - + Reload card database Reload card database - + Tabs Tabs - + &Help &Help - + &About Cockatrice &About Cockatrice - + &Tip of the Day &Tip of the Day - + Check for Client Updates Check for Client Updates - + Check for Card Updates... Check for Card Updates... - + Check for Card Updates (Automatic) Check for Card Updates (Automatic) - + Show Status Bar Show Status Bar - + View &Debug Log View &Debug Log - + Open Settings Folder Open Settings Folder - + + Re-run Onboarding Wizard... + + + + Show/Hide Show/Hide - + + Sign-in cancelled + + + + New Version New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + + Are you sure? + + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5446,29 +7098,29 @@ Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes Yes - - + + No No - + Open settings Open settings - + New sets found New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5479,22 +7131,22 @@ Set codes: %1 Do you want to enable them? - + Yes, always enable - + View sets View sets - + Welcome Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5503,90 +7155,90 @@ All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - + Information Information - + A card database update is already running. A card database update is already running. - + Unable to run the card database updater: Unable to run the card database updater: - + Card database update running. Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. Unknown error occurred. - + The card database updater exited with an error: %1 The card database updater exited with an error: %1 - - - - - + + + + + Load sets/cards Load sets/cards - + Selected file cannot be found. Selected file cannot be found. - + You can only import XML databases at this time. You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. Sets/cards failed to import. @@ -5791,615 +7443,615 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play from play - + from their graveyard from their graveyard - + from exile from exile - + from their hand from their hand - + the top card of %1's library the top card of %1's library - + the top card of their library the top card of their library - + from the top of %1's library from the top of %1's library - + from the top of their library from the top of their library - + the bottom card of %1's library the bottom card of %1's library - + the bottom card of their library the bottom card of their library - + from the bottom of %1's library from the bottom of %1's library - + from the bottom of their library from the bottom of their library - + from %1's library from %1's library - + from their library from their library - + from sideboard from sideboard - + from the stack from the stack - + from custom zone '%1' from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 is now keeping the top card %2 revealed. - + %1 is not revealing the top card %2 any longer. %1 is not revealing the top card %2 any longer. - + %1 can now look at top card %2 at any time. %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. %1 attaches %2 to %3's %4. - + %1 has conceded the game. %1 has conceded the game. - + %1 has unconceded the game. %1 has unconceded the game. - + %1 has restored connection to the game. %1 has restored connection to the game. - + %1 has lost connection to the game. %1 has lost connection to the game. - + %1 points from their %2 to themselves. %1 points from their %2 to themselves. - + %1 points from their %2 to %3. %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. %1 creates a face down token. - + %1 creates token: %2%3. %1 creates token: %2%3. - + %1 has loaded a deck (%2). %1 has loaded a deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 has loaded a deck with %2 sideboard cards (%3). - + %1 destroys %2. %1 destroys %2. - + a card a card - + %1 gives %2 control over %3. %1 gives %2 control over %3. - + %1 puts %2 into play%3 face down. %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1 puts %2 into play%3. - + %1 puts %2%3 into their graveyard face down. %1 puts %2%3 into their graveyard face down. - + %1 puts %2%3 into their graveyard. %1 puts %2%3 into their graveyard. - + %1 exiles %2%3 face down. %1 exiles %2%3 face down. - + %1 exiles %2%3. %1 exiles %2%3. - + %1 moves %2%3 to their hand. %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. %1 moves %2%3 to sideboard. - + %1 plays %2%3 face down. %1 plays %2%3 face down. - + %1 plays %2%3. %1 plays %2%3. - + %1 moves %2%3 to custom zone '%4' face down. %1 moves %2%3 to custom zone '%4' face down. - + %1 moves %2%3 to custom zone '%4'. %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1 tries to draw from an empty library - + %1 draws %2 card(s). %1 draws %2 card.%1 draws %2 cards. - + %1 is looking at %2. %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 is looking at the %4 %3 card %2.%1 is looking at the %4 %3 cards %2. - + bottom bottom - + top top - + %1 turns %2 face-down. %1 turns %2 face-down. - + %1 turns %2 face-up. %1 turns %2 face-up. - + The game has been closed. The game has been closed. - + The game has started. The game has started. - + You are flooding the game. Please wait a couple of seconds. You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 has joined the game. - + %1 is now watching the game. %1 is now watching the game. - + You have been kicked out of the game. You have been kicked out of the game. - + %1 has left the game (%2). %1 has left the game (%2). - + %1 is not watching the game any more (%2). %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 shuffles their deck and draws a card.%1 shuffles their deck and draws a new hand of %2 cards. - + %1 shuffles their deck and draws a new hand. %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. You are watching a replay of game #%1. - + %1 is ready to start the game. %1 is ready to start the game. - + cards an unknown amount of cards cards - + %1 card(s) a card for singular, %1 cards for plural one card%1 cards - + %1 lends %2 to %3. %1 lends %2 to %3. - + %1 reveals %2 to %3. %1 reveals %2 to %3. - + %1 reveals %2. %1 reveals %2. - + %1 randomly reveals %2%3 to %4. %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. %1 reveals %2%3 to %4. - + %1 reveals %2%3. %1 reveals %2%3. - + %1 reversed turn order, now it's %2. %1 reversed turn order, now it's %2. - + reversed reversed - + normal normal - + Heads Heads - + Tails Tails - + %1 flipped a coin. It landed as %2. %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. %1's turn. - + %1 sets annotation of %2 to %3. %1 sets annotation of %2 to %3. - + %1 places %2 %3%4 counter(s) on %5 (now %6). - + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + %1 failed to undo their last draw. - + %1 sets counter %2 to %3 (%4%5). %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. %1 sets %2 to untap normally. - + %1 removes the PT of %2. %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. %1 has locked their sideboard. - + %1 has unlocked their sideboard. %1 has unlocked their sideboard. - + %1 taps their permanents. %1 taps their permanents. - + %1 untaps their permanents. %1 untaps their permanents. - + %1 taps %2. %1 taps %2. - + %1 untaps %2. %1 untaps %2. - + %1 shuffles %2. %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. %1 unattaches %2. - + %1 undoes their last draw. %1 undoes their last draw. - + %1 undoes their last draw (%2). %1 undoes their last draw (%2). @@ -6407,115 +8059,125 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 Word1 Word2 Word3 - + Add New Message Add New Message - + Edit Message Edit Message - + Remove Message Remove Message - + Add message Add message - - + + Message: Message: - + Edit message Edit message - + Chat settings Chat settings - + Custom alert words Custom alert words - + Enable chat mentions Enable chat mentions - + Enable mention completer Enable mention completer - + In-game message macros In-game message macros - + How to use in-game message macros How to use in-game message macros - + Ignore chat room messages sent by unregistered users Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users Ignore private messages sent by unregistered users - + Ignore private messages sent by non-buddy users - - + + Invert text color Invert text color - + Enable desktop notifications for private messages Enable desktop notifications for private messages - + Enable desktop notification for mentions Enable desktop notification for mentions - + Enable room message history on join Enable room message history on join - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only Separate words with a space, alphanumeric characters only @@ -6523,42 +8185,42 @@ Cockatrice will now reload the card database. MoveMenu - + Move to Move to - + &Top of library in random order &Top of library in random order - + X cards from the top of library... X cards from the top of library... - + &Bottom of library in random order &Bottom of library in random order - + T&able T&able - + &Hand &Hand - + &Graveyard &Graveyard - + &Exile &Exile @@ -6637,148 +8299,134 @@ Cockatrice will now reload the card database. PaletteEditorDialog - - + + Reset - - - Apply - - - - - - Save && Apply - - - - + ▼ Edit Palette - - + + ▶ Edit Palette - + Palette Editor — %1 - + <b>Palette Editor</b> &nbsp;·&nbsp; %1 - - This theme ships no default palette files - - - - - Replace current colours with the theme author's defaults - - - - - Switch between the light and dark palette files + + + Save - Editing: + Delete this scheme's custom palette and revert to the theme default (or the application palette) - Show or hide the per-role colour grid for manual tweaks + Switch between the light and dark palette files + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + ↺ Revert to theme default - + Discard unsaved edits and restore the last saved palette - - Preview this palette without saving to disk + + Cannot save: this theme has no writable directory - + Write palette-%1.toml and reload the theme - - Cannot save: this theme has no directory on disk - - - - + + Save failed - + + Could not write %1 to: %2 - - - No default found - - - - - No default palette file found for the "%1" scheme. - - PaletteGridWidget - + Active - + Disabled - + Inactive - + Normal interactive state - + Widget is disabled / not interactive - + Window is in background / unfocused + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + Phase @@ -6901,30 +8549,21 @@ Cockatrice will now reload the card database. End of turn step - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - en - - PlayerActions - - - - + + + + grave grave - - - - + + + + exile exile @@ -7043,16 +8682,272 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" Player "%1" - + &Counters &Counters + + PlaymatCollectionDialog + + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + PrintingDisabledInfoWidget @@ -7077,12 +8972,12 @@ This setting means you'll only see the default printing for each card, inst PrintingSelector - + Display Navigation Buttons Display Navigation Buttons - + Printing Selector Printing Selector @@ -7090,25 +8985,65 @@ This setting means you'll only see the default printing for each card, inst PrintingSelectorCardOverlayWidget - + Preference Preference - + Pin Printing Pin Printing - + Unpin Printing Unpin Printing - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards Show Related cards + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -7139,28 +9074,28 @@ This setting means you'll only see the default printing for each card, inst PrintingSelectorCardSortingWidget - + Alphabetical Alphabetical - + Preference Preference - + Release Date Release Date - - + + Descending Descending - + Ascending Ascending @@ -7176,95 +9111,103 @@ This setting means you'll only see the default printing for each card, inst PtMenu - + Power / toughness Power / toughness - + &Increase power &Increase power - + &Decrease power &Decrease power - + I&ncrease toughness I&ncrease toughness - + D&ecrease toughness D&ecrease toughness - + In&crease power and toughness In&crease power and toughness - + Dec&rease power and toughness Dec&rease power and toughness - + Increase power and decrease toughness Increase power and decrease toughness - + Decrease power and increase toughness Decrease power and increase toughness - + Set &power and toughness... Set &power and toughness... - + Reset p&ower and toughness Reset p&ower and toughness + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services Services - + Hide %1 Hide %1 - + Hide Others Hide Others - + Show All Show All - + Preferences... Preferences... - + Quit %1 Quit %1 - + About %1 About %1 @@ -7272,17 +9215,17 @@ This setting means you'll only see the default printing for each card, inst QObject - + Cockatrice card database (*.xml) Cockatrice card database (*.xml) - + All files (*.*) All files (*.*) - + Cockatrice replays (*.cor) Cockatrice replays (*.cor) @@ -7302,17 +9245,22 @@ This setting means you'll only see the default printing for each card, inst Tokens - + + Maybeboard + + + + Overwrite Existing File? Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? A .cod version of this deck already exists. Overwrite it? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7329,7 +9277,7 @@ You will have to use the Set Manager, available through Card Database -> Mana Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7346,100 +9294,110 @@ You can also use the Set Manager to adjust custom sort order for printings in th Are you sure you would like to disable this feature? - + Confirm Change Confirm Change + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK OK - + Save Save - + Save All Save All - + Open Open - + &Yes &Yes - + Yes to &All Yes to &All - + &No &No - + N&o to All N&o to All - + Abort Abort - + Retry Retry - + Ignore Ignore - + Close Close - + Cancel Cancel - + Discard Discard - + Help Help - + Apply Apply - + Reset Reset - + Restore Defaults Restore Defaults @@ -7447,121 +9405,189 @@ Are you sure you would like to disable this feature? QuickSetupPanel - + %1% - + <b>Quick Setup</b> - + Generate all palette roles automatically from a single accent colour - + Accent: - + Primary hue. Used directly for highlights and links. At high intensity it also tints buttons and backgrounds. - + Intensity: - + Subtle - + Full colour - + 0–30 Subtle tint — only highlights and links change hue 30–70 Accented — buttons, tooltips, and borders join in 70–100 Full colour — backgrounds, everything - + 70% - - - Generate ↓ - - - - - Derive all palette roles from the accent colour above. -Fine-tune individual colours in the grid afterwards. - - RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name Name - + ID ID - + Upload time Upload time + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID ID - + Name Name - + Players Players - + Keep Keep - + Time started Time started - + Duration (sec) Duration (sec) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu @@ -7603,37 +9629,37 @@ Fine-tune individual colours in the grid afterwards. RoomSelector - + Rooms Rooms - + Joi&n Joi&n - + Room Room - + Description Description - + Permissions Permissions - + Players Players - + Games Games @@ -7641,7 +9667,7 @@ Fine-tune individual colours in the grid afterwards. SayMenu - + S&ay S&ay @@ -7649,32 +9675,32 @@ Fine-tune individual colours in the grid afterwards. SequenceEdit - + Choose an action from the table Choose an action from the table - + Hit the key/combination of keys you want to set for this action Hit the key/combination of keys you want to set for this action - + Invalid key Invalid key - + Shortcut already in use by: Shortcut already in use by: - + Clear Clear - + Restore default Restore default @@ -7707,56 +9733,92 @@ Fine-tune individual colours in the grid afterwards. Release date + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts Restore all default shortcuts - + Do you really want to restore all default shortcuts? Do you really want to restore all default shortcuts? - + Clear all default shortcuts Clear all default shortcuts - + Do you really want to clear all shortcuts? Do you really want to clear all shortcuts? - + Section: Section: - + Action: Action: - + Shortcut: Shortcut: - + + Shortcut editor + + + + How to set custom shortcuts How to set custom shortcuts - + Clear all shortcuts Clear all shortcuts - + Search by shortcut name Search by shortcut name @@ -7812,12 +9874,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard &Sideboard - + &View sideboard &View sideboard @@ -7825,27 +9887,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Enable &sounds - + Current sounds theme: Current sounds theme: - + Test system sound engine Test system sound engine - + Sound settings Sound settings - + Master volume Master volume @@ -7853,48 +9915,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed Spoilers download failed - + No internet connection No internet connection - + Error Error - + Spoilers already up to date Spoilers already up to date - + No new spoilers added No new spoilers added - + Spoilers have been updated! Spoilers have been updated! - + Last change: Last change: @@ -7902,157 +9964,170 @@ Please check your shortcut settings! StableReleaseChannel - + Default Default - + No reply received from the release update server. No reply received from the release update server. - + Invalid reply received from the release update server. Invalid reply received from the release update server. - + No reply received from the tag update server. No reply received from the tag update server. - + Invalid reply received from the tag update server. Invalid reply received from the tag update server. + + StatsTally + + + Total Power + + + + + Total Toughness + + + StorageSettingsPage - - - + + + Success - + Cached card pictures have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + In-memory (currently loaded) card pictures have been reset. - + Card Picture Loader Caching Method: - + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. - + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. - + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. - + Delete Cached Images - + Delete Saved Images - + Clear In-Memory Images - + Card Picture Loader Cache Method - + Network Cache - + Filesystem - + In-Memory Picture Cache - + Network Cache Size: - + On-disk cache for downloaded pictures - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + In-memory cache for pictures not currently on screen - + Naming scheme: - + Day(s) @@ -8060,17 +10135,33 @@ Please check your shortcut settings! TabAccount - + + + My Reports + + + + Add to Buddy List Add to Buddy List - + Add to Ignore List Add to Ignore List - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account Account @@ -8191,118 +10282,118 @@ Please check your shortcut settings! TabArchidekt - - - + + + Desc. Desc. - - + + AND AND - - + + Require ALL selected colors Require ALL selected colors - - + + Deck name... Deck name... - - + + Owner... Owner... - - + + Packages Packages - - + + Advanced Filters Advanced Filters - + Bracket: Bracket: - - + + Any Any - - + + Contains card... Contains card... - - + + Commander... Commander... - - + + Tag... Tag... - - + + Deck Size Deck Size - + Cards: Cards: - - + + Asc. Asc. - + Sort by: Sort by: - + Filter by: Filter by: - + Display Settings Display Settings - - + + Search Search - - + + Formats Formats @@ -8315,42 +10406,42 @@ Please check your shortcut settings! TabCardArtRules - + Card: - + ProviderId: - + Mode: - + Reason: - + Type a card name... - + Add rule - + Remove rule - + Refresh @@ -8363,52 +10454,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info Card Info - + Deck Deck - + Filters Filters - + &View &View - + Card Database Card Database - + Printing Printing - + Visible Visible - + Floating Floating - + Reset layout Reset layout - + Deck: %1 Deck: %1 @@ -8416,49 +10507,49 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 Visual Deck: %1 - + &Visual Deck Editor &Visual Deck Editor - - + + Card Info Card Info - - + + Deck Deck - + &View &View - + Printing Printing - + Visible Visible - + Floating Floating - + Reset layout Reset layout @@ -8466,22 +10557,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View Visual Deck View - + Visual Database Display Visual Database Display - + Deck Analytics Deck Analytics - + Sample Hand Sample Hand @@ -8489,139 +10580,237 @@ Please check your shortcut settings! TabDeckStorage - + Local file system Local file system - + Server deck storage Server deck storage - - + + Open in deck editor Open in deck editor - + Rename deck or folder Rename deck or folder - + Upload deck Upload deck - + Download deck Download deck - - - - + + + + New folder New folder - - + + Delete Delete - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder Open decks folder - + Rename local folder Rename local folder - + Rename local file Rename local file - + New name: New name: - - - - + + + + + + + Error Error - + Rename failed Rename failed - - + + + Invalid deck file Invalid deck file - + Enter deck name Enter deck name - + This decklist does not have a name. Please enter a name: This decklist does not have a name. Please enter a name: - + Unnamed deck Unnamed deck - + Failed to upload deck to server Failed to upload deck to server - + Delete local file Delete local file - + Are you sure you want to delete the selected files? Are you sure you want to delete the selected files? - + Delete remote decks Delete remote decks - + Are you sure you want to delete the selected decks? Are you sure you want to delete the selected decks? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: Name of new folder: - + Deck Storage Deck Storage @@ -8629,20 +10818,271 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage Visual Deck Storage - + Error Error - + Could not open deck at %1 Could not open deck at %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -8655,27 +11095,27 @@ Please enter a name: TabEdhRecMain - + &Cards &Cards - + Top Commanders Top Commanders - + Tags Tags - + Search for a card ... Search for a card ... - + Search Search @@ -8688,191 +11128,207 @@ Please enter a name: TabGame - - - + + + Replay Replay - - + + Game Game - - + + Player List Player List - - + + + Invite + + + + + Card Info Card Info - - + + Messages Messages - - + + Replay Timeline Replay Timeline - + &Phases &Phases - + &Game &Game - + Next &phase Next &phase - + Next phase with &action Next phase with &action - + Next &turn Next &turn - + Reverse turn order Reverse turn order - + &Remove all local arrows &Remove all local arrows - + Rotate View Cl&ockwise Rotate View Cl&ockwise - + Rotate View Co&unterclockwise Rotate View Co&unterclockwise - + Game &information Game &information - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede Un&concede - - - + + + &Concede &Concede - + &Leave game &Leave game - + C&lose replay C&lose replay - + &Focus Chat &Focus Chat - + &Say: &Say: - + Selected cards Selected cards - + &View &View - + Visible Visible - + Floating Floating - + Reset layout Reset layout - + Concede Concede - + Are you sure you want to concede this game? Are you sure you want to concede this game? - + Unconcede Unconcede - + You have already conceded. Do you want to return to this game? You have already conceded. Do you want to return to this game? - + Leave game Leave game - + Are you sure you want to leave this game? Are you sure you want to leave this game? - + A player has joined game #%1 A player has joined game #%1 - + %1 has joined the game %1 has joined the game - + You have been kicked out of the game. You have been kicked out of the game. @@ -8888,158 +11344,158 @@ Please enter a name: TabLog - + Logs Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs Room Logs - + Game Logs Game Logs - + Chat Logs Chat Logs - - + + Error Error - + You must select at least one filter. You must select at least one filter. - + You have to select a valid number of days to locate. You have to select a valid number of days to locate. - + Username: Username: - + IP Address: IP Address: - + Game Name: Game Name: - + GameID: GameID: - + Message: Message: - + Main Room Main Room - + Game Room Game Room - + Private Chat Private Chat - + Past X Days: Past X Days: - + Today Today - + Last Hour Last Hour - + Maximum Results: Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs Get User Logs - + Clear Filters Clear Filters - + Filters Filters - + Log Locations Log Locations - + Date Range Date Range - + Maximum Results Maximum Results - - + + Message History Message History - + Failed to collect message history information. Failed to collect message history information. - + There are no messages for the selected filters. There are no messages for the selected filters. @@ -9047,219 +11503,559 @@ The more information you put in, the more specific your results will be. TabMessage - + Private &chat Private &chat - + &Leave &Leave - + %1 - Private chat %1 - Private chat - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from Private message from - + %1 has left the server. %1 has left the server. - + %1 has joined the server. %1 has joined the server. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system Local file system - + Server replay storage Server replay storage - - + + Watch replay Watch replay - + Rename Rename - - + + New folder New folder - - + + Delete Delete - + Open replays folder Open replays folder - + Download replay Download replay - + Toggle expiration lock Toggle expiration lock - + Get replay share code Get replay share code - - + + Look up replay by share code Look up replay by share code - + Rename local folder Rename local folder - + Rename local file Rename local file - + New name: New name: - + Error Error - + Rename failed Rename failed - + Name of new folder: Name of new folder: - + Delete local file Delete local file - + Are you sure you want to delete the selected files? Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? Are you sure you want to delete the selected replays? - + Failed to get code Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed Failed - + Could not get replay code Could not get replay code - + Replay Share Code Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard Copy to clipboard - + Replay share code Replay share code - + Replay code found Replay code found - + Replay was added, or you already had access to it. Replay was added, or you already had access to it. - + Replay code not found Replay code not found - + Failed to submit code Failed to submit code - + Unexpected error Unexpected error - + Delete remote replay Delete remote replay @@ -9269,65 +12065,478 @@ The more information you put in, the more specific your results will be.Game Replays + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - - Friends - - - - - Online - - - - - Ignored - - - - + &Say: &Say: - + Chat Chat - + &Room &Room - + &Leave room &Leave room - + &Clear chat &Clear chat - + Chat Settings... Chat Settings... - + mentioned you. mentioned you. - + Click to view Click to view - + You are flooding the chat. Please wait a couple of seconds. You are flooding the chat. Please wait a couple of seconds. @@ -9335,35 +12544,35 @@ The more information you put in, the more specific your results will be. TabServer - + Server Server - - - - + + + + Error Error - + Failed to join the server room: it doesn't exist on the server. Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. Failed to join the server room due to an unknown error: %1. @@ -9371,97 +12580,173 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor Deck Editor - + Visual Deck Editor Visual Deck Editor - + EDHRec EDHRec - + Archidekt Archidekt - + Home Home - + &Visual Deck Storage &Visual Deck Storage - + Visual Database Display Visual Database Display - + Server Server - + Account Account - + Deck Storage Deck Storage - + Game Replays Game Replays - + Administration Administration - + Logs Logs - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? Are you sure? - + There are still open games. Are you sure you want to quit? There are still open games. Are you sure you want to quit? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view Click to view - + Your buddy %1 has signed on! Your buddy %1 has signed on! - + Unknown Event Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -9472,39 +12757,39 @@ This message might mean there is a new version of Cockatrice available or this s To update your client, go to Help -> Check for Updates. - + Idle Timeout Idle Timeout - + You are about to be logged out due to inactivity. You are about to be logged out due to inactivity. - + Promotion Promotion - + You have been promoted. Please log out and back in for changes to take effect. You have been promoted. Please log out and back in for changes to take effect. - + Warned Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) You have received the following message from the server. @@ -9524,6 +12809,34 @@ Please refrain from engaging in this activity or further actions may be taken ag Visual Database Display + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -9538,6 +12851,65 @@ Please refrain from engaging in this activity or further actions may be taken ag Unable to analyze the deck. + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -9609,57 +12981,52 @@ Please refrain from engaging in this activity or further actions may be taken ag UserCardArtSettingsDialog - + Card Art Settings - + Type a card name... - + Card name: - - Card ProviderId: + + Printing: - + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + Left margin (%): - + Right margin (%): - - Vertical offset: - - - - - Zoom: - - - - - Parameters - - - - - Preview - - - - + Remove Banner Card @@ -9667,216 +13034,266 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details User &details - + Private &chat Private &chat - + Show this user's &games Show this user's &games - + + View this user's &public decks + + + + Add to &buddy list Add to &buddy list - + Remove from &buddy list Remove from &buddy list - + Add to &ignore list Add to &ignore list - + Remove from &ignore list Remove from &ignore list - + Kick from &game Kick from &game - + + Report user + + + + Warn user Warn user - + View user's war&n history View user's war&n history - + Ban from &server Ban from &server - + View user's &ban history View user's &ban history - + &Promote user to moderator &Promote user to moderator - + Dem&ote user from moderator Dem&ote user from moderator - + Promote user to &judge Promote user to &judge - + Demote user from judge Demote user from judge - + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes View admin notes - - + Investigate user + + + + + + Error Error - + This user does not exist. This user does not exist. - + You are being ignored by %1 and can't see their games. You are being ignored by %1 and can't see their games. - + Could not get %1's games. Could not get %1's games. - + %1's games %1's games - - - + + + Ban History Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. User has never been banned. - + Failed to collect ban information. Failed to collect ban information. - - - + + + Warning History Warning History - + Warning Time;Moderator;User Name;Reason Warning Time;Moderator;User Name;Reason - + User has never been warned. User has never been warned. - + Failed to collect warning information. Failed to collect warning information. - + Failed to get admin notes. Failed to get admin notes. - - + + Success Success - + Successfully promoted user. Successfully promoted user. - + Successfully demoted user. Successfully demoted user. - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + Kick Player - + Are you sure you want to kick this player from the game? - - - + + + Failed Failed - + Failed to promote user. Failed to promote user. - + Failed to demote user. Failed to demote user. - + Copy hash to clipboard Copy hash to clipboard - + Remove this user's messages Remove this user's messages @@ -9914,61 +13331,61 @@ Please refrain from engaging in this activity or further actions may be taken ag Administrator - + Moderator Moderator - + Registered user Registered user - - + + Unregistered user Unregistered user - + Judge Judge - + Unknown Unknown - + The entered password does not match your account. The entered password does not match your account. - - - - + + + + Information Information - + User information updated. User information updated. - - - - - - + + + + + - - - + + + + Error Error @@ -9992,283 +13409,292 @@ Please refrain from engaging in this activity or further actions may be taken ag Edit Banner Card + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) %n Year, %n Years, - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format %10%n Day %20%10%n Days %20 - + Enter Password Enter Password - + Password verification is required in order to change your email address Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. An error occurred while trying to update your user information. - + The selected card is blacklisted on this server or another error occurred. - + Banner card removed. - + Banner card updated. - + This server does not permit you to update your user informations. This server does not permit you to update your user informations. - + Password changed. Password changed. - + This server does not permit you to change your password. This server does not permit you to change your password. - + The new password is too short. The new password is too short. - + The old password is incorrect. The old password is incorrect. - + Avatar updated. Avatar updated. - + This server does not permit you to update your avatar. This server does not permit you to update your avatar. - + An error occured while trying to update your avatar. - - - This server does not permit you to update your user informations. - An error occured while trying to updater your avatar. - This server does not permit you to update your user informations. - UserInfoPopup - - + + Games - + Chat - + Open private chat - + Profile - + View user profile - + Show this user's games - + + Invite + + + + + Invite to your game + + + + − Buddy - + Remove from buddy list - + + Buddy - + Add to buddy list - + − Ignore - + Remove from ignore list - + + Ignore - + Add to ignore list - + Ban - + Ban from server - + Warn - + Warn user - + Ban log - + View ban history - + Warn log - + View warning history - + Notes - + View admin notes - + − Mod - + Demote from moderator - + + Mod - + Promote to moderator - + − Judge - + Demote from judge - + + Judge - + Promote to judge - + Join game - + Spectate - - + + Loading games… - + Could not load games. - + No active games. @@ -10276,223 +13702,344 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings General interface settings - + &Double-click cards to play them (instead of single-click) &Double-click cards to play them (instead of single-click) - + &Clicking plays all selected cards (instead of just the clicked card) &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default &Play all nonlands onto the stack (not the battlefield) by default - + Do not delete &arrows inside of subphases Do not delete &arrows inside of subphases - + Close card view window when last card is removed Close card view window when last card is removed - + Auto focus search bar when card view window is opened Auto focus search bar when card view window is opened - + Annotate card text on tokens Annotate card text on tokens - + Show selection count during drag selection - + Show total selection count - - Show subtype breakdown in selection tally + + + Disabled - + + + Enabled + + + + + + Automatic + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + Use tear-off menus, allowing right click menus to persist on screen Use tear-off menus, allowing right click menus to persist on screen - + Keep game chat focused when clicking in game (Note: disables card view search bar) - + Notifications settings Notifications settings - + Enable notifications in taskbar Enable notifications in taskbar - + Notify in the taskbar for game events while you are spectating Notify in the taskbar for game events while you are spectating - + Notify in the taskbar when users in your buddy list connect Notify in the taskbar when users in your buddy list connect - + Animation settings Animation settings - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation &Tap/untap animation - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings Deck editor/storage settings - + Open deck in new tab by default Open deck in new tab by default - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby Use visual deck storage in game lobby - + Use selection animation for Visual Deck Storage Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: When adding a tag in the visual deck storage to a .txt deck: - - do nothing - do nothing + + Do nothing + - - ask to convert to .cod - ask to convert to .cod + + Ask to convert to .cod + - - always convert to .cod - always convert to .cod + + Always convert to .cod + - + Default deck editor type Default deck editor type - + Classic Deck Editor Classic Deck Editor - + Visual Deck Editor Visual Deck Editor - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings Replay settings - + Buffer time for backwards skip via shortcut: Buffer time for backwards skip via shortcut: + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 Users connected to server: %1 - + Users in this room: %1 Users in this room: %1 - + Buddies online: %1 / %2 Buddies online: %1 / %2 - + Ignored users online: %1 / %2 Ignored users online: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters Increment all card counters - + &Untap all permanents &Untap all permanents - + R&oll die... R&oll die... - + Flip coin - + &Create token... &Create token... - + C&reate another token C&reate another token - + Cr&eate predefined token Cr&eate predefined token @@ -10529,22 +14076,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... Search filter... - + Save Filter Save Filter - + Save all currently applied filters to a file Save all currently applied filters to a file - + Enter filename... Enter filename... @@ -10552,72 +14099,72 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterToolbarWidget - + Sort by Sort by - + Filter by Filter by - + Save and load filters Save and load filters - + Filter by exact card name Filter by exact card name - + Filter by card main-type Filter by card main-type - + Filter by card sub-type Filter by card sub-type - + Filter by set Filter by set - + Filter by format legality Filter by format legality - + Save/Load Save/Load - + Name Name - + Main Type Main Type - + Sub Type Sub Type - + Sets Sets - + Formats Formats @@ -10719,7 +14266,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets Filter to most recent sets @@ -10727,19 +14274,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... Search sets... - - + + Mode: Exact Match Mode: Exact Match - - + + Mode: Includes Mode: Includes @@ -10785,28 +14332,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) Search by card name (or search expressions) - - + + Visual Visual - + Loading database ... Loading database ... - + Clear all filters Clear all filters - + Table Table @@ -10814,43 +14361,43 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: Group by: - + Change how cards are divided into categories/groups. Change how cards are divided into categories/groups. - + Sort by: Sort by: - + Click and drag to change the sort order within the groups Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat Toggle Layout: Flat @@ -10866,12 +14413,12 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorSampleHandWidget - + Draw a new sample hand Draw a new sample hand - + Sample hand size Sample hand size @@ -10879,17 +14426,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... Type a card name here for suggestions from the database... - + Quick search and add card Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -10897,7 +14444,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageFolderDisplayWidget - + Deck Storage Deck Storage @@ -10905,52 +14452,57 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders Show Folders - + Show Tag Filter Show Tag Filter - + Show Color Identity Show Color Identity - + Show Tags On Deck Previews Show Tags On Deck Previews - + + Show Upload Time + + + + Show Banner Card Selection Option Show Banner Card Selection Option - + Draw unused Color Identities Draw unused Color Identities - + Unused Color Identities Opacity Unused Color Identities Opacity - + Deck tooltip: Deck tooltip: - + None None - + Filepath Filepath @@ -10958,7 +14510,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) Search by filename (or search expression) @@ -10966,22 +14518,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) Sort Alphabetically (Filename) - + Sort by Last Modified Sort by Last Modified - + Sort by Last Loaded Sort by Last Loaded @@ -10989,17 +14541,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... Loading database ... - + Refresh loaded files Refresh loaded files - + + Select decks to share + + + + Visual Deck Storage Settings Visual Deck Storage Settings @@ -11007,177 +14564,202 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? Which warning would you like to send? - + Redact all messages from this user in all rooms Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Cancel - + Warn user for misconduct Warn user for misconduct - - + + Error Error - + User name to send a warning to can not be blank, please specify a user to warn. User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. Warning to use can not be blank, please select a valid warning to send. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top Move selected set to the top - + Move selected set up Move selected set up - + Move selected set down Move selected set down - + Move selected set to the bottom Move selected set to the bottom - - Search by set name, code, or type - Search by set name, code, or type - - - + Default order Default order - + Restore original art priority order Restore original art priority order - + Enable all sets Enable all sets - + Disable all sets Disable all sets - + Enable selected set(s) Enable selected set(s) - + Disable selected set(s) Disable selected set(s) - + Deck Editor Deck Editor - + Use CTRL+A to select all sets in the view. Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) - + Include cards rebalanced for Alchemy [requires restart] Include cards rebalanced for Alchemy [requires restart] - + Card Art Card Art - + + Search by set name, code, type, or release date + + + + How to use custom card art How to use custom card art - + Hints Hints - + Note Note - + Sorting by column allows you to find a set while not changing set priority. Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead Use the current sorting as the set priority instead - + Sorts the set priority using the same column Sorts the set priority using the same column - + Manage sets Manage sets @@ -11185,93 +14767,85 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) Search by card name (or search expressions) - + Ungrouped Ungrouped - + Group by Type Group by Type - + Group by Mana Value Group by Mana Value - + Group by Color Group by Color - + Unsorted Unsorted - + Sort by Name Sort by Name - + Sort by Type Sort by Type - + Sort by Mana Cost Sort by Mana Cost - + Sort by Colors Sort by Colors - + Sort by P/T Sort by P/T - + Sort by Set Sort by Set - + shuffle when closing shuffle when closing - + pile view pile view - - i18n - - - English - English - - main - + Connect on startup Connect on startup - + Debug to file Debug to file @@ -12055,6 +15629,16 @@ Please refrain from engaging in this activity or further actions may be taken ag Graveyard Graveyard + + + Report Queue + + + + + Moderation + + diff --git a/cockatrice/translations/cockatrice_es.ts b/cockatrice/translations/cockatrice_es.ts index 2a0e479c3..718e30f82 100644 --- a/cockatrice/translations/cockatrice_es.ts +++ b/cockatrice/translations/cockatrice_es.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Establecer contador... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Establecer contador - + New value for counter '%1': Nuevo valor para el contador '%1': @@ -36,64 +36,113 @@ AbstractTabDeckEditor - + Open in new tab Abrir en nueva pestaña - + Are you sure? ¿Estás seguro? - + The decklist has been modified. Do you want to save the changes? La lista del mazo ha sido modificada. ¿Deseas guardar los cambios? - - - - - - + + + + + + Error Error - + Could not open deck at %1 No se pudo abrir mazo en %1 - + Could not save remote deck No se pudo guardar mazo remoto - - + + The deck could not be saved. Please check that the directory is writable and try again. - + El mazo no pudo ser guardado. Por favor revisa los permisos de escritura del directorio e intenta de nuevo. - + Save deck Guardar mazo - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. El mazo no puede ser guardado. - + There are no cards in your deck to be exported No hay cartas en tu mazo para exportar + + AccountSetupPage + + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + AddAnalyticsPanelDialog @@ -105,12 +154,12 @@ Please check that the directory is writable and try again. AdminNotesDialog - + Update Notes Notas de actualización - + Admin Notes for %1 @@ -130,224 +179,310 @@ Please check that the directory is writable and try again. Tokens - + Fichas AppearanceSettingsPage - + seconds segundos - + Error Error - + Could not create themes directory at '%1'. No se pudo crear directorio de temas en '%1'. - + Theme settings Preferencias del tema - + Current theme: Tema actual: - + Open themes folder Abrir carpeta de temas - + Home tab background source: Fuente de segundo plano de pestaña de incio: - + Home tab background shuffle frequency: - + La frecuencia de cambiar los fondos en la pestaña de inicio: - + Disabled Deshabilitado - + Menu settings Preferencias del menú - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering Representación de las cartas - + Display card names on cards having a picture Mostrar nombre de las cartas en aquellas que tengan imagen - + Auto-Rotate cards with sideways layout - + Girar automáticamente cartas con diseño horizontal - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Sobreescribe todo arte de carta con preferencia de set personal (comportamiento de cambio previo a ProviderID) - + Light - + Modo claro - + Dark - + Modo obscuro - + + System + Sistema + + + + Show all playmats - + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + Active theme palette: + Paleta de tema activo + + + + Active theme style: - + + Qt widget style saved to this theme ("System" lets the application decide) + + + + Edit theme palette - + Editar la paleta del tema - + Home tab settings - + Preferencias de pestaña de inicio - + Display card name of background in bottom right + Mostrar el nombre de la carta del reverso en parte inferior derecha + + + + Dim the home tab background - + + Draw a translucent overlay over the home tab background so buttons and text stand out + Poner una superposición translúcida encima de la pestaña de inicio para facilitar la legibilidad del texto y botones. + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + Styling settings - + Configuraciones de estilo - + Style user list - + Diseño de la lista de usuario - + Card printings - + Impresiones de la carta - + Bump sets that the deck contains cards from to the top in the printing selector - + Empujar colecciones de las que el mazo contenga cartas al tope en el selector de impresion - + Scale cards on mouse over Cambiar tamaño de las cartas al pasar el ratón por encima - + Use rounded card corners Usar esquinas redondeadas de las cartas - + Card layout - + Diseño de carta - + Minimum overlap percentage of cards on the stack and in vertical hand - + Porcentaje minimo de empalme de cartas en la pila y a mano vertical - + Maximum initial height for card view window: - + Altura inicial máxima para vista de ventana de carta - - + + rows filas - + Maximum expanded height for card view window: - + Altura expandida máxima para vista de ventana de carta - + Card counters Contadores de carta - + Counter %1 Contador %1 - + Hand layout Disposición de la mano - + Display hand horizontally (wastes space) Mostrar la mano horizontalmente (desperdicia espacio) - + Enable left justification Habilitar justificación a la izquierda - + Table grid layout Diseño de la cuadrícula de la mesa - + Invert vertical coordinate Invertir coordenada vertical - + Minimum player count for multi-column layout: Número minimo de jugadores para usar la cuadrícula multicolumna: - + Maximum font size for information displayed on cards: Tamaño de fuente máxima para la información presentada en las cartas: @@ -355,12 +490,12 @@ Please check that the directory is writable and try again. ArchidektApiResponseDeckDisplayWidget - + Back to results - Regressar a los resultados + Regresar a los resultados - + Open Deck in Deck Editor Abrir mazo en editor de mazos @@ -375,123 +510,123 @@ Please check that the directory is writable and try again. Art crop of random card - + Recorte de arte a carta al azar Art crop of background.cod deck file - + Recorte de arte al archivo de mazo background.cod BanDialog - + ban &user name banear &usuario - + ban &IP address banear dirección IP &IP - + ban client I&D banear por I&D de cliente - + Ban type Tipo de ban - + &permanent ban acceso prohibido &permanentemente - + &temporary ban acceso prohibido &temporalmente - + &Days: &Días: - + &Hours: &Horas: - + &Minutes: &Minutos: - + Duration of the ban Duración del ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Por favor, introduce el motivo de la restricción de acceso Se almacenará unicamente para moderadores y no podrá ser visto por la persona restringida. - + Please enter the reason for the ban that will be visible to the banned person. Por favor, introduce el motivo de la restricción que será visible para la persona restringida. - + Redact all messages from this user in all rooms Redactar todos los mensajes de este usuario en todas las salas - + &OK Aceptar (&O) - + &Cancel &Cancelar - + Ban user from server Banear usuario del servidor - - - - + + + + Error Error - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Debes seleccionar baneo por nombre, por IP, por id de cliente, o cualquier combinación de los tres. - + You must have a value in the name ban when selecting the name ban checkbox. Debes introducir un nombre cuando la casilla de baneo por nombre está activada. - + You must have a value in the ip ban when selecting the ip ban checkbox. Debes introducir una IP cuando la casilla de baneo por IP está activada. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Debes introducir un id de cliente cuando la casilla de baneo por id de cliente está activada. @@ -499,22 +634,22 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona BetaReleaseChannel - + Beta Beta - + No reply received from the release update server. No se recibió respuesta del servidor de actualización. - + Invalid reply received from the release update server. Respuesta inválida del servidor de actualización. - + No reply received from the file update server. Sin respuesta por parte del servidor de actualización de archivos @@ -522,98 +657,255 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona CardArtPreviewWidget - - No card selected + + Banner preview + + + + + + Zoom %1× + + + + + No card selected + Sin carta seleccionada + CardArtRulesModel - + Card - + Carta - + ProviderId - + ProviderId - + Mode - + Modo - + Reason - + Motivo CardDatabaseModel - + Name Nombre - + Sets Ediciones - + Mana cost Coste de maná - + Card type Tipo de carta - + P/T F/R - + Color(s) Color(es) + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + CardDatabaseView - + Add to Deck - + Añadir a mazo - + Add to Sideboard - - - - - Select Printing - + Añadir a banquillo + Select Printing + Escoger impresión + + + + Add to Zone + + + + + Create &new zone... + + + + Show on EDHRec (Commander) - + Mostrar en EDHRec (Comandante) - + Show on EDHRec (Card) - + Mostrar en EDHRec (Carta) - + Show Related cards - + Mostrar cartas relacionadas @@ -721,22 +1013,22 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona CardInfoFrameWidget - + Image Imagen - + Description Descripción - + Both Ambos - + View transformation Ver transformación @@ -744,22 +1036,22 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona CardInfoPictureWidget - + View related cards Ver cartas relacionadas - + Add card to deck Añadir carta al mazo - + Mainboard Mazo principal - + Sideboard Banquillo @@ -767,27 +1059,27 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona CardInfoTextWidget - + Name: Nombre: - + Set: Expansión - + Collector Number: - + Número de coleccionador - + Related cards: Cartas relacionadas: - + Unknown card: Carta desconocida: @@ -795,129 +1087,129 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona CardMenu - + Re&veal to... - + Revelar a... - + &All players &Todos los jugadores - + View related cards Ver cartas relacionadas - + Token: Ficha: - + All tokens Todas las fichas - + &Select All &Seleccionar todo - + S&elect Row &Seleccionar fila - + S&elect Column &Seleccionar columna - + &Play &Jugar - + &Hide &Ocultar - + Play &Face Down Jugar boca abajo - + &Tap / Untap Turn sideways or back again &Girar / Enderezar - + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone &Clonar - + Attac&h to card... Anexar a carta... - + Unattac&h Desanexar - + &Draw arrow... Dibujar flecha... - + &Set annotation... Escribir anotación... - + Reduce life by power - + Ca&rd counters Contadores de carta - + &Add counter (%1) &Añadir contador (%1) - + &Remove counter (%1) &Quitar contador (%1) - + &Set counters (%1)... &Establecer contadores (%1) @@ -927,18 +1219,18 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona Network Cache - + Caché de red Filesystem - + Sistema de archivos CardSizeWidget - + Card Size Tamaño de carta @@ -1078,12 +1370,30 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona la zona personalizada de %1 '%2' + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser Parse error at line %1 col %2: - + Error de análisis en linea %1 columna %2: @@ -1091,7 +1401,7 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona Parse error at line %1 col %2: - + Error de análisis en linea %1 columna %2: @@ -1099,288 +1409,390 @@ Se almacenará unicamente para moderadores y no podrá ser visto por la persona Click to pick a color - + Clic para elegir un color Pick colour + Elige color + + + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket ConnectionController - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Vetado por un moderador - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - - - - - - - - - - - + + + + + + + + + + - - + + + Error - + Error - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + Server timeout - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Information - + Información - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -1388,113 +1800,113 @@ To update your client, go to Help -> Check for Updates. - - - + + + Reset Password - + Reiniciar contraseña - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - + Connecting to %1... - + Conectar a %1... - + Registering to %1 as %2... - + Disconnected - + Desconectado - + Connected, logging in at %1 - + Requesting forgotten password to %1 as %2... - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + NO - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. @@ -1538,13 +1950,13 @@ To update your client, go to Help -> Check for Updates. Include Sideboard - + Incluir banquillo DeckEditorCardDatabaseDockWidget - + Card Database Base de datos de cartas @@ -1560,17 +1972,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) Buscar por nombre de carta (o buscar expresiones) - + Add card to &maindeck Añadir carta al &mazo principal - + Add card to &sideboard Añadir carta al &banquillo @@ -1578,97 +1990,158 @@ To update your client, go to Help -> Check for Updates. DeckEditorDeckDockWidget - + Loading Database... Cargando la base de datos... - + Banner Card - + Main Type Tipo principal de carta - + Mana Cost Coste de maná - + Colors Colores - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck Mazo - + Deck &name: &Nombre del mazo: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: &Comentarios: - + Group by: Agrupar por: - + Format: Formato: - + Hash: Hash: - + &Increment number &Incrementar número - + &Decrement number &Reducir número - + &Remove row &Quitar fila - + Swap card to/from sideboard @@ -1676,17 +2149,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorFilterDockWidget - + Filters Filtros - + &Clear all filters &Limpiar todos los filtros - + Delete selected Borrar seleccionado @@ -1694,114 +2167,119 @@ To update your client, go to Help -> Check for Updates. DeckEditorMenu - + &Deck Editor &Editor de mazos - + &New deck &Nuevo mazo - + &Load deck... &Cargar mazo... - + Load recent deck... Cargar mazo reciente... - + Clear Borrar - + &Save deck &Guardar mazo - + Save deck &as... Guardar mazo &como... - + + Share deck... + + + + Load deck from cl&ipboard... Cargar mazo desde el &portapapeles... - + Edit deck in clipboard Editar mazo en el portapapeles - - + + Annotated Anotado - - + + Not Annotated No Anotado - + Save deck to clipboard Guardar mazo al portapapeles - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... Imprimir mazo... - + Load deck from online service... Cargar mazo desde el servicio en línea... - + &Send deck to online service - + Enviar mazo al servicio en línea - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close &Cerrar @@ -1809,7 +2287,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1817,110 +2295,152 @@ To update your client, go to Help -> Check for Updates. DeckEditorSettingsPage - - + + Update Spoilers Actualizar Spoilers - + Success Éxito - + Download URLs have been reset. Las URL de descarga se han reiniciado. - + Add URL Añadir URL - - + + URL: URL: - - + + Edit URL Editar URL - + Add New URL Añadir URL - + Remove URL Borrar URL - + Updating... Actualizando... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path Elegir camino - + URL Download Priority Prioridad de descarga de URL - + Spoilers Spoilers - + Download Spoilers Automatically Descargar Spoilers automáticamente - + Spoiler Location: Localización del Spoiler: - + Last Change Último cambio - + Spoilers download automatically on launch Spoilers se descargan automáticamente en lanzamiento - + Press the button to manually update without relaunching Presione el botón para actualizar manualmente sin reiniciar - + Do not close settings until manual update is complete No cierre la ventana de configuración hasta que la actualización manual este completa. - + Download card pictures on the fly Descargar imagenes de las cartas rapidamente - + How to add a custom URL Cómo agregar una URL personalizada - + Reset Download URLs Reiniciar descarga de URLs @@ -1928,32 +2448,32 @@ To update your client, go to Help -> Check for Updates. DeckListHistoryManagerWidget - + Undo Deshacer - + Redo Rehacer - + Undo/Redo history Historial de deshacer/rehacer - + Click on an entry to revert to that point in the history. - + [redo] [rehacer] - + [undo] [deshacer] @@ -1961,27 +2481,27 @@ To update your client, go to Help -> Check for Updates. DeckListModel - + Count Conteo - + Set Expansión - + Number Número - + Provider ID - + Card Carta @@ -2002,17 +2522,22 @@ To update your client, go to Help -> Check for Updates. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match Modo: Coincidencia Exacta - + Mode: Includes Modo: Incluye - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -2020,7 +2545,7 @@ To update your client, go to Help -> Check for Updates. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... Editar etiquetas @@ -2028,62 +2553,62 @@ To update your client, go to Help -> Check for Updates. DeckPreviewTagDialog - + Deck Tags Manager Gestor de etiquetas de mazos - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) Añadir una nueva etiqueta (p.e., Aggro) - + Add Tag Añadir etiqueta - + Filter tags... Filtrar etiquetas... - + OK OK - + Edit default tags - + Cancel Cancelar - + Invalid Input - + Tag name cannot be empty! ¡Nombre de etiqueta no puede estar vacío! - + Duplicate Tag Etiqueta Duplicada - + This tag already exists. Esta etiqueta ya existe. @@ -2091,94 +2616,109 @@ To update your client, go to Help -> Check for Updates. DeckPreviewWidget - + Banner Card - + Open in deck editor Abrir en editor de mazos - + + Share deck... + + + + Edit Tags Editar etiquetas - + Rename Deck Renombrar mazo - + Save Deck to Clipboard Guardar mazo al portapapeles - + Annotated Anotado - + Annotated (No set info) - + Not Annotated No Anotado - + Not Annotated (No set info) - + Rename File Renombrar archivo - + Delete File Eliminar archivo - + Set Banner Card - - + + New name: Nuevo nombre: - - + + Rename deck + + + + + Rename file + + + + + Error Error - + Rename failed - + Delete file Eliminar archivo - + Are you sure you want to delete the selected file? ¿Estás seguro de que quieres eliminar el archivo seleccionado? - + Delete failed Borrado fallido @@ -2186,57 +2726,102 @@ To update your client, go to Help -> Check for Updates. DeckStateManager - + Rename deck to "%1" from "%2" - + Renombrar mazo a «%1» desde «%2» - + Updated comments (was %1 chars, now %2 chars) - + Set banner card to %1 (%2) - + + Set playmat to %1 + + + + Tags changed - + Set format to %1 - + Added (%1): %2 (%3) %4 Añadido (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Movido a %1 1 × «%2» (%3) - + Removed "%1" (all copies) - - %1 1 × "%2" (%3) + + Moved %1 × "%2" (%3) to %4 - + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + + %1 1 × "%2" (%3) + %1 1 × «%2» (%3) + + + Added Añadido - + Removed Borrado @@ -2258,94 +2843,122 @@ To update your client, go to Help -> Check for Updates. DeckViewContainer - + Load deck... Cargar mazo... - + Load remote deck... Cargar mazo remoto... - + Load from clipboard... Cargar desde el portapapeles... - + Load from website... Cargar desde el sitio web... - + Unload deck - + Ready to start Listo para empezar - + Force start Forzar inicio - + Sideboard unlocked Banquillo desbloqueado - + Sideboard locked Banquillo bloqueado - - + + Error Error - + The selected file could not be loaded. El fichero seleccionado no pudo cargarse. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? - + Remember and automatically apply choice in the future @@ -2358,143 +2971,143 @@ This will kick all non-ready players from the game. Descargando... - + Known Hosts Hospedajes Conocidos - + Delete the currently selected saved server - + Refresh the server list with known public servers Actualizar la lista con servidores públicos conocidos - + New Host Nueva dirección - + Name: Nombre: - + &Host: &Dirección: - + &Port: &Puerto: - + Player &name: &Nombre del jugador: - + P&assword: &Contraseña: - + &Save password &Guardar contraseña - + A&uto connect Conectarse a&utomáticamente - + Automatically connect to the most recent login when Cockatrice opens Conectar automáticamente al servidor más reciente cuando se inicie Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! ¡Si tiene algún problema en conectarse o registrarse puede contactar a nuestro equipo! - - + + Webpage Página web - + Reset Password Reiniciar Contraseña - + Forgot password? ¿Olvidó la contraseña? - + &Connect &Conectar - + Server Servidor - + Login Iniciar sesión - + Server Contact Contacto del Servidor - + Connect to Server Conectar al Servidor - + Server URL dirección URL del servidor - + Communication Port Puerto de comunicación - + Unique Server Name Nombre único para el servidor - + Connection Warning Advertencia de Conexión - + You need to name your new connection profile. Necesita dar nombre a su nuevo perfil de conexión. - + Connect Warning Advertencia de conexión - + The player name can't be empty. El nombre del jugador no puede estar vacío. @@ -2502,122 +3115,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings Recordar preferencias (&M) - + &Description: &Descripción: - + P&layers: &Jugadores: - + General General - + Game type Tipo de partida - + &Password: &Contraseña: - + Only &buddies can join Sólo los &amigos pueden participar - + Only &registered users can join Sólo los usuarios &registrados pueden participar - + Joining restrictions Restricciones de participación - + &Spectators can watch Los e&spectadores pueden observar - + Spectators &need a password to watch Los espectadores &necesitan contraseña para unirse - + Spectators can &chat Los espectadores pueden &chatear - + Spectators can see &hands Los espectadores pueden ver las &manos - + Create game as spectator Crear una partida como espectador - + Spectators Espectadores - + Starting life total: Vida inicial total: - + Open decklists in lobby - + Create game as judge - + Game setup options - + &Clear Limpiar (&C) - + Create game Crear partida - + Game information Información de la partida - + Error Error - + Server error. Error del servidor. @@ -2625,97 +3238,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Nombre: - + Token Ficha - + C&olor: &Color: - + white blanco - + blue azul - + black negro - + red rojo - + green verde - + multicolor multicolor - + colorless incoloro - + &P/T: &F/R: - + &Annotation: &Anotación: - + &Destroy token when it leaves the table &Destruir la ficha cuando deje la mesa - + Create face-down (Only hides name) - + Token data Datos de la ficha - + Show &all tokens Mostrar &todas las fichas - + Show tokens from this &deck Mostrar todas las fichas de este &mazo - + Choose token from list Elegir ficha de la lista - + Create token Crear ficha @@ -2723,53 +3336,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags Editar etiquetas - + Add Añadir - + Confirm Confirmar - + Cancel Cancelar - + Enter a tag and press Enter Introduce una etiqueta y pulsa Enter - - + + ✖ - + Invalid Input - + Tag name cannot be empty! El nombre de la etiqueta no puede estar vacío! - + Duplicate Tag Duplicar etiqueta - + This tag already exists. Esta etiqueta ya existe. @@ -2778,7 +3391,7 @@ This will kick all non-ready players from the game. DlgEditAvatar - + No image chosen. Ninguna imagen seleccionada @@ -2790,27 +3403,32 @@ To remove your current avatar, confirm without choosing a new image. Para eliminar tu avatar actual, confirma sin elegir una nueva imagen. - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... Buscar - + Change avatar Cambiar avatar - + Open Image Abrir imagen - + Image Files (*.png *.jpg *.bmp) Archivos de imagen (*.png *.jpg *.bmp) - + Invalid image chosen. La imagen seleccionada no es válida. @@ -2836,38 +3454,38 @@ Para eliminar tu avatar actual, confirma sin elegir una nueva imagen. DlgEditPassword - + Old password: Contraseña anterior: - + New password: Nueva contraseña: - + Confirm new password: Confirmar la nueva contraseña: - + Change password Cambiar contraseña - - + + Error Error - + Your password is too short. Su contraseña es muy corta - + The new passwords don't match. Las nuevas contraseñas no coinciden. @@ -2875,93 +3493,93 @@ Para eliminar tu avatar actual, confirma sin elegir una nueva imagen. DlgEditTokens - + &Name: &Nombre: - + C&olor: &Color: - + white blanco - + blue azul - + black negro - + red rojo - + green verde - + multicolor multicolor - + colorless incoloro - + &P/T: &F/R: - + &Annotation: &Anotación: - + Token data Información de la ficha - - + + Add token Añadir ficha - + Remove token Eliminar ficha - + Edit custom tokens Editar fichas personalizadas - + Please enter the name of the token: Por favor, introduzca el nombre de la ficha: - + Error Error - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. El nombre elegido ya existe y está siendo usado por una carta o ficha. @@ -3040,7 +3658,8 @@ Asegúrese de habilitar la edición 'Fichas' en la opción "Mante - Hide games that have started + Hide started games + Hide games that have started @@ -3076,8 +3695,9 @@ Asegúrese de habilitar la edición 'Fichas' en la opción "Mante - &Creator name: - &Nombre del creador: + &Host name: + &Creator name: + @@ -3143,52 +3763,52 @@ Asegúrese de habilitar la edición 'Fichas' en la opción "Mante DlgForgotPasswordChallenge - + Reset Password Challenge Warning Reiniciar contraseña de desafíos con advertencia - + A problem has occurred. Please try to request a new password again. Ha ocurrido un problema. Por favor intenta solicitar una nueva contraseña. - + Enter the information of the server and the account you'd like to request a new password for. Introduce la información del servidor y de la cuenta para la que deseas solicitar una nueva contraseña. - + &Host: &Dirección: - + &Port: &Puerto: - + Player &name: &Nombre del jugador: - + Email: Correo electrónico: - + Reset Password Challenge Reiniciar contraseña de desafío - + Reset Password Challenge Error Error al reiniciar contraseña de desafío - + The email address can't be empty. La dirección de correo electrónico no puede estar vacía. @@ -3196,37 +3816,37 @@ Asegúrese de habilitar la edición 'Fichas' en la opción "Mante DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. Introduce la información del servidor para la que deseas solicitar una nueva contraseña. - + &Host: &Dirección: - + &Port: &Puerto: - + Player &name: &Nombre del jugador: - + Reset Password Request Error al reiniciar la petición para una contraseña - + Reset Password Error Error al reiniciar la contraseña - + The player name can't be empty. El nombre del jugador no puede estar vacío. @@ -3234,94 +3854,127 @@ Asegúrese de habilitar la edición 'Fichas' en la opción "Mante DlgForgotPasswordReset - + Reset Password Warning Reinicia la contraseña de advertencia - + A problem has occurred. Please try to request a new password again. Ha ocurrido un problema. Por favor intenta solicitar de nuevo una nueva contraseña. - + Enter the received token and the new password in order to set your new password. Introduce el token recibido y la nueva contraseña para establecer tu nueva contraseña. - + &Host: &Dirección: - + &Port: &Puerto: - + Player &name: &Nombre del jugador: - + Token: Ficha: - - + + New Password: Nueva contraseña: - + Reset Password Reiniciar contraseña - + The player name can't be empty. El nombre del jugador no puede estar vacío. - - - - + + + + Reset Password Error Error al reiniciar contraseña - + The token can't be empty. La ficha no puede estar vacía. - + The new password can't be empty. La nueva contraseña no puede estar vacía. - + Error Error - + Your password is too short. Tu contraseña es demasiado corta. - + The passwords do not match. Las contraseñas no coinciden. + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck Cargar mazo @@ -3370,7 +4023,7 @@ Asegúrese de habilitar la edición 'Fichas' en la opción "Mante Network error: %1 - + Error de la red: %1 @@ -3405,173 +4058,574 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLocalGameOptions - + Players: Jugadores: - + General General - + Starting life total: Total de vidas inicial: - + Game setup options - + Remember settings Recordar preferencias - + Local game options + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): Nombre de carta (o buscar expresiones): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + Poner cartas superiores en la pila hasta... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice Cockatrice - + Invalid filter Filtro no válido + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. Introduce tu información y la información del servidor en el que deseas registrarte. Tu correo electrónico se utilizará para verificar tu cuenta. - + &Host: &Dirección: - + &Port: &Puerto: - + Player &name: &Nombre del jugador: - + P&assword: &Contraseña: - + Password (again): Contraseña (de nuevo): - + Email: Correo electrónico: - + Email (again): Correo electrónico (de nuevo): - + Country: País: - + Undefined Indefinido - + Real name: Nombre real: - + Register to server Registrarse en el servidor - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning Advertencia durante el registro - + Your password is too short. Tu contraseña es demasiado corta. - + Your passwords do not match, please try again. Tus contraseñas no coinciden, por favor inténtalo de nuevo. - + Your email addresses do not match, please try again. Tus direcciones de correo electrónico no coinciden, por favor inténtalo de nuevo. - + The player name can't be empty. El nombre del jugador no puede estar vacío. + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice Number of sides: - + Cantidad de lados: @@ -3630,46 +4684,12 @@ Tu correo electrónico se utilizará para verificar tu cuenta. DlgSettings - + Unknown Error loading card database Error desconocido al cargar la base de datos de cartas. - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - Tu base de datos de cartas no es válida. - -Cockatrice podría no funcionar correctamente con una base de datos inválida. - -Podrías necesitar volver a ejecutar oracle para actualizar tu base de datos de cartas. - -¿Quieres cambiar la ubicación de tu base de datos? - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - Tu base de datos de cartas es demasiado antigua. - -Esto puede causar problemas al cargar la información o las imágenes de las cartas. - -Normalmente esto se soluciona volviendo a ejecutar oracle para actualizar tu base de datos. - -¿Quieres cambiar la ubicación de tu base de datos? - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3682,7 +4702,7 @@ Por favor, envía un ticket en https://github.com/Cockatrice/Cockatrice/issues c ¿Te gustaría cambiar la configuración de la ubicación de tu base de datos? - + File Error loading your card database. Would you like to change your database location setting? @@ -3691,7 +4711,7 @@ Would you like to change your database location setting? ¿Quieres cambiar la ubicación de tu base de datos? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3700,7 +4720,7 @@ Would you like to change your database location setting? ¿Quieres cambiar la ubicación de tu base de datos? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3713,104 +4733,235 @@ Por favor, envía un ticket en https://github.com/Cockatrice/Cockatrice/issues ¿Te gustaría cambiar la configuración de la ubicación de tu base de datos? - - - + + + Error Error - + The path to your deck directory is invalid. Would you like to go back and set the correct path? La ruta a tu directorio de mazos no es válida. ¿Deseas volver y seleccionar la ruta correcta? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? La ruta a tu directorio de imágenes de las cartas no es válida. ¿Deseas volver y seleccionar la ruta correcta? - + Settings Preferencias - + + Search settings... + + + + + OK + + + + General General - + Appearance Apariencia - + User Interface Interfaz de usuario - + Storage - + Almacén - + Card Sources Origen de la carta - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat Chat - + Sound Sonido - + Shortcuts Atajos de teclado + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground Ejecutar en primer plano - + Run in background Ejecutar en segundo plano - + Run in background and always from now on Ejecutar en segundo plano y siempre a partir de ahora - + Don't prompt again and don't run - + Don't run this time No ejecutar esta vez @@ -3818,17 +4969,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next Siguiente - + Previous Anterior - + Tip of the Day Tip del día @@ -3836,165 +4987,175 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel Canal de lanzamiento actual - + Reinstall Reinstalar - + Cancel Download Cancelar Descarga - + Open Download Page Abrir página de descarga - + Check for Client Updates Comprobar actualizaciones del cliente - - - - + + + + Error Error - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice no fue creado con soporte para SSL, ¡por lo que no se pueden descargar actualizaciones automáticamente! Por favor, visita la página de descargas para actualizarlo manualmente. - + Downloading update: %1 Descargando actualización: %1 - + Checking for updates... Buscando actualizaciones... - + Finished checking for updates Comprobación de actualizaciones finalizada - + No Update Available Actualización no disponible - + Cockatrice is up to date! ¡Cockatrice se ha actualizado! - + You are already running the latest version available in the chosen release channel. Actualmente está ejecutando la última versión disponible en el canal de distribución. - + Current version Versión actual - + Selected release channel Seleccionado canal de lanzamiento - - + + Update Available Actualización disponible - - + + A new version of Cockatrice is available! ¡Una nueva version de Cockatrice está disponible! - - + + New version Nueva versión - - + + Released Lanzado - - + + Changelog Registro de cambios - + Do you want to update now? ¿Quieres actualizar ahora? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error Error en la actualización - + An error occurred while checking for updates: Ha ocurrido un error mientras se buscaban actualizaciones: - + An error occurred while downloading an update: Ha ocurrido un error mientras se descargaba una actualización: - + Installing... Instalando... - + Cockatrice is unable to open the installer. Cockatrice es incapaz de abrir el instalador. - + Try to update manually by closing Cockatrice and running the installer. Intente actualizar manualmente cerrando Cockatrice y ejecutando el instalador. - + Download location Ubicación de descarga @@ -4002,17 +5163,17 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing Limpiar el historial al cerrar - + Copy to clipboard Copiar al portapapeles - + Debug Log Registro de depuración @@ -4047,7 +5208,7 @@ You may have to manually download the new version. Mana Value - + Valor de maná @@ -4062,12 +5223,12 @@ You may have to manually download the new version. Exactly - + Exactamente Quantity (N): - + Cantidad (N) @@ -4111,7 +5272,7 @@ You may have to manually download the new version. Mana Value - + Valor de maná @@ -4121,7 +5282,7 @@ You may have to manually download the new version. Exactly - + Exactamente @@ -4136,12 +5297,12 @@ You may have to manually download the new version. Category - + Categoría Qty - + Cantidad @@ -4154,7 +5315,7 @@ You may have to manually download the new version. In %1 decks - + En %1 mazos @@ -4192,7 +5353,7 @@ You may have to manually download the new version. MTG Stocks - + MTG Stocks @@ -4250,15 +5411,15 @@ You may have to manually download the new version. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: - + Sal: FilterBuilder - + Type your filter here Escriba su filtro aquí @@ -4266,45 +5427,88 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? ¿Estás seguro/a de que quieres eliminar el filtro '%1'? - + Delete Failed Borrado fallido - + Failed to delete filter '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator expulsado por el anfitrión del juego o moderador - + player left the game jugador ha dejado la partida - + player disconnected from server jugador se ha desconectado del servidor - + reason unknown razón desconocida @@ -4312,140 +5516,156 @@ You may have to manually download the new version. GameSelector - - - - - - - - - + + + + + + + + + Error Error - + Please join the appropriate room first. Por favor, entre a la sala adecuada primero. - + Wrong password. Contraseña incorrecta. - + Spectators are not allowed in this game. No se permiten espectadores en esta partida. - + The game is already full. La partida no tiene plazas libres. - + The game does not exist any more. La partida ya no existe. - + This game is only open to registered users. Esta partida está abierta sólo a usuarios registrados. - + This game is only open to its creator's buddies. Esta partida está abierta sólo a los amigos del creador. - + You are being ignored by the creator of this game. Estás siendo ignorado por el creador de la partida. - + Join Game Unirse a partida - + Spectate Game Espectar partida - + Game Information Información de la partida - + + Cop&y game link + + + + Join Game as Judge - + Spectate Game as Judge - + + Join game Entrar en la partida - - Password: - Contraseña: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. Por favor, entre primero en la sala respectiva. - + &Filter games &Filtrar partidas - + C&lear filter &Limpiar filtro - + C&reate C&rear - + &Join Unirse (&J) - + Join as judge - + J&oin as spectator Entrar como e&spectador - + Join as judge spectator - + Games shown: %1 / %2 Juegos mostrados: %1 / %2 - + Games Partidas @@ -4475,7 +5695,7 @@ You may have to manually download the new version. Hide full games - + Esconder los partidos llenos @@ -4486,105 +5706,105 @@ You may have to manually download the new version. GamesModel - + >1 day >1 day - + %1%2 hr short age in hours %1%2 hora%1%2 horas%1%2 horas - + new nuevo - + %1%2 min short age in minutes %1%2 min%1%2 min%1%2 min - + password contraseña - + buddies only solo amigos - + reg. users only solo usuarios registrados - + open decklists - - + + can chat pueden usar el chat - + see hands ver manos - + can see hands puede ver manos - + not allowed no permitidos - + Room Sala - + Age Edad - + Description Descripción - - Creator - Creador + + Host + - + Type Tipo - + Restrictions Restricciones - + Players Jugadores - + Spectators Espectadores @@ -4592,158 +5812,275 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths Reiniciar todos los caminos - + All paths have been reset Todos los caminos se han reiniciado - - - - - - - + + + + English + Español (Spanish) + + + + + + + + + Choose path Elija ruta - - Language settings + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> - + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + + Language settings + Preferencias de la idioma + + + Language: Idioma: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + Version settings - + Preferencias de versión - + Card database - + Base de datos de cartas - + Startup settings - + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) Rutas (edición deshabilitada en modo portatil) - + Paths Rutas - + How to help with translations Cómo ayudar con las traducciones - + Decks directory: Directorio de mazos: - + Filters directory: Directorio de filtros: - + Replays directory: Directorio de repeticiones: - + Pictures directory: Directorio de imágenes: - + Card database: Base de datos de cartas: - + Custom database directory: Directorio de base de datos personalizado: - + Token database: Base de datos de tokens: - + Update channel Actualizar canal - + Check for client updates on startup Comprobar actualizaciones del cliente al arrancar - + Check for card database updates on startup Comprobar actualizaciones de la base de datos de cartas al arrancar - + Don't check No comprobar - + Prompt for update - + Always update in the background Siempre actualizar en segundo plano - + Check for card database updates every Comprobar actualizaciones de la base de datos de cartas cada - + days días - + Notify if a feature supported by the server is missing in my client Notificar si hace falta en mi cliente alguna característica soportada por el servidor - + Automatically run Oracle when running a new version of Cockatrice Oracle se ejecutará automáticamente cuando se ejecuté una nueva versión de Cockatrice - + Show tips on startup Mostrar tips al inicio - + Last update check on %1 (%2 days ago) @@ -4751,47 +6088,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard &Cementerio - + &View graveyard Ver &cementerio - + &Move graveyard to... &Mover cementerio a... - + &Top of library &Parte superior de la biblioteca - + &Bottom of library &Parte inferior de la biblioteca - + &All players Todos los jugadores - + &Hand &Mano - + &Exile &Exilio - + Reveal random card to... Mostrar una carta al azar a... @@ -4811,7 +6148,7 @@ You may have to manually download the new version. Sort hand by... - + Ordenar mano por... @@ -4826,7 +6163,7 @@ You may have to manually download the new version. Mana Value - + Valor de maná @@ -4885,59 +6222,353 @@ You may have to manually download the new version. Mostrar una carta al azar a... + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck Crear nuevo mazo - + Browse Decks Buscar mazos - + Browse Card Database Buscar base de datos de cartas - + Browse EDHRec Buscar EDHRec - + Browse Archidekt Buscar Archidekt - + View Replays Ver repeticiones - + Quit Quitar - + Connecting... Conectando... - + Connect Conectar - + Play Jugar + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu @@ -5008,7 +6639,7 @@ You may have to manually download the new version. D&raw cards... - + Robar cartas... @@ -5033,7 +6664,7 @@ You may have to manually download the new version. Put top card on &bottom - + Poner carta superior en la parte inferior @@ -5068,7 +6699,7 @@ You may have to manually download the new version. Put top cards on stack &until... - + Poner cartas superiores en la pila hasta... @@ -5144,7 +6775,7 @@ You may have to manually download the new version. Reveal top cards of library - + Revelar cartas superiores de la biblioteca @@ -5155,273 +6786,294 @@ You may have to manually download the new version. MainWindow - - + + Player %1 Jugador %1 - + Load replay Cargar repetición - + About Cockatrice Acerca de Cockatrice - + Version Versión - + Cockatrice Webpage Página de Cockatrice - + Project Manager: Jefe de Proyecto: - + Past Project Managers: Antiguos Jefes de Proyecto: - + Developers: Desarrolladores: - + Our Developers Nuestros desarrolladores - + Help Develop! ¡Ayuda a desarrollar! - + Translators: Traductores: - + Our Translators Nuestros Traductores - + Help Translate! ¡Ayuda a traducir! - + Support: Soporte: - + Report an Issue Informa de un problema - + Troubleshooting Problemas frecuentes - + F.A.Q. Preguntas frecuentes - - + + Error Error - + &Connect... &Conectar... - + &Disconnect &Desconectar - + Start &local game... Empezar partida &local... - + &Watch replay... &Ver repetición... - + &Full screen &Pantalla completa - + &Register to server... &Registrarse en el servidor... - + &Restore password... &Restaurar contraseña... - + &Settings... &Preferencias... - + &Exit &Salir - + A&ctions A&cciones - + &Cockatrice &Cockatrice - + C&ard Database Base de datos de c&artas - + &Manage sets... &Mantenedor de ediciones... - + Edit custom &tokens... Editar &fichas personalizadas... - + Open custom image folder Abrir carpeta de imágenes personalizadas - + Open custom sets folder Abrir carpeta de ediciones personalizadas - + Add custom sets/cards Agregar cartas/ediciones personalizadas - + Reload card database Recargar la base de datos de cartas - + Tabs Pestañas - + &Help A&yuda - + &About Cockatrice &Acerca de Cockatrice - + &Tip of the Day &Tip del Día - + Check for Client Updates Buscar Actualizaciones de Cliente - + Check for Card Updates... Comprobar actualizaciones de cartas... - + Check for Card Updates (Automatic) Comprobar actualizaciones de cartas (Automático) - + Show Status Bar Mostrar barra de estatus - + View &Debug Log Ver &registro de debugs - + Open Settings Folder Abrir carpeta de preferencias - + + Re-run Onboarding Wizard... + + + + Show/Hide Mostrar/Ocultar - + + Sign-in cancelled + + + + New Version Nueva Versión - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Felicitaciones se ha actualizado Cockatrice %1! Oracle actualizará tu base de datos de cartas. - + Cockatrice installed Cockatrice está instalado. - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. ¡Felicitaciones! Se ha instalado Cockatrice %1 Ahora se ejecutará Oracle para instalar la base de datos de cartas inicial. - + + Are you sure? + ¿Estás seguro? + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database Base de datos de cartas - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5430,29 +7082,29 @@ If unsure or first time user, choose "Yes" Si no está seguro o es su primera vez, seleccione "Sí" - - + + Yes Si - - + + No No - + Open settings Abrir preferencias - + New sets found Nuevas ediciones encontradas - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5465,22 +7117,22 @@ Códigos de sets: %1 ¿Quieres habilitarlos? - + Yes, always enable - + View sets Ver ediciones - + Welcome Bienvenido/a - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5489,89 +7141,89 @@ Todas las ediciones en la base de datos de cartas han sido habilitadas. Aprenda más sobre cambiar el orden de la edición o deshabilitando ediciones especificas y otras preferencias en la opción "Mantenedor de ediciones". - + Information Información - + A card database update is already running. La actualización de la base de datos de cartas ya está en marcha. - + Unable to run the card database updater: Imposible iniciar el actualizador de la base de datos de cartas: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. Ocurrió un error desconocido. - + The card database updater exited with an error: %1 - - - - - + + + + + Load sets/cards Cargar ediciones/cartas - + Selected file cannot be found. El archivo seleccionado no fue encontrado. - + You can only import XML databases at this time. Solo puedes importar bases de datos en XML en este momento. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Las nuevas ediciones/cartas se han agregado correctamente. Cockatrice está recargando la base de datos de cartas. - + Sets/cards failed to import. Las ediciones/cartas fallaron al ser importadas. @@ -5591,12 +7243,12 @@ Cockatrice está recargando la base de datos de cartas. pie - + gráfico circular bar - + gráfico de barras @@ -5606,12 +7258,12 @@ Cockatrice está recargando la base de datos de cartas. Filter Colors (optional): - + Filtrar colores (opcional): OK - + OK @@ -5632,7 +7284,7 @@ Cockatrice está recargando la base de datos de cartas. Group By: - + Agrupar por: @@ -5662,7 +7314,7 @@ Cockatrice está recargando la base de datos de cartas. Filters (optional): - + Filtros (opcional): @@ -5693,12 +7345,12 @@ Cockatrice está recargando la base de datos de cartas. pie - + gráfico circular bar - + gráfico de barras @@ -5708,7 +7360,7 @@ Cockatrice está recargando la base de datos de cartas. Filter Colors (optional): - + Filtrar colores (opcional): @@ -5729,17 +7381,17 @@ Cockatrice está recargando la base de datos de cartas. pie - + gráfico circular bar - + gráfico de barras Colors: - + Colores @@ -5776,616 +7428,616 @@ Cockatrice está recargando la base de datos de cartas. MessageLogWidget - + from play del juego - + from their graveyard de su cementerio - + from exile del exilio - + from their hand de su mano - + the top card of %1's library la carta superior de la biblioteca de %1 - + the top card of their library la parte superior de su biblioteca - + from the top of %1's library desde la parte superior de la biblioteca de %1 - + from the top of their library Desde la parte superior de su biblioteca - + the bottom card of %1's library la carta de la parte inferior de la biblioteca de %1 - + the bottom card of their library La carta de la parte inferior de su biblioteca - + from the bottom of %1's library desde la parte inferior de la biblioteca de %1 - + from the bottom of their library Desde la parte inferior de su biblioteca - + from %1's library desde la biblioteca de %1 - + from their library desde su biblioteca - + from sideboard desde el banquillo - + from the stack desde la pila - + from custom zone '%1' desde la zona personalizada '%1' - + %1 is now keeping the top card %2 revealed. %1 está manteniendo la carta superior %2 revelada. - + %1 is not revealing the top card %2 any longer. %1 ya no mantiene revelada la carta superior %2. - + %1 can now look at top card %2 at any time. %1 ahora puede ver la carta superior de %2 en cualquier momento. - + %1 no longer can look at top card %2 at any time. %1 ya no puede ver la carta superior de %2 en cualquier momento. - + %1 attaches %2 to %3's %4. %1 anexa %2 a %3's %4. - + %1 has conceded the game. %1 ha concedido la partida. - + %1 has unconceded the game. %1 ha concedido la partida - + %1 has restored connection to the game. %1 ha recuperado la conexión a la partida. - + %1 has lost connection to the game. %1 ha perdido la conexión a la partida. - + %1 points from their %2 to themselves. %1 apunta desde su %2 a si mismo. - + %1 points from their %2 to %3. %1 apunta desde su %2 a %3. - + %1 points from %2's %3 to themselves. %1 apunta desde el %3 de %2 a si mismo. - + %1 points from %2's %3 to %4. %1 apunta desde el %3 de %2 a %4. - + %1 points from their %2 to their %3. %1 apunta de su %2 a su %3. - + %1 points from their %2 to %3's %4. %1 apunta desde su %2 al %4 de %3. - + %1 points from %2's %3 to their own %4. %1 apunta desde el %3 de %2 a su %4. - + %1 points from %2's %3 to %4's %5. %1 apunta desde el %3 de %2 al %5 de %4. - + %1 creates a face down token. - + %1 crea una ficha boca abajo. - + %1 creates token: %2%3. %1 crea una ficha: %2%3. - + %1 has loaded a deck (%2). %1 ha cargado un mazo (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 ha cargado un mazo con %2 cartas en el banquillo (%3). - + %1 destroys %2. %1 destruye %2. - + a card una carta - + %1 gives %2 control over %3. %1 entrega a %2 el control sobre %3. - + %1 puts %2 into play%3 face down. %1 pone %2 en juego %3 bocabajo. - + %1 puts %2 into play%3. %1 pone %2 en juego %3. - + %1 puts %2%3 into their graveyard face down. %1 pone %2%3 en su cementerio boca abajo. - + %1 puts %2%3 into their graveyard. %1 pone %2%3 en su cementerio. - + %1 exiles %2%3 face down. %1 exilia %2%3 boca abajo. - + %1 exiles %2%3. %1 exilia %2%3. - + %1 moves %2%3 to their hand. %1 mueve %2%3 a su mano. - + %1 puts %2%3 into their library. %1 pone %2%3 en su biblioteca. - + %1 puts %2%3 onto the bottom of their library. %1 pone %2%3 al fondo de su biblioteca. - + %1 puts %2%3 on top of their library. %1 pone %2%3 en la parte superior de su biblioteca. - + %1 puts %2%3 into their library %4 cards from the top. %1 pone %2%3 en la biblioteca de su propietario en el %4 lugar desde la parte superior - + %1 moves %2%3 to sideboard. %1 mueve %2%3 al banquillo. - + %1 plays %2%3 face down. %1 juga %2%3 boca abajo. - + %1 plays %2%3. %1 juega %2%3. - + %1 moves %2%3 to custom zone '%4' face down. %1 mueve %2%3 a la zona personalizada '%4' boca abajo. - + %1 moves %2%3 to custom zone '%4'. %1 mueve %2%3 a la zona personalizada '%4' - + %1 tries to draw from an empty library - + %1 trata de robar desde una biblioteca vacía. - + %1 draws %2 card(s). - %1 roba %2 carta(s).%1 roba %2 c%1 roba %2 c + %1 roba %2 carta.%1 roba %2 cartas.%1 roba %2 carta(s). - + %1 is looking at %2. %1 está mirando a %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom parte inferior - + top parte superior - + %1 turns %2 face-down. %1 pone %2 boca abajo. - + %1 turns %2 face-up. %1 pone %2 boca arriba. - + The game has been closed. La partida ha sido cerrada. - + The game has started. La partida ha comenzado. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 se ha unido a la partida. - + %1 is now watching the game. %1 está ahora observando la partida. - + You have been kicked out of the game. Has sido expulsado de la partida. - + %1 has left the game (%2). %1 ha dejado la partida (%2). - + %1 is not watching the game any more (%2). %1 ya no está viendo el juego (%2). - + %1 is not ready to start the game any more. %1 está preparado/a para empezar la partida. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 barajea su mazo y roba una nueva mano de %2 carta(s).%1 barajea su mazo y roba una nueva mano de %2 carta(s)%1 barajea su mazo y roba una nueva mano de %2 carta(s) - + %1 shuffles their deck and draws a new hand. %1 Baraja su maso y robas una nueva mano. - + You are watching a replay of game #%1. Estás viendo una repetición de la partida #%1. - + %1 is ready to start the game. %1 está preparado/a para empezar la partida. - + cards an unknown amount of cards cartas - + %1 card(s) a card for singular, %1 cards for plural - %1 carta(s)%1 cars%1 cars + %1 carta%1 cartas%1 carta(s) - + %1 lends %2 to %3. %1 presta %2 a %3. - + %1 reveals %2 to %3. %1 revela %2 a %3. - + %1 reveals %2. %1 revela %2. - + %1 randomly reveals %2%3 to %4. %1 revela al azar %2%3 a %4. - + %1 randomly reveals %2%3. %1 revela al azar %2%3. - + %1 peeks at face down card #%2. %1 está mirando la carta #%2 boca abajo. - + %1 peeks at face down card #%2: %3. %1 mira la carta boca abajo #%2: %3. - + %1 reveals %2%3 to %4. %1 revela %2%3 a %4. - + %1 reveals %2%3. %1 revela %2%3. - + %1 reversed turn order, now it's %2. %1 Orden del turno invertido, ahora es %2. - + reversed Invertido. - + normal Normal. - + Heads Cara - + Tails Cruz - + %1 flipped a coin. It landed as %2. %1 tira una moneda. Ha salido %2. - + %1 rolls a %2 with a %3-sided die. %1 sacó un %2 con un dado de %3 caras. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 lanza %2 monedas. Hay %3 que salió cara y %4 que salió cruz - + %1 rolls a %2-sided dice %3 times: %4. - + %1 lanza un dado de %2 caras %3 veces: %4. - + %1's turn. Turno %1. - + %1 sets annotation of %2 to %3. %1 sets annotation of %2 to %3. - + %1 places %2 %3%4 counter(s) on %5 (now %6). - + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + %1 failed to undo their last draw. - + %1 sets counter %2 to %3 (%4%5). %1 establece el contador %2 a %3 (%4%5). - + %1 sets %2 to not untap normally. %1 establece que %2 no se endereze normalmente. - + %1 sets %2 to untap normally. %1 establece que %2 se endereze normalmente. - + %1 removes the PT of %2. %1 elimina la F/R de %2 - + %1 changes the PT of %2 from nothing to %4. %1 cambia la F/R de %2 de nada a %4. - + %1 changes the PT of %2 from %3 to %4. %1 cambia la F/R de %2 de %3 a %4. - + %1 has locked their sideboard. %1 ha bloqueado su banquillo. - + %1 has unlocked their sideboard. %1 ha desbloqueado su banquillo. - + %1 taps their permanents. %1 gira sus permanentes. - + %1 untaps their permanents. %1 ha enderezado sus permanentes. - + %1 taps %2. %1 gira %2. - + %1 untaps %2. %1 endereza %2. - + %1 shuffles %2. %1 baraja %2. - + %1 shuffles the bottom %3 cards of %2. %1 barajea las últimas %3 cartas de %2. - + %1 shuffles the top %3 cards of %2. %1 barajea las primeras %3 cartas de %2. - + %1 shuffles cards %3 - %4 of %2. %1 barajea las cartas %3 - %4 de %2. - + %1 unattaches %2. %1 desanexa %2. - + %1 undoes their last draw. %1 deshace su último robo. - + %1 undoes their last draw (%2). %1 deshace su último robo (%2). @@ -6393,115 +8045,125 @@ Cockatrice está recargando la base de datos de cartas. MessagesSettingsPage - + Word1 Word2 Word3 Palabra1 Palabra2 Palabra3 - + Add New Message Añadir nuevo mensaje - + Edit Message Editar mensaje - + Remove Message Borrar mensaje - + Add message Añadir mensaje - - + + Message: Mensaje: - + Edit message Editar mensaje - + Chat settings Preferencias de Chat - + Custom alert words Palabras de alerta personalizadas - + Enable chat mentions Habilitar las menciones en el chat - + Enable mention completer Habilitar completado de menciones - + In-game message macros Macros para mensajes durante la partida - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users Ignorar mensajes de sala de usuarios sin registrar - + Ignore private messages sent by unregistered users Ignorar mensajes privados de usuarios sin registrar - + Ignore private messages sent by non-buddy users - - + + Invert text color Invertir el color del texto - + Enable desktop notifications for private messages Habilitar notificaciones de escritorio para los mensajes privados - + Enable desktop notification for mentions Habilitar notificaciones de escritorio para menciones - + Enable room message history on join Habilitar historial de mensajes de sala al unirse - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) (El color es hexadecimal) - + Separate words with a space, alphanumeric characters only Separa las palabras con un espacio, solo caracteres alfanuméricos @@ -6509,42 +8171,42 @@ Cockatrice está recargando la base de datos de cartas. MoveMenu - + Move to Mover a - + &Top of library in random order - + X cards from the top of library... - + X cartas desde la parte superior de la biblioteca... - + &Bottom of library in random order - + T&able Tabla - + &Hand &Mano - + &Graveyard &Cementerio - + &Exile &Exilio @@ -6623,148 +8285,134 @@ Cockatrice está recargando la base de datos de cartas. PaletteEditorDialog - - + + Reset - + Reiniciar - - - Apply - - - - - - Save && Apply - - - - + ▼ Edit Palette - - + + ▶ Edit Palette - + Palette Editor — %1 - + <b>Palette Editor</b> &nbsp;·&nbsp; %1 - - This theme ships no default palette files - - - - - Replace current colours with the theme author's defaults - - - - - Switch between the light and dark palette files + + + Save - Editing: + Delete this scheme's custom palette and revert to the theme default (or the application palette) - Show or hide the per-role colour grid for manual tweaks + Switch between the light and dark palette files + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + ↺ Revert to theme default - + Discard unsaved edits and restore the last saved palette - - Preview this palette without saving to disk + + Cannot save: this theme has no writable directory - + Write palette-%1.toml and reload the theme - - Cannot save: this theme has no directory on disk - - - - + + Save failed - + + Could not write %1 to: %2 - - - No default found - - - - - No default palette file found for the "%1" scheme. - - PaletteGridWidget - + Active - + Disabled - + Deshabilitado - + Inactive - + Normal interactive state - + Widget is disabled / not interactive - + Window is in background / unfocused + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + Phase @@ -6887,30 +8535,21 @@ Cockatrice está recargando la base de datos de cartas. Paso final - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - es - - PlayerActions - - - - + + + + grave cementerio - - - - + + + + exile exilio @@ -6920,7 +8559,7 @@ Cockatrice está recargando la base de datos de cartas. View top cards of library - + Ver cartas superiores de la biblioteca @@ -6943,17 +8582,17 @@ Cockatrice está recargando la base de datos de cartas. Shuffle top cards of library - + Barajar cartas superiores de la biblioteca Shuffle bottom cards of library - + Barajar cartas inferiores de la biblioteca Draw hand - + Robar mano @@ -6963,32 +8602,32 @@ Cockatrice está recargando la base de datos de cartas. Draw cards - + Robar cartas Move top cards to %1 - + Movar las cartas superiores a %1 Move bottom cards to %1 - + Movar las cartas inferiores a %1 Draw bottom cards - + Robar cartas inferiores Create tokens - + Crear fichas Number: - + Numero: @@ -7029,16 +8668,272 @@ Cockatrice está recargando la base de datos de cartas. PlayerMenu - + Player "%1" - Jugador(a) "%1" + Jugador(a) «%1» - + &Counters Contadores + + PlaymatCollectionDialog + + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + PrintingDisabledInfoWidget @@ -7059,12 +8954,12 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelector - + Display Navigation Buttons - + Printing Selector @@ -7072,23 +8967,63 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardOverlayWidget - + Preference Preferencia - + Pin Printing - + Unpin Printing - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards + Mostrar cartas relacionadas + + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. @@ -7105,7 +9040,7 @@ This setting means you'll only see the default printing for each card, instead o Previous Card in Deck - + Carta anterior en el mazo @@ -7121,28 +9056,28 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardSortingWidget - + Alphabetical - + Alfabético - + Preference Preferencia - + Release Date Fecha de lanzamiento - - + + Descending Descendente - + Ascending Ascendente @@ -7158,95 +9093,103 @@ This setting means you'll only see the default printing for each card, instead o PtMenu - + Power / toughness Fuerza / resistencia - + &Increase power &Incrementar fuerza - + &Decrease power &Reducir fuerza - + I&ncrease toughness I&ncrementar resistencia - + D&ecrease toughness R&educir resistencia - + In&crease power and toughness In&crementar fuerza y resistencia - + Dec&rease power and toughness Red&ucir fuerza y resistencia - + Increase power and decrease toughness Incrementa fuerza y reducir resistencia - + Decrease power and increase toughness Reducir fuerza e incrementar resistencia - + Set &power and toughness... Establecer &fuerza y resistencia... - + Reset p&ower and toughness Reiniciar f&uerza y resistencia + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services Servicios - + Hide %1 Ocultar %1 - + Hide Others Ocultar otros - + Show All Mostrar todo - + Preferences... Preferencias... - + Quit %1 Salir %1 - + About %1 Sobre %1 @@ -7254,17 +9197,17 @@ This setting means you'll only see the default printing for each card, instead o QObject - + Cockatrice card database (*.xml) Base de datos de cartas de Cockatrice (*.xml) - + All files (*.*) Todos los archivos (*.*) - + Cockatrice replays (*.cor) Repeticiones de Cockatrice (*.cor) @@ -7284,17 +9227,22 @@ This setting means you'll only see the default printing for each card, instead o Fichas - + + Maybeboard + + + + Overwrite Existing File? ¿Sobrescribir archivo existente? - + A .cod version of this deck already exists. Overwrite it? Ya existe una versión .cod de este mazo. ¿Sobrescribirla? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7305,7 +9253,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7316,100 +9264,110 @@ Are you sure you would like to disable this feature? - + Confirm Change + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK OK - + Save Guardar - + Save All Guardar todo - + Open Abrir - + &Yes &Sí - + Yes to &All Sí a &Todo - + &No &No - + N&o to All N&o a todo - + Abort Abortar - + Retry Volver a intentar - + Ignore Ignorar - + Close Cerrar - + Cancel Cancelar - + Discard Descartar - + Help Ayuda - + Apply Aplicar - + Reset Reiniciar - + Restore Defaults Restaurar predeterminados @@ -7417,121 +9375,189 @@ Are you sure you would like to disable this feature? QuickSetupPanel - + %1% - + %1% - + <b>Quick Setup</b> - + Generate all palette roles automatically from a single accent colour - + Accent: - + Primary hue. Used directly for highlights and links. At high intensity it also tints buttons and backgrounds. - + Intensity: - + Subtle - + Full colour - + 0–30 Subtle tint — only highlights and links change hue 30–70 Accented — buttons, tooltips, and borders join in 70–100 Full colour — backgrounds, everything - + 70% - - - Generate ↓ - - - - - Derive all palette roles from the accent colour above. -Fine-tune individual colours in the grid afterwards. - - RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name Nombre - + ID ID - + Upload time Tiempo de subida + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID ID - + Name Nombre - + Players Jugadores - + Keep Mantener - + Time started Tiempo de inicio - + Duration (sec) Duración (seg.) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu @@ -7573,37 +9599,37 @@ Fine-tune individual colours in the grid afterwards. RoomSelector - + Rooms Salas - + Joi&n E&ntrar - + Room Sala - + Description Descripción - + Permissions Permisos - + Players Jugadores - + Games Partidas @@ -7611,7 +9637,7 @@ Fine-tune individual colours in the grid afterwards. SayMenu - + S&ay Decir @@ -7619,32 +9645,32 @@ Fine-tune individual colours in the grid afterwards. SequenceEdit - + Choose an action from the table Selecciona una acción de la tabla - + Hit the key/combination of keys you want to set for this action Presiona la tecla/combinación de teclas que desees configurar para esta acción - + Invalid key Clave inválida - + Shortcut already in use by: - + Clear Limpiar - + Restore default Restaurar configuración predeterminada @@ -7677,58 +9703,94 @@ Fine-tune individual colours in the grid afterwards. Fecha de lanzamiento + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts Restaurar atajos por defecto - + Do you really want to restore all default shortcuts? ¿Realmente quieres restablecer los atajos predeterminados? - + Clear all default shortcuts Limpiar todos los atajos predeterminados - + Do you really want to clear all shortcuts? ¿Realmente quieres limpiar los atajos predeterminados? - + Section: Sección: - + Action: Acción: - + Shortcut: Atajo: - + + Shortcut editor + + + + How to set custom shortcuts Cómo fijar atajos personalizados - + Clear all shortcuts - + Limpiar todos los atajos - + Search by shortcut name - + Buscar por nombre de atajo @@ -7781,12 +9843,12 @@ Por favor, revise su configuración de atajos. SideboardMenu - + &Sideboard &Banquillo - + &View sideboard Ver banquillo @@ -7794,27 +9856,27 @@ Por favor, revise su configuración de atajos. SoundSettingsPage - + Enable &sounds Activar &sonidos - + Current sounds theme: Tema de sonidos actual: - + Test system sound engine Probar el audio del sistema - + Sound settings Preferencias de sonido - + Master volume Volumen maestro @@ -7822,48 +9884,48 @@ Por favor, revise su configuración de atajos. SpoilerBackgroundUpdater - + Spoilers season has ended La temporada de Spoilers ha terminado - + Deleting spoiler.xml. Please run Oracle Eliminando spoiler.xml Por favor, ejecute Oracle - - + + Spoilers download failed Error al descargar Spoilers - + No internet connection No hay conexión a internet - + Error Error - + Spoilers already up to date Los spoilers ya están actualizados - + No new spoilers added No se han añadido nuevos spoilers - + Spoilers have been updated! Los Spoilers han sido actualizados! - + Last change: Último cambio: @@ -7871,175 +9933,204 @@ Por favor, revise su configuración de atajos. StableReleaseChannel - + Default Por defecto - + No reply received from the release update server. No se recibe respuesta del servidor de lanzamiento de actualizaciones. - + Invalid reply received from the release update server. Respuesta invalida del servidor de lanzamiento de actualizaciones. - + No reply received from the tag update server. No se recibe respuesta del servidor de etiquetas de actualizaciones. - + Invalid reply received from the tag update server. Respuesta invalida del servidor de etiquetas de actualizaciones. + + StatsTally + + + Total Power + + + + + Total Toughness + + + StorageSettingsPage - - - + + + Success - + Cached card pictures have been reset. - + Las imágenes de las cartas en caché se han reiniciado. - + Downloaded card pictures have been reset. - + Las imágenes de las cartas descargadas se han reiniciado. - + Error - + Error - + One or more downloaded card pictures could not be cleared. - + In-memory (currently loaded) card pictures have been reset. - + Card Picture Loader Caching Method: - + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. - + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. - + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. - + Delete Cached Images - + Eliminar las imágenes en caché - + Delete Saved Images - + Clear In-Memory Images - + Card Picture Loader Cache Method - + Network Cache - + Filesystem - + Sistema de archivos - + In-Memory Picture Cache - + Network Cache Size: - + On-disk cache for downloaded pictures - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + In-memory cache for pictures not currently on screen - + Naming scheme: - + Day(s) - + Día(s) TabAccount - - Add to Buddy List + + + My Reports - + + Add to Buddy List + Añadir a la lista de amigos + + + Add to Ignore List Añadir a la Lista de Ignorados - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account Cuenta @@ -8160,224 +10251,224 @@ Por favor, revise su configuración de atajos. TabArchidekt - - - + + + Desc. - - + + AND Y - - + + Require ALL selected colors - - + + Deck name... Nombre del mazo... - - + + Owner... - + Propietario/a... - - + + Packages - + Paquetes - - + + Advanced Filters - + Bracket: - - + + Any Alguno - - + + Contains card... - - + + Commander... Comandante... - - + + Tag... - + Etiqueta - - + + Deck Size Tamaño de mazo - + Cards: Cartas: - - + + Asc. - + Sort by: - + Ordenar por: - + Filter by: Filtrar por: - + Display Settings - - + + Search - + Buscar - - + + Formats Formatos Archidekt: - + Archidekt TabCardArtRules - + Card: - + Carta: - + ProviderId: - - - Mode: - - - Reason: - + Mode: + Modo: - + + Reason: + Razón: + + + Type a card name... - + Add rule - + Añadir regla - + Remove rule - + Borrar regla - + Refresh - + Actualizar Card Art Rules - + Reglas de arte de carta TabDeckEditor - + Card Info Información de la carta - + Deck Mazo - + Filters Filtros - + &View &Ver - + Card Database Base de datos de cartas - + Printing - + Visible Visible - + Floating Flotante - + Reset layout Reinicializar la disposición - + Deck: %1 Mazo: %1 @@ -8385,72 +10476,72 @@ Por favor, revise su configuración de atajos. TabDeckEditorVisual - + Visual Deck: %1 Mazo visual: %1 - + &Visual Deck Editor Editor de mazos visual - - + + Card Info Información de la carta - - + + Deck Mazo - + &View Ver - + Printing - + Visible Visible - + Floating - + Reset layout - + Restablecer disposición TabDeckEditorVisualTabWidget - + Visual Deck View Vista de mazos visual - + Visual Database Display - + Deck Analytics Análisis de mazo - + Sample Hand @@ -8458,139 +10549,237 @@ Por favor, revise su configuración de atajos. TabDeckStorage - + Local file system Sistema de archivos local - + Server deck storage Almacen de mazos del servidor - - + + Open in deck editor Abrir en el editor de mazos - + Rename deck or folder Renombrar mazo o carpeta - + Upload deck Subir mazo - + Download deck Descargar mazo - - - - + + + + New folder Nueva carpeta - - + + Delete Borrar - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder Abrir carpeta de mazos - + Rename local folder Renombrar carpeta local - + Rename local file - + Renombrar archivo local - + New name: Nombre nuevo: - - - - + + + + + + + Error Error - + Rename failed - - + + + Invalid deck file Archivo de baraja inválido - + Enter deck name Introduzca el nombre del mazo - + This decklist does not have a name. Please enter a name: Esta lista de mazo no tiene un nombre. Por favor, introduce un nombre: - + Unnamed deck Mazo sin nombre - + Failed to upload deck to server Fallo al subir baraja al servidor - + Delete local file Borrar fichero local - + Are you sure you want to delete the selected files? ¿Estás seguro/a de que quieres eliminar los archivos seleccionados? - + Delete remote decks Borrar mazos remotos - + Are you sure you want to delete the selected decks? ¿Estás seguro/a de que quieres eliminar los mazos seleccionados? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: Nombre de la nueva carpeta: - + Deck Storage Almacén de mazos @@ -8598,20 +10787,271 @@ Por favor, introduce un nombre: TabDeckStorageVisual - + Visual Deck Storage Almacén de mazos visual - + Error Error - + Could not open deck at %1 No se pudo abrir mazo en %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -8624,27 +11064,27 @@ Por favor, introduce un nombre: TabEdhRecMain - + &Cards &Cartas - + Top Commanders - + Los mejores comandantes - + Tags Etiquetas - + Search for a card ... Buscar una carta ... - + Search Buscar @@ -8657,191 +11097,207 @@ Por favor, introduce un nombre: TabGame - - - + + + Replay Repetir - - + + Game Partida - - + + Player List Lista de jugadores - - + + + Invite + + + + + Card Info Información de la carta - - + + Messages Mensajes - - + + Replay Timeline Historial de partidas - + &Phases &Fases - + &Game &Partida - + Next &phase Próxima &fase - + Next phase with &action Próxima fase con &acción - + Next &turn Próximo &turno - + Reverse turn order Orden del turno invertido. - + &Remove all local arrows &Retirar todas las flechas locales - + Rotate View Cl&ockwise Girar en sentido horario - + Rotate View Co&unterclockwise Girar en sentido antih&orario (&U) - + Game &information &Información de la partida - - Un&concede + + Cop&y game link - - - + + Invite to Game... + + + + + Un&concede + Revertir rendición + + + + + &Concede &Conceder - + &Leave game &Abandonar la partida - + C&lose replay &Cerrar repetición - + &Focus Chat &Resaltar chat - + &Say: &Decir: - + Selected cards Cartas seleccionadas - + &View &Ver - + Visible Visible - + Floating Flotante - + Reset layout Resetear la distribución - + Concede Conceder - + Are you sure you want to concede this game? ¿Estás seguro de que quieres conceder esta partida? - + Unconcede Revertir rendición - + You have already conceded. Do you want to return to this game? Ya te rendiste. ¿Quieres regresar a este juego? - + Leave game Abandonar la partida - + Are you sure you want to leave this game? ¿Estás seguro de que quieres abandonar la partida? - + A player has joined game #%1 Un jugador se ha unido a la partida #%1 - + %1 has joined the game %1 se ha unido a la partida - + You have been kicked out of the game. Has sido expulsado de la partida. @@ -8857,158 +11313,158 @@ Por favor, introduce un nombre: TabLog - + Logs Historial - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs Registros de sala - + Game Logs Registros de partidas - + Chat Logs Registros de chat - - + + Error Error - + You must select at least one filter. Debes seleccionar como mínimo un filtro. - + You have to select a valid number of days to locate. Debes seleccionar un número de días válido para buscar. - + Username: Nombre de usuario: - + IP Address: Dirección IP: - + Game Name: Nombre de partida: - + GameID: ID de partida: - + Message: Mensaje: - + Main Room Sala Principal - + Game Room Sala de Partida - + Private Chat Chat privado - + Past X Days: Pasados X días: - + Today Hoy - + Last Hour Última hora - + Maximum Results: Resultados máximos: - + At least one filter is required. The more information you put in, the more specific your results will be. Se requiere un filtro como mínimo. Cuando más información introduzcas, más específicos serán los resultados. - + Get User Logs Ver registros de usuario - + Clear Filters Borrar filtros - + Filters Filtros - + Log Locations Localización de registro. - + Date Range Alcance de fecha - + Maximum Results Resultados máximos - - + + Message History Historial de mensajes - + Failed to collect message history information. Error al recopilar información del historial de mensajes. - + There are no messages for the selected filters. No hay mensajes con los filtros seleccionados @@ -9016,218 +11472,558 @@ Cuando más información introduzcas, más específicos serán los resultados. TabMessage - + Private &chat &Chat privado - + &Leave &Cerrar - + %1 - Private chat %1 - Chat privado - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. El usuario te esta ignorando, los usuarios no podrán ver tus mensajes en el chat principal y no podrás unirte a sus partidas. - + Private message from Mensaje privado de - + %1 has left the server. %1 ha abandonado el servidor. - + %1 has joined the server. %1 se ha unido al servidor. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system Sistema de archivos local - + Server replay storage Almacén de repeticiones del servidor - - + + Watch replay Ver repetición - + Rename Renombrar - - + + New folder Nueva carpeta - - + + Delete Borrar - + Open replays folder Abrir carpeta de repeticiones - + Download replay Descargar repetición - + Toggle expiration lock Alternar expiración del bloqueo - + Get replay share code - - + + Look up replay by share code - + Rename local folder Renombrar carpeta local - + Rename local file - - - - - New name: - + Renombrar archivo local + New name: + Nuevo nombre + + + Error Error - + Rename failed - + Name of new folder: Nombre de nueva carpeta - + Delete local file Borrar fichero local - + Are you sure you want to delete the selected files? ¿Estás seguro/a de que quieres eliminar los archivos seleccionados? - + Are you sure you want to delete the selected replays? ¿Estás seguro/a de que quieres eliminar los repeticiones seleccionados? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed Fallido - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard Copiar al portapapeles - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay Borrar repetición remota @@ -9237,65 +12033,478 @@ Cuando más información introduzcas, más específicos serán los resultados.Repetición de partidas + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - - Friends - - - - - Online - - - - - Ignored - - - - + &Say: &Decir: - + Chat Chat - + &Room &Sala - + &Leave room &Dejar sala - + &Clear chat &Limpiar chat - + Chat Settings... Preferencias del Chat... - + mentioned you. te ha mencionado. - + Click to view Click para ver - + You are flooding the chat. Please wait a couple of seconds. Estás saturando el chat. Por favor, espera unos segundos. @@ -9303,35 +12512,35 @@ Cuando más información introduzcas, más específicos serán los resultados. TabServer - + Server Servidor - - - - + + + + Error Error - + Failed to join the server room: it doesn't exist on the server. Fallo al unirse a la sala del servidor: no existe en el servidor. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. El servidor cree que estás en la sala del servidor pero tu cliente es incapaz de mostrarlo. Intenta reiniciar tu cliente. - + You do not have the required permission to join this server room. No tienes los permisos requeridos para unirte a esta sala del servidor. - + Failed to join the server room due to an unknown error: %1. Fallo al unirse a la sala del servidor debido a un error desconocido: %1. @@ -9339,97 +12548,173 @@ Cuando más información introduzcas, más específicos serán los resultados. TabSupervisor - + Deck Editor Editor de mazos - + Visual Deck Editor Editor de mazos visual - + EDHRec EDHRec - + Archidekt - + Archidekt - + Home Inicio - + &Visual Deck Storage Almacén de mazos visual - + Visual Database Display - + Server Servidor - + Account Cuenta - + Deck Storage Almacén de mazos - + Game Replays Repetición de partidas - + Administration Administración - + Logs + Historial + + + + Report Queue - + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? ¿Estás seguro? - + There are still open games. Are you sure you want to quit? Todavía hay partidas abiertas. ¿Estás seguro/a de que quieres salir? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view Click para ver - + Your buddy %1 has signed on! Tu amigo %1 se ha conectado! - + Unknown Event Evento desconocido - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -9439,39 +12724,39 @@ Este mensaje podría significar que hay una nueva versión de Cockatrice disponi Para actualizar tu cliente, ve a Ayuda -> Buscar Actualizaciones. - + Idle Timeout Tiempo de ausencia agotado - + You are about to be logged out due to inactivity. Va a ser desconectado por inactividad. - + Promotion Promoción - + You have been promoted. Please log out and back in for changes to take effect. Has sido promocionado. Por favor cierra la sesión y vuelve a iniciarla para que los cambios surtan efecto. - + Warned Advertido - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Has recibido una advertencia debido a %1 Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas contra usted. Si tiene alguna pregunta, por favor envíe un mensaje privado a un moderador. - + You have received the following message from the server. (custom messages like these could be untranslated) Ha recibido los siguientes mensajes del servidor. @@ -9491,6 +12776,34 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -9505,6 +12818,65 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas Análisis de mazo no disponible. + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -9574,57 +12946,52 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas UserCardArtSettingsDialog - + Card Art Settings - + Preferencias de arte de carta - + Type a card name... - + Card name: + Nombre de carta: + + + + Printing: - - Card ProviderId: + + Card - + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + Left margin (%): - + Right margin (%): - - Vertical offset: - - - - - Zoom: - - - - - Parameters - - - - - Preview - - - - + Remove Banner Card @@ -9632,216 +12999,266 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas UserContextMenu - + User &details &Detalles del usuario - + Private &chat &Chat privado - + Show this user's &games Mostrar &partidas de este usuario - + + View this user's &public decks + + + + Add to &buddy list Añadir a la lista de &amigos - + Remove from &buddy list Quitar de la lista de &amigos - + Add to &ignore list Añadir a la lista de &ignorados - + Remove from &ignore list Quitar de la lista de &ignorados - + Kick from &game Expulsar de la &partida - + + Report user + + + + Warn user Usuario advertido - + View user's war&n history Ver historial de advertencias del usuario - + Ban from &server Banear del &servidor - + View user's &ban history Ver historial historial de baneo del usuario. - + &Promote user to moderator &Promocionar usuario a moderador - + Dem&ote user from moderator Degradar usuario de m&oderador - + Promote user to &judge Usuario promovido a &juez - + Demote user from judge Degradar usuario de juez - + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes - - + Investigate user + + + + + + Error Error - + This user does not exist. Este usuario no existe. - + You are being ignored by %1 and can't see their games. Estás siendo ignorado por %1 y no puedes ver sus partidos. - + Could not get %1's games. - + %1's games Partidas de %1 - - - + + + Ban History Historial de baneo - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. El usuario nunca ha sido baneado - + Failed to collect ban information. Error al recopilar informacion de baneos. - - - + + + Warning History Historial de advertencias - + Warning Time;Moderator;User Name;Reason Warning Time;Moderator;User Name;Reason - + User has never been warned. El usuario nunca ha sido advertido - + Failed to collect warning information. Error al recopilar información de advertencias. - + Failed to get admin notes. - - + + Success Éxito - + Successfully promoted user. Usuario promocionado con éxito. - + Successfully demoted user. Usuario degradado con éxito. - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + Kick Player - + Are you sure you want to kick this player from the game? - - - + + + Failed Fallo - + Failed to promote user. Fallo al promocionar usuario. - + Failed to demote user. Fallo al degradar usuario. - + Copy hash to clipboard Copiar al portapapeles - + Remove this user's messages Borrar mensajes de este usuario @@ -9879,61 +13296,61 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas Administrador - + Moderator Moderador - + Registered user Usuario registrado - - + + Unregistered user Usuario no registrado - + Judge Juez - + Unknown Desconocido - + The entered password does not match your account. La contraseña introducida no coincide con tu cuenta - - - - + + + + Information Información - + User information updated. Información del usuario actualizada. - - - - - - + + + + + - - - + + + + Error Error @@ -9957,283 +13374,292 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas Edit Banner Card + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) %n año,%n años,%n año(s), - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format %10%n día %20%10%n días %20%10%n días %20 - + Enter Password Introducir contraseña - + Password verification is required in order to change your email address Se requiere verificación de la contraseña para cambiar tu dirección de correo electrónico - - - + + + An error occurred while trying to update your user information. Un error ha ocurrido mientras intentaba actualizarse tu información de usuario. - + The selected card is blacklisted on this server or another error occurred. - + Banner card removed. - + Banner card updated. - + This server does not permit you to update your user informations. Este servidor no permite actualizar su información de usuario. - + Password changed. Tu contraseña ha sido cambiada. - + This server does not permit you to change your password. Este servidor no permite cambiar tu contraseña. - + The new password is too short. La nueva contraseña es muy corta - + The old password is incorrect. La contraseña anterior no es correcta. - + Avatar updated. Avatar actualizado - + This server does not permit you to update your avatar. Este servidor no permite actualizar avatar. - + An error occured while trying to update your avatar. - - - This server does not permit you to update your user informations. - An error occured while trying to updater your avatar. - Este servidor no permite actualizar su información de usuario. - UserInfoPopup - - + + Games - + Partidos - + Chat - + Chat - + Open private chat - + Abrir chat privado - + Profile - + Perfil - + View user profile - + Ver perfil de usuario - + Show this user's games + Mostrar los partidos de este usuario + + + + Invite - + + Invite to your game + + + + − Buddy - + − Amigo - + Remove from buddy list - + Quitar de la lista de amigos - + + Buddy - + + Amigo - + Add to buddy list - + Añadir a la lista de amigos - + − Ignore - + Remove from ignore list - + Quitar de la lista de ignorados - + + Ignore - + Add to ignore list - + Añadir a la lista de ignorados - + Ban - + Ban from server - + Warn - + Advertir - + Warn user - + Ban log - + View ban history - + Ver historial de baneo - + Warn log - + View warning history - + Ver historial de advertencias - + Notes - + Notas - + View admin notes - + − Mod - + Demote from moderator - + + Mod - + Promote to moderator - + − Judge - + Demote from judge - + + Judge - + Promote to judge - + Join game - + Spectate - - + + Loading games… - + Could not load games. - + No active games. @@ -10241,223 +13667,344 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas UserInterfaceSettingsPage - + General interface settings Preferencias generales de la interfaz - + &Double-click cards to play them (instead of single-click) &Doble click en las cartas para jugarlas (en lugar de un solo click) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default &Jugar todas las cartas que no sean tierras en la pila (en lugar de en el campo de batalla) por defecto. - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens Anotar en las fichas. - + Show selection count during drag selection - + Show total selection count - - Show subtype breakdown in selection tally + + + Disabled - + + + Enabled + + + + + + Automatic + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + Use tear-off menus, allowing right click menus to persist on screen Usar menus flotantes, permitir click derecho para anclar menus en la pantalla - + Keep game chat focused when clicking in game (Note: disables card view search bar) - + Notifications settings Ajustes de notificaciones - + Enable notifications in taskbar Habilitar notificaciones en la barra de tareas - + Notify in the taskbar for game events while you are spectating Notificar en la barra de tareas los eventos de la partida mientras estás como espectador - + Notify in the taskbar when users in your buddy list connect Recibir una notificación en la barra de tareas cuando alguien de tu lista de amigos se conecte - + Animation settings Preferencias de animación - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation Animación de &girar/enderezar - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings Preferencias de editor/almacén de mazos - + Open deck in new tab by default + Abrir mazo en nueva pestaña por defecto + + + + Show card counts in Visual Deck Editor - + Use visual deck storage in game lobby Usar almacén de mazos visual en el vestíbulo de juego - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - - do nothing - no hacer nada - - - - ask to convert to .cod + + Do nothing - - always convert to .cod + + Ask to convert to .cod - + + Always convert to .cod + + + + Default deck editor type - + Tipo de editor de mazos por defecto - + Classic Deck Editor - + Editor de mazos clásico - + Visual Deck Editor Editor de mazos visual - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings Preferencias de repetición - + Buffer time for backwards skip via shortcut: + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 - + Usuarios conectado al servidor: %1 - + Users in this room: %1 - + Usuarios en esta sala: %1 - + Buddies online: %1 / %2 Amigos en línea: %1 / %2 - + Ignored users online: %1 / %2 Usuarios ignorados en línea: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters - + &Untap all permanents - + Enderezar todos los permanentes - + R&oll die... &Lanzar dado... - + Flip coin - + Lanzar una moneda - + &Create token... Crear ficha... - + C&reate another token Crear otra ficha - + Cr&eate predefined token @@ -10477,13 +14024,13 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas Includes - + Incluye Include / Exclude Mode: Includes - + Incluir / Excluir @@ -10494,22 +14041,22 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... - + Save Filter Guardar filtro - + Save all currently applied filters to a file - + Enter filename... @@ -10517,72 +14064,72 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDatabaseDisplayFilterToolbarWidget - + Sort by - + Ordenar por: - + Filter by Filtrar por - + Save and load filters Guardar y cargar filtros - + Filter by exact card name Filtrar por nombre de carta exacto - + Filter by card main-type Filtrar por tipo principal de carta - + Filter by card sub-type Filtrar por subtipo de carta - + Filter by set Filtrar por expansión - + Filter by format legality - + Save/Load Guardar/Cargar - + Name Nombre - + Main Type Tipo principal - + Sub Type Subtipo - + Sets Ediciones - + Formats Formatos @@ -10625,7 +14172,7 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas Show main types with at least: - + Mostrar tipos principales con al menos: @@ -10684,7 +14231,7 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -10692,19 +14239,19 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDatabaseDisplaySetFilterWidget - + Search sets... Buscar ediciones... - - + + Mode: Exact Match Modo: Coincidencia Exacta - - + + Mode: Includes Modo: Incluye @@ -10719,7 +14266,7 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas Show sub types with at least: - + Mostrar subtipos de carta con al menos: @@ -10750,28 +14297,28 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDatabaseDisplayWidget - + Search by card name (or search expressions) Buscar por nombre de carta (o buscar expresiones) - - + + Visual Visual - + Loading database ... Cargando la base de datos... - + Clear all filters Limpiar todos los filtros - + Table Tabla @@ -10779,43 +14326,43 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDeckDisplayOptionsWidget - + Group by: - + Agrupar por: - + Change how cards are divided into categories/groups. - + Sort by: - + Ordenar por: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat @@ -10831,12 +14378,12 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDeckEditorSampleHandWidget - + Draw a new sample hand Robar una nueva mano de muestra - + Sample hand size @@ -10844,17 +14391,17 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -10862,7 +14409,7 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDeckStorageFolderDisplayWidget - + Deck Storage Almacén de mazos @@ -10870,52 +14417,57 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDeckStorageQuickSettingsWidget - + Show Folders Mostrar las carpetas - + Show Tag Filter - + Show Color Identity - + Mostrar identidad de color - + Show Tags On Deck Previews - + + Show Upload Time + + + + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None Ninguno - + Filepath @@ -10923,7 +14475,7 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -10931,22 +14483,22 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -10954,17 +14506,22 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas VisualDeckStorageWidget - + Loading database ... Cargando la base de datos... - + Refresh loaded files - + + Select decks to share + + + + Visual Deck Storage Settings Preferencias de almacén de mazos visual @@ -10972,177 +14529,202 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas WarningDialog - + Which warning would you like to send? ¿Qué advertencia quieres enviar? - + Redact all messages from this user in all rooms Redactar todos los mensajes de este usuario en todas las salas - + &OK &OK - + &Cancel &Cancelar - + Warn user for misconduct Usuario advertido por conducta inadecuada - - + + Error Error - + User name to send a warning to can not be blank, please specify a user to warn. El nombre de usuario no puede quedarse en blanco, por favor especifique un usuario al que advertir. - + Warning to use can not be blank, please select a valid warning to send. La advertencia no puede quedarse en blanco, por favor seleccione una advertencia válida. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top Mover el set seleccionado al principio - + Move selected set up Mover arriba el set seleccionado - + Move selected set down Mover abajo el set seleccionado - + Move selected set to the bottom Mover el set seleccionado al final - - Search by set name, code, or type - Buscar por nombre de edición, código, o tipo - - - + Default order Orden por defecto - + Restore original art priority order Restaurar orden predeterminado de arte original - + Enable all sets Seleccionar todos los sets - + Disable all sets Deseleccionar todos los sets - + Enable selected set(s) Habilitar set(s) seleccionados - + Disable selected set(s) Deshabilitar set(s) seleccionados - + Deck Editor Editor de mazo - + Use CTRL+A to select all sets in the view. Usa CTRL+A para seleccionar todas las colecciones aquí mostradas. - + Only cards in enabled sets will appear in the card list of the deck editor. Solo las cartas en las colecciones permitidas aparecerán en la lista de cartas del editor de mazos. - + Image priority is decided in the following order: La prioridad de las imágenes es decidida en el siguiente orden: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki primero la Carpeta PERSONALIZADA (%1), luego las Ediciones Habilitadas en este mensaje (de Arriba a Abajo) - + Include cards rebalanced for Alchemy [requires restart] - + Card Art Arte de carta - + + Search by set name, code, type, or release date + + + + How to use custom card art Cómo usar arte de carta personalizada. - + Hints Consejos - + Note Nota - + Sorting by column allows you to find a set while not changing set priority. Ordenar por columna te permite encontrar una edición sin cambiar la prioridad de edición. - + To enable ordering again, click the column header until this message disappears. Para habilitar el orden de nuevo, haga click en el encabezado de la columna hasta que este mensaje deje de aparecer. - + Use the current sorting as the set priority instead Usa el filtro actual como la prioridad de edición - + Sorts the set priority using the same column Ordena la prioridad de edición usando la misma columna - + Manage sets Mantenedor de ediciones @@ -11150,93 +14732,85 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas ZoneViewWidget - + Search by card name (or search expressions) Buscar por nombre de carta (o buscar expresiones) - + Ungrouped Desagrupado - + Group by Type Agrupar por tipo - + Group by Mana Value Agrupar por valor de maná - + Group by Color Agrupar por color - + Unsorted Desordenado - + Sort by Name Ordenar por nombre - + Sort by Type Ordenar por tipo - + Sort by Mana Cost Ordenar por coste de maná - + Sort by Colors Ordenar por colores - + Sort by P/T - + Ordenar por F/R - + Sort by Set - + shuffle when closing barajar al cerrar - + pile view vista de la pila - - i18n - - - English - Español (Spanish) - - main - + Connect on startup Conectar al inicio - + Debug to file Depurar a archivo @@ -11575,22 +15149,22 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas Show Status Bar - + Mostrar barra de estado Load deck from online service... - + Cargar desde el servicio en línea... Load from website... - + Cargar desde el sitio web... Unload Deck - + Desmontar mazo @@ -11775,7 +15349,7 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas Increment all card counters - + Incrementa contadores en todas las cartas @@ -11900,7 +15474,7 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas Hide Card in Reveal Window - + Ocultar carta en ventana de reveladas @@ -11916,7 +15490,7 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas Toggle Skip Untapping Toggle Untap - + Alterna saltar fase de destapar @@ -11981,22 +15555,22 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas Select All Cards in Zone - + Seleccionar todas las cartas en zona Select All Cards in Row - + Seleccionar todas las cartas en fila Select All Cards in Column - + Seleccionar todas las cartas en columna Reveal Selected Cards to All Players - + Muestra cartas seleccionadas a todos los jugadores @@ -12020,6 +15594,16 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas Graveyard Cementerio + + + Report Queue + + + + + Moderation + + @@ -12062,7 +15646,7 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas Bottom Cards of Library - + Cartas del fondo de la biblioteca @@ -12102,7 +15686,7 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas Stack Until Found - + Apilar hasta encontrado @@ -12142,7 +15726,7 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas Flip Coin - + Realizar un volado @@ -12152,12 +15736,12 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas Shuffle Top Cards of Library - + Barajar cartas superiores de la biblioteca Shuffle Bottom Cards of Library - + Barajar cartas inferiores de la biblioteca @@ -12167,12 +15751,12 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas Mulligan (Same hand size) - + Mulligan (Mismo tamaño de mano) Mulligan (Hand size - 1) - + Mulligan (Cartas en mano - 1) @@ -12202,27 +15786,27 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas Sort Hand by Name - + Ordenar mano por nombre Sort Hand by Type - + Ordenar mano por tipo Sort Hand by Mana Value - + Ordenar mano por valor de maná Reveal Hand to All Players - + Revelar mano a todos los jugadores Reveal Random Card to All Players - + Revelar al azar una carta a todos los jugadores @@ -12257,32 +15841,32 @@ Por favor, absténgase de realizar de nuevo esta actividad o se tomarán medidas Skip Forward - + Saltar hacia adelante Skip Backward - + Saltar hacia atrás Skip Forward by a lot - + Saltar mucho hacia adelante Skip Backward by a lot - + Saltar mucho hacia atrás Play/Pause - + Reproducir/Pausar Toggle Fast Forward - + Alternar adelanto rápido diff --git a/cockatrice/translations/cockatrice_et.ts b/cockatrice/translations/cockatrice_et.ts index 2df3cbdac..7343c2336 100644 --- a/cockatrice/translations/cockatrice_et.ts +++ b/cockatrice/translations/cockatrice_et.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Komplektide loendaja... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter - + New value for counter '%1': @@ -23,12 +23,12 @@ AbstractDlgDeckTextEdit - + &Refresh - + Parse Set Name and Number (if available) @@ -36,79 +36,130 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. - + There are no cards in your deck to be exported + + + AccountSetupPage - - No deck was selected to be exported. + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + + + AddAnalyticsPanelDialog + + + Add Analytics Panel AdminNotesDialog - + Update Notes - + Admin Notes for %1 @@ -116,202 +167,339 @@ Please check that the directory is writable and try again. AllZonesCardAmountWidget - + Mainboard - + Sideboard + + + Tokens + + AppearanceSettingsPage - + seconds - + Error - + Could not create themes directory at '%1'. - - Enabling this feature will disable the use of the Printing Selector. - -You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. - -You will have to use the Set Manager, available through Card Database -> Manage Sets. - -Are you sure you would like to enable this feature? - - - - - Disabling this feature will enable the Printing Selector. - -You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. - -You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). - -Are you sure you would like to disable this feature? - - - - - Confirm Change - - - - + Theme settings Teema valikud - + Current theme: Praegune teema: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings - + Show keyboard shortcuts in right-click menus - + + Show game filter toolbar above list in room tab + + + + Card rendering Kaartide visualiseerimine - + Display card names on cards having a picture Kuva piltidega kaartidel kaardi nimi - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + + Light + + + + + Dark + + + + + + System + + + + + Show all playmats + + + + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + + Active theme palette: + + + + + Active theme style: + + + + + Qt widget style saved to this theme ("System" lets the application decide) + + + + + Edit theme palette + + + + + Home tab settings + + + + + Display card name of background in bottom right + + + + + Dim the home tab background + + + + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + + Styling settings + + + + + Style user list + + + + + Card printings + + + + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Muuda kaartide mõõtkava hiire ülejooksul - + Use rounded card corners - + + Card layout + + + + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Käe välimus - + Display hand horizontally (wastes space) Kuva käsi horisontaalselt (raiskab ruumi) - + Enable left justification Luba vasakvormistus - + Table grid layout Laua ruudustiku vormistus - + Invert vertical coordinate Põõra vertikaalne koordinaat pahupidi - + Minimum player count for multi-column layout: Väikseim mängijate kogus mitmetulbaliseks vormistuseks: - + Maximum font size for information displayed on cards: Kaartidel kujutatava teabe fondi suurim suurus: + + ArchidektApiResponseDeckDisplayWidget + + + Back to results + + + + + Open Deck in Deck Editor + + + BackgroundSources @@ -333,112 +521,112 @@ Are you sure you would like to disable this feature? BanDialog - + ban &user name blokeeri &kasutajanimi - + ban &IP address blokeeri &IP-aadress - + ban client I&D blokeeri kliendi I&D - + Ban type Blokeerimise tüüp - + &permanent ban &igavene blokeering - + &temporary ban &ajutine blokeering - + &Days: &Päeva: - + &Hours: &Tundi: - + &Minutes: &Minutit - + Duration of the ban Blokeerimise kestvus - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Palun sisestage blokeerimise põhjus. See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. - + Please enter the reason for the ban that will be visible to the banned person. Palun sisestage blokeerimise põhjus, mis on nähtav blokeeritavale. - + Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Tühista - + Ban user from server Blokeeri kasutaja serverist - - - - + + + + Error Viga - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Peate valima, kas nimepõhise, IP-põhise või klientidpõhise blokeeringu või mingi kombinatsiooni kolmest et blokeerida. - + You must have a value in the name ban when selecting the name ban checkbox. Sul peab olema väärtus nime blokeeringus kui valid nime blokeerimise. - + You must have a value in the ip ban when selecting the ip ban checkbox. Sul peab olema väärtus IP blokeeringus kui valid IP blokeerimise. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Sul peab olema väärtus klientid blokeeringus kui valid klientid blokeerimise. @@ -446,152 +634,378 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. BetaReleaseChannel - + Beta - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the file update server. + + CardArtPreviewWidget + + + Banner preview + + + + + + + + Zoom %1× + + + + + No card selected + + + + + CardArtRulesModel + + + Card + + + + + ProviderId + + + + + Mode + + + + + Reason + + + CardDatabaseModel - + Name Nimi - + Sets Komplektid - + Mana cost Manahind - + Card type Kaardi tüüp - + P/T T/V - + Color(s) Värv(id) + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + + + CardDatabaseView + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Add to Zone + + + + + Create &new zone... + + + + + Show on EDHRec (Commander) + + + + + Show on EDHRec (Card) + + + + + Show Related cards + + + CardFilter - + AND Logical conjunction operator used in card filter - + OR Logical disjunction operator used in card filter - + AND NOT Negated logical conjunction operator used in card filter - + OR NOT Negated logical disjunction operator used in card filter - + Name - + + Name (Exact) + + + + Type - + Color - + Text - + Set - + Mana Cost - + Mana Value - + Rarity - + Power - + Toughness - + Loyalty - + Format - + Main Type - + Sub Type @@ -622,22 +1036,22 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -645,17 +1059,27 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. CardInfoTextWidget - + Name: + Set: + + + + + Collector Number: + + + + Related cards: - + Unknown card: @@ -663,132 +1087,150 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - - Toggle &normal untapping + + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + + Reduce life by power + + + + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... + + CardPictureLoaderCacheMethod + + + Network Cache + + + + + Filesystem + + + CardSizeWidget - + Card Size @@ -796,142 +1238,160 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. CardZoneLogic - + their hand nominative - + %1's hand nominative - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - + their graveyard nominative - + %1's graveyard nominative - + their exile nominative - + %1's exile nominative - + their sideboard look at zone - + %1's sideboard look at zone - + their sideboard nominative - + %1's sideboard nominative - + their custom zone '%1' nominative - + %1's custom zone '%2' nominative + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser - + Parse error at line %1 col %2: @@ -939,29 +1399,572 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. CockatriceXml4Parser - + Parse error at line %1 col %2: + + ColorButton + + + Click to pick a color + + + + + Pick colour + + + + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + + + ConnectionController + + + + The server has reached its maximum user capacity, please check back later. + + + + + There are too many concurrent connections from your address. + + + + + Banned by moderator + + + + + Expected end time: %1 + + + + + This ban lasts indefinitely. + + + + + Scheduled server shutdown. + + + + + + Invalid username. + + + + + You have been logged out due to logging in at another location. + + + + + Connection closed + + + + + The server has terminated your connection. +Reason: %1 + + + + + The server is going to be restarted in %n minute(s). +All running games will be lost. +Reason for shutdown: %1 + + + + + Scheduled server shutdown + + + + + Failed Login + + + + + Your client seems to be missing features this server requires for connection. + + + + + To update your client, go to 'Help -> Check for Client Updates'. + + + + + + + + + + + + + + + + + + + + + + Error + + + + + Incorrect username or password. Please check your authentication information and try again. + + + + + There is already an active session using this user name. +Please close that session first and re-login. + + + + + + You are banned until %1. + + + + + + You are banned indefinitely. + + + + + This server requires user registration. Do you want to register now? + + + + + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. +Please close and reopen your client to try again. + + + + + An internal error has occurred, please close and reopen Cockatrice before trying again. +If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. + + + + + Account activation + + + + + Your account has not been activated yet. +You need to provide the activation token received in the activation email. + + + + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + + Server Full + + + + + Unknown login error: %1 + + + + + + +This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. + + + + + + + + + + Registration denied + + + + + Registration is currently disabled on this server + + + + + There is already an existing account with the same user name. + + + + + It's mandatory to specify a valid email address when registering. + + + + + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. + + + + + Password too short. + + + + + Registration failed for a technical problem on the server. + + + + + The connection to the server has been lost. + + + + + Unknown registration error: %1 + + + + + Account activation failed + + + + + Socket error: %1 + + + + + Server timeout + + + + + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. +Local version is %1, remote version is %2. + + + + + Your Cockatrice client is obsolete. Please update your Cockatrice version. +Local version is %1, remote version is %2. + + + + + + Success + + + + + Registration accepted. +Will now login. + + + + + Account activation accepted. +Will now login. + + + + + Information + + + + + This server supports additional features that your client doesn't have. +This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. + +To update your client, go to Help -> Check for Updates. + + + + + + + Reset Password + + + + + Your password has been reset successfully, you can now log in using the new credentials. + + + + + Failed to reset user account password, please contact the server operator to reset your password. + + + + + Activation request received, please check your email for an activation token. + + + + + Connecting to %1... + + + + + Registering to %1 as %2... + + + + + Disconnected + + + + + Connected, logging in at %1 + + + + + Requesting forgotten password to %1 as %2... + + + + + Your username must respect these rules: + + + + + is %1 - %2 characters long + + + + + can %1 contain lowercase characters + + + + + + + + NOT + + + + + can %1 contain uppercase characters + + + + + can %1 contain numeric characters + + + + + can contain the following punctuation: %1 + + + + + first character can %1 be a punctuation mark + + + + + no unacceptable language as specified by these server rules: + note that the following lines will not be translated + + + + + can not contain any of the following words: %1 + + + + + can not match any of the following expressions: %1 + + + + + You may only use A-Z, a-z, 0-9, _, ., and - in your username. + + + CustomZoneMenu - + C&ustom Zones - - + + View custom zone '%1' + + DeckAnalyticsWidget + + + Add Panel + + + + + Remove Panel + + + + + Save Layout + + + + + Load Layout + + + + + Include Sideboard + + + + + DeckEditorCardDatabaseDockWidget + + + Card Database + + + DeckEditorCardInfoDockWidget - + Card Info @@ -969,47 +1972,17 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - - Add to Deck - - - - - Add to Sideboard - - - - - Select Printing - - - - - Show on EDHRec (Commander) - - - - - Show on EDHRec (Card) - - - - - Show Related cards - - - - + Add card to &maindeck - + Add card to &sideboard @@ -1017,87 +1990,158 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. DeckEditorDeckDockWidget - + + Loading Database... + + + + Banner Card - + Main Type - + Mana Cost - + Colors - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + + Format: + + + + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1105,17 +2149,17 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1123,114 +2167,119 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - - - Load deck from cl&ipboard... - - - - - Edit deck in clipboard - - - - - - Annotated - - - - - - Not Annotated - - - Save deck to clipboard + Share deck... - Annotated (No set info) + Load deck from cl&ipboard... - Not Annotated (No set info) + Edit deck in clipboard + + + + + + Annotated - &Print deck... + + Not Annotated - Load deck from online service... - - - - - &Send deck to online service + Save deck to clipboard - Create decklist (decklist.org) - - - - - Create decklist (decklist.xyz) + Annotated (No set info) + Not Annotated (No set info) + + + + + &Print deck... + + + + + Load deck from online service... + + + + + &Send deck to online service + + + + + Create decklist (decklist.org) + + + + + Create decklist (decklist.xyz) + + + + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1238,7 +2287,7 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1246,194 +2295,213 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. DeckEditorSettingsPage - - + + Update Spoilers - - + Success - + Download URLs have been reset. - - Downloaded card pictures have been reset. - - - - - Error - - - - - One or more downloaded card pictures could not be cleared. - - - - + Add URL - - + + URL: - - + + Edit URL - - Network Cache Size: - - - - - Redirect Cache TTL: - - - - - How long cached redirects for urls are valid for. - - - - - Picture Cache Size: - - - - + Add New URL - + Remove URL - - Day(s) - - - - + Updating... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - - Delete Downloaded Images - - - - + Reset Download URLs + + + DeckListHistoryManagerWidget - - On-disk cache for downloaded pictures + + Undo - - In-memory cache for pictures not currently on screen + + Redo + + + + + Undo/Redo history + + + + + Click on an entry to revert to that point in the history. + + + + + [redo] + + + + + [undo] DeckListModel - + Count - + Set - + Number Number - + Provider ID - + Card Kaart @@ -1441,12 +2509,12 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1454,17 +2522,22 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -1472,7 +2545,7 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -1480,62 +2553,62 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -1543,98 +2616,216 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. DeckPreviewWidget - + Banner Card - + Open in deck editor - + + Share deck... + + + + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Rename deck + + + + + Rename file + + + + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed + + DeckStateManager + + + Rename deck to "%1" from "%2" + + + + + Updated comments (was %1 chars, now %2 chars) + + + + + Set banner card to %1 (%2) + + + + + Set playmat to %1 + + + + + Tags changed + + + + + Set format to %1 + + + + + Added (%1): %2 (%3) %4 + + + + + Moved to %1 1 × "%2" (%3) + + + + + Removed "%1" (all copies) + + + + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + + %1 1 × "%2" (%3) + + + + + Added + + + + + Removed + + + DeckStatsInterface @@ -1652,94 +2843,122 @@ See on mõeldud ainult moderaatoritele ja blokeeritav isik seda ei näe. DeckViewContainer - + Load deck... Lae pakk... - + Load remote deck... Lae kaugpakk... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start - + Force start - + Sideboard unlocked - + Sideboard locked - - + + Error Viga - + The selected file could not be loaded. Valitud faili ei suudetud laadida. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? - + Remember and automatically apply choice in the future @@ -1752,143 +2971,143 @@ This will kick all non-ready players from the game. - + Known Hosts Teadaolevad hostid - + Delete the currently selected saved server - + Refresh the server list with known public servers - + New Host Uus võõrustaja - + Name: Nimi: - + &Host: &Võõrustaja: - + &Port: &Port: - + Player &name: Mängija &nimi: - + P&assword: S&alasõna: - + &Save password &Salvesta salasõna - + A&uto connect Automaatne ühendamine - + Automatically connect to the most recent login when Cockatrice opens Automaatselt ühenda viimasele sisselogimisele kui Cockatrice avaneb - + If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage - + Reset Password - + Forgot password? - + &Connect - + Server Server - + Login Sisselogimine - + Server Contact - + Connect to Server - + Server URL - + Communication Port - + Unique Server Name - + Connection Warning Ühenduse hoiatus - + You need to name your new connection profile. Peate oma uuele ühendusprofiilile valima nime. - + Connect Warning Ühendumisehoiatus - + The player name can't be empty. Mängija nimi ei saa olla tühi @@ -1981,32 +3200,37 @@ This will kick all non-ready players from the game. - + + Create game as judge + + + + Game setup options - + &Clear &Tühjenda - + Create game Loo mäng - + Game information Mängu info - + Error Viga - + Server error. Serveri viga. @@ -2014,97 +3238,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Nimi: - + Token Märgistused - + C&olor: V&ärv: - + white valge - + blue sinine - + black must - + red punane - + green roheline - + multicolor mitmevärviline - + colorless värvitu - + &P/T: &T/V: - + &Annotation: &Märkus: - + &Destroy token when it leaves the table &Hävita märgistus kui see lahkub laualt - + Create face-down (Only hides name) - + Token data Märgistusinfo - + Show &all tokens Näita &kõik märgistused - + Show tokens from this &deck Näita märgistusi siit &pakist - + Choose token from list Vali märgistus nimekirjast - + Create token Loo märgistus @@ -2112,53 +3336,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + ✖ - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2166,13 +3390,13 @@ This will kick all non-ready players from the game. DlgEditAvatar - - + + No image chosen. Pilti pole valitud - + To change your avatar, choose a new image. To remove your current avatar, confirm without choosing a new image. Et muuta avatari, vali uus pilt. @@ -2180,26 +3404,31 @@ Et eemaldada praegust avatari, kinnita ilma uue pildi valimata. + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... Otsi... - + Change avatar Muuda avatar - + Open Image Ava pilt - + Image Files (*.png *.jpg *.bmp) Pildi Failid (*.png *.jpg *.bmp) - + Invalid image chosen. Vigane pilt valitud. @@ -2207,17 +3436,17 @@ Et eemaldada praegust avatari, kinnita ilma uue pildi valimata. DlgEditDeckInClipboard - + Edit deck in clipboard - + Error - + Invalid deck list. @@ -2225,38 +3454,38 @@ Et eemaldada praegust avatari, kinnita ilma uue pildi valimata. DlgEditPassword - + Old password: Vana salasõna: - + New password: Uus salasõna: - + Confirm new password: Kinnita uus salasõna: - + Change password Vaheta salasõna - - + + Error Viga - + Your password is too short. - + The new passwords don't match. Uued salasõnad ei kattu. @@ -2264,83 +3493,83 @@ Et eemaldada praegust avatari, kinnita ilma uue pildi valimata. DlgEditTokens - + &Name: &Nimi: - + C&olor: V&ärv: - + white valge - + blue sinine - + black must - + red punane - + green roheline - + multicolor mitmevärviline - + colorless värvitu - + &P/T: &T/V: - + &Annotation: &Märkus: - + Token data Märgistusandmed - - + + Add token Lisa märgistus - + Remove token Eemalda märgistus - + Edit custom tokens - + Please enter the name of the token: Sisenda märgistuse nimi: @@ -2374,12 +3603,12 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Ebamääratletud - + Real name: Päris nimi: - + Edit user profile Muuda kasutaja profiili @@ -2417,112 +3646,115 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - + Hide 'buddies only' games - + Hide full games - - Hide games that have started + + Hide started games + Hide games that have started - + Hide password protected games - + Hide 'ignored user' games - - Hide games not created by buddy + + Hide games not created by buddies + Hide games not created by buddy - + Hide games with forced open decklists - + &Newer than: - + Game &description: Mängu &kirjeldus: - - &Creator name: - %Looja nimi: + + &Host name: + &Creator name: + - + General - + &Game types &Mängu tüübid - + at &least: &vähemalt: - + at &most: &enamjaolt: - + Maximum player count Mängijate ülempiir - + Restrictions Piirangud - + Show games only if &spectators can watch - + Show spectator password p&rotected games - + Show only if spectators can ch&at - + Show only if spectators can see &hands - + Spectators - + Filter games Filtreeri mänge @@ -2705,10 +3937,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck @@ -2716,17 +3981,17 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromClipboard - + Load deck from clipboard Lae pakk lõikelaualt - + Error Viga - + Invalid deck list. Vigane pakilist: @@ -2734,43 +3999,43 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgLoadDeckFromWebsite - + Paste a link to a decklist site here to import it. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - - - - - + + + + + Load Deck from Website - + No parser available for this deck provider. (Archidekt, Deckstats, Moxfield, and TappedOut are supported.) - + Network error: %1 - + Received empty deck data. - + Failed to parse deck data: %1 - + The provided URL is not recognized as a valid deck URL. Valid deck URLs look like this: @@ -2784,155 +4049,589 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLoadRemoteDeck - + Load deck Lae pakk + + DlgLocalGameOptions + + + Players: + + + + + General + + + + + Starting life total: + + + + + Game setup options + + + + + Remember settings + + + + + Local game options + + + + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: &Võõrustaja: - + &Port: &Port: - + Player &name: Mängija &nimi: - + P&assword: S&alasõna: - + Password (again): Salasõna (uuesti): - + Email: Email: - + Email (again): Email (uuesti): - + Country: Riik: - + Undefined Ebamääratletud - + Real name: Päris nimi: - + Register to server Registreeri serveriga - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning Registreerimishoiatus - + Your password is too short. - + Your passwords do not match, please try again. Su salasõnad ei kattu, palun proovi uuesti. - + Your email addresses do not match, please try again. Su emailid ei kattu, palun proovi uuesti. - + The player name can't be empty. Mängija nimi ei saa olla tühi. + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice - + Number of sides: - + Number of dice: - + Roll Dice @@ -2940,74 +4639,55 @@ Your email will be used to verify your account. DlgSelectSetForCards - + Unmodified Cards: - + Modified Cards: - + Check Sets to enable them. Drag-and-Drop to reorder them and change their priority. Cards will use the printing of the highest priority enabled set. - + Clear all set information - + Set all to preferred + + + Bulk modified printings. + + + + + Cleared all printing information. + + + + + Set all printings to preferred. + + DlgSettings - + Unknown Error loading card database Ebateatud kaardi laadimis andmebaas - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - Su kaardi andmebaas on vigane. - -Cockatrice ei või korralikult töötada vigase andmebaasiga - -Sul võib vaja olla oraaklit uuesti jooksutada et uuendada kaardi andmebaasi. - -Kas sa tahaksid muuta andmebaasi asukoha seadmeid? - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - Su kaardi andmebaasi versioon on liiga vana. - -See võib luua probleeme kaartide informatsiooni või piltide laadimisega - -Tavaliselt seda saab parantada oraakli uuesti jooksutamisega et uuentada kaartida andmebaasi. - -Kas sa tahad muuta andmebaasi asukoha valikuid? - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3016,7 +4696,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3025,7 +4705,7 @@ Would you like to change your database location setting? Kaas sa tahaks muuta andmebaasi asukoha valikuid? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3034,7 +4714,7 @@ Would you like to change your database location setting? Kas sa tahaks muuta enda andmebaasi asukoha valikuid? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3043,99 +4723,235 @@ Would you like to change your database location setting? - - - + + + Error Viga - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Tee paki kataloogi on vigane. Kas sa tahaks tagasi minna ja seada õige tee? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Tee kaartide piltidele on vigane. Kas sa tahaks tagasi minna ja seada õige tee? - + Settings Valikud - + + Search settings... + + + + + OK + + + + General Üldine - + Appearance Välimus - + User Interface Kasutajaliides - + + Storage + + + + Card Sources - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat Jutustamine - + Sound Hääl - + Shortcuts Lühiteed + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3161,164 +4977,174 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel - + Reinstall - + Cancel Download - + Open Download Page Ava allalaadimisleht - + Check for Client Updates - - - - + + + + Error Viga - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - + Downloading update: %1 - + Checking for updates... Uuenduste otsimine... - + Finished checking for updates - + No Update Available - + Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. - + Current version - + Selected release channel - - + + Update Available - - + + A new version of Cockatrice is available! - - + + New version - - + + Released - - + + Changelog - + Do you want to update now? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error Uuendusviga - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Installing... Paigaldamine... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -3326,21 +5152,153 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing - + Copy to clipboard - + Debug Log Putukaeemalduslogi + + DrawProbabilityConfigDialog + + + Draw Probability Settings + + + + + Criteria: + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + Exactness: + + + + + At least + + + + + Exactly + + + + + Quantity (N): + + + + + Cards drawn (M): + + + + + + cards + + + + + DrawProbabilityWidget + + + Draw Probability + + + + + Probability of drawing + + + + + Card Name + + + + + Type + + + + + Subtype + + + + + Mana Value + + + + + At least + + + + + Exactly + + + + + card(s) having drawn at least + + + + + cards + + + + + Category + + + + + Qty + + + + + Odds (%) + + + EdhrecApiResponseCardInclusionDisplayWidget @@ -3410,33 +5368,39 @@ You may have to manually download the new version. + + EdhrecCommanderApiResponseBracketNavigationWidget + + + Game Changers + + + + + EdhrecCommanderApiResponseBudgetNavigationWidget + + + Budget + + + EdhrecCommanderApiResponseNavigationWidget - + Combos - + Average Deck - - - Game Changers - - - - - Budget - - EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -3444,7 +5408,7 @@ You may have to manually download the new version. FilterBuilder - + Type your filter here @@ -3452,45 +5416,88 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -3498,226 +5505,295 @@ You may have to manually download the new version. GameSelector - - - - - - - - - + + + + + + + + + Error Viga - + Please join the appropriate room first. Palun ühine sobiva toaga enne. - + Wrong password. Vale salasõna. - + Spectators are not allowed in this game. Vaatlejad ei ole selles mängus lubatud. - + The game is already full. Mäng on juba täis. - + The game does not exist any more. - + This game is only open to registered users. - + This game is only open to its creator's buddies. - + You are being ignored by the creator of this game. - + Join Game - + Spectate Game - + Game Information - + + Cop&y game link + + + + + Join Game as Judge + + + + + Spectate Game as Judge + + + + + Join game - - Password: + + The game is full. Join as a spectator instead? - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. - + &Filter games - + C&lear filter - + C&reate - + &Join - + + Join as judge + + + + J&oin as spectator - + + Join as judge spectator + + + + Games shown: %1 / %2 - + Games + + GameSelectorQuickFilterToolBar + + + All types + + + + + Filter by game name... + + + + + Filter by game type/format + + + + + Hide games not created by buddies + + + + + Hide full games + + + + + Hide started games + + + GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password - + buddies only - + reg. users only - + open decklists - - + + can chat - + see hands - + can see hands - + not allowed - + Room - + Age - + Description - - Creator + + Host - + Type - + Restrictions - + Players - + Spectators @@ -3725,143 +5801,275 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + English + Eesti (Estonian) + + + + + + + + + Choose path - - Personal settings + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> - + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + + Language settings + + + + Language: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + + Version settings + + + + + Card database + + + + + Startup settings + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) - + Paths - + How to help with translations - + Decks directory: - + Filters directory: - + Replays directory: - + Pictures directory: - + Card database: - + Custom database directory: - + Token database: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup - + Last update check on %1 (%2 days ago) @@ -3869,47 +6077,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -3917,305 +6125,649 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - - &Sort hand + + Sort hand by... - - Take &mulligan + + Name - + + Type + + + + + Mana Value + + + + + Take &mulligan (Choose hand size) + + + + + Take mulligan (Same hand size) + + + + + Take mulligan (Hand size - 1) + + + + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - Reveal r&andom card to... + + + All players - - - &All players + + Reveal r&andom card to... + + + + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + + Browse Archidekt + + + + View Replays - + Quit - + Connecting... - + Connect - + Play + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu - + &Library - + &View library - + View &top cards of library... - + View bottom cards of library... - + Reveal &library to... - + Lend library to... - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always reveal top card - + &Always look at top card - + &Open deck in deck editor - + &Draw card - + D&raw cards... - + &Undo last draw - + Shuffle - + &Play top card - + Play top card &face down - + Put top card on &bottom - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + + Move top cards to graveyard face down... + + + + Move top cards to &exile... - + + Move top cards to exile face down... + + + + Put top cards on stack &until... - + Shuffle top cards... - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + + Move bottom cards to graveyard face down... + + + + Move bottom cards to &exile... - + + Move bottom cards to exile face down... + + + + Put bottom card on &top - + Shuffle bottom cards... - - + + &All players - + Reveal top cards of library - + Number of cards: (max. %1) @@ -4223,1416 +6775,1244 @@ You may have to manually download the new version. MainWindow - - - The server has reached its maximum user capacity, please check back later. - - - - - There are too many concurrent connections from your address. - - - - - Banned by moderator - - - - - Expected end time: %1 - - - - - This ban lasts indefinitely. - - - - - Scheduled server shutdown. - - - - - - Invalid username. - - - - - You have been logged out due to logging in at another location. - - - - - Connection closed - - - - - The server has terminated your connection. -Reason: %1 - - - - - The server is going to be restarted in %n minute(s). -All running games will be lost. -Reason for shutdown: %1 - - - - - Scheduled server shutdown - - - - - - Success - - - - - Registration accepted. -Will now login. - - - - - Account activation accepted. -Will now login. - - - - - Number of players - - - - - Please enter the number of players. - - - - - + + Player %1 - + Load replay - + About Cockatrice - + Version - + Cockatrice Webpage - + Project Manager: - + Past Project Managers: - + Developers: - + Our Developers - + Help Develop! - + Translators: - + Our Translators - + Help Translate! - + Support: - + Report an Issue - + Troubleshooting - + F.A.Q. - - - - - - - - - - - - - - - - - - - - + + Error - - Server timeout - - - - - Failed Login - - - - - Your client seems to be missing features this server requires for connection. - - - - - To update your client, go to 'Help -> Check for Client Updates'. - - - - - Incorrect username or password. Please check your authentication information and try again. - - - - - There is already an active session using this user name. -Please close that session first and re-login. - - - - - - You are banned until %1. - - - - - - You are banned indefinitely. - - - - - This server requires user registration. Do you want to register now? - - - - - This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. -Please close and reopen your client to try again. - - - - - An internal error has occurred, please close and reopen Cockatrice before trying again. -If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - - - - - Account activation - - - - - Your account has not been activated yet. -You need to provide the activation token received in the activation email. - - - - - Server Full - - - - - Unknown login error: %1 - - - - - - -This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - - Your username must respect these rules: - - - - - is %1 - %2 characters long - - - - - can %1 contain lowercase characters - - - - - - - - NOT - - - - - can %1 contain uppercase characters - - - - - can %1 contain numeric characters - - - - - can contain the following punctuation: %1 - - - - - first character can %1 be a punctuation mark - - - - - no unacceptable language as specified by these server rules: - note that the following lines will not be translated - - - - - can not contain any of the following words: %1 - - - - - can not match any of the following expressions: %1 - - - - - You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - - - - - Registration denied - - - - - Registration is currently disabled on this server - - - - - There is already an existing account with the same user name. - - - - - It's mandatory to specify a valid email address when registering. - - - - - It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - - - - - Password too short. - - - - - Registration failed for a technical problem on the server. - - - - - The connection to the server has been lost. - - - - - Unknown registration error: %1 - - - - - Account activation failed - - - - - Socket error: %1 - - - - - You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. -Local version is %1, remote version is %2. - - - - - Your Cockatrice client is obsolete. Please update your Cockatrice version. -Local version is %1, remote version is %2. - - - - - Connecting to %1... - - - - - Registering to %1 as %2... - - - - - Disconnected - - - - - Connected, logging in at %1 - - - - - - - Requesting forgotten password to %1 as %2... - - - - + &Connect... - + &Disconnect - + Start &local game... - + &Watch replay... - + &Full screen - + &Register to server... - + &Restore password... - + &Settings... - + &Exit - + A&ctions - + &Cockatrice - + C&ard Database - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + Reload card database - + Tabs - + &Help - + &About Cockatrice - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + + Re-run Onboarding Wizard... + + + + Show/Hide - + + Sign-in cancelled + + + + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + + Are you sure? + + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + + Yes, always enable + + + + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + Information - + A card database update is already running. - + Unable to run the card database updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - - This server supports additional features that your client doesn't have. -This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. - -To update your client, go to Help -> Check for Updates. - - - - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. + + + ManaBaseConfigDialog - - - - Reset Password + + Mana Base Configuration - - Your password has been reset successfully, you can now log in using the new credentials. + + Display type: - - Failed to reset user account password, please contact the server operator to reset your password. + + pie - - Activation request received, please check your email for an activation token. + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + + + OK + + + + + Cancel ManaBaseWidget - - + Mana Base + + ManaCurveConfigDialog + + + Group By: + + + + + type + + + + + color + + + + + subtype + + + + + power + + + + + toughness + + + + + Filters (optional): + + + + + Show main bar row + + + + + Show per-category rows + + + ManaCurveWidget - - + Mana Curve + + ManaDevotionConfigDialog + + + Display type: + + + + + pie + + + + + bar + + + + + combinedBar + + + + + Filter Colors (optional): + + + ManaDevotionWidget - - + Mana Devotion + + ManaDistributionConfigDialog + + + Top display type: + + + + + pie + + + + + bar + + + + + Colors: + + + + + Show per-color rows + + + + + ManaDistributionSingleDisplayWidget + + + %1 pips (%2 cards) + + + + + %1 mana (%2 cards) + + + + + ManaDistributionWidget + + + Mana Production + Devotion + + + + + Mana Distribution Settings + + + MessageLogWidget - + from play - + from their graveyard - + from exile - + from their hand - + the top card of %1's library - + the top card of their library - + from the top of %1's library - + from the top of their library - + the bottom card of %1's library - + the bottom card of their library - + from the bottom of %1's library - + from the bottom of their library - + from %1's library - + from their library - + from sideboard - + from the stack - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. - + %1 is not revealing the top card %2 any longer. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. - + %1 has conceded the game. - + %1 has unconceded the game. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. - + %1 has loaded a deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). - + %1 destroys %2. - + a card - + %1 gives %2 control over %3. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. - + + %1 puts %2%3 into their graveyard face down. + + + + %1 puts %2%3 into their graveyard. - + + %1 exiles %2%3 face down. + + + + %1 exiles %2%3. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. - + + %1 plays %2%3 face down. + + + + %1 plays %2%3. - + + %1 moves %2%3 to custom zone '%4' face down. + + + + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. - + The game has started. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. - + %1 is now watching the game. - + You have been kicked out of the game. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. - + %1 sets annotation of %2 to %3. - - %1 places %2 "%3" counter(s) on %4 (now %5). + + %1 places %2 %3%4 counter(s) on %5 (now %6). - - %1 removes %2 "%3" counter(s) from %4 (now %5). + + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + + %1 failed to undo their last draw. + + + + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. - + %1 untaps their permanents. - + %1 taps %2. - + %1 untaps %2. - + %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -5640,110 +8020,125 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message - - + + Message: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Ignore private messages sent by non-buddy users + + + + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -5751,37 +8146,42 @@ Cockatrice will now reload the card database. MoveMenu - + Move to - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + + T&able + + + + &Hand - + &Graveyard - + &Exile @@ -5794,47 +8194,47 @@ Cockatrice will now reload the card database. - + Mana Value - + Color(s) - + Loyalty - + Main Card Type - + Mana Cost - + P/T - + Side - + Layout - + Color Identity @@ -5857,65 +8257,197 @@ Cockatrice will now reload the card database. + + PaletteEditorDialog + + + + Reset + + + + + ▼ Edit Palette + + + + + + ▶ Edit Palette + + + + + Palette Editor — %1 + + + + + <b>Palette Editor</b> &nbsp;·&nbsp; %1 + + + + + + Save + + + + + Delete this scheme's custom palette and revert to the theme default (or the application palette) + + + + + Switch between the light and dark palette files + + + + + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + + ↺ Revert to theme default + + + + + Discard unsaved edits and restore the last saved palette + + + + + Cannot save: this theme has no writable directory + + + + + Write palette-%1.toml and reload the theme + + + + + + Save failed + + + + + + Could not write %1 to: +%2 + + + + + PaletteGridWidget + + + Active + + + + + Disabled + + + + + Inactive + + + + + Normal interactive state + + + + + Widget is disabled / not interactive + + + + + Window is in background / unfocused + + + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + + Phase - + Unknown Phase - + Untap - + Upkeep - + Draw - + First Main - + Beginning of Combat - + Declare Attackers - + Declare Blockers - + Combat Damage - + End of Combat - + Second Main - + End/Cleanup @@ -5923,253 +8455,552 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step - + Upkeep step - + Draw step - + First main phase - + Beginning of combat step - + Declare attackers step - + Declare blockers step - + Combat damage step - + End of combat step - + Second main phase - + End of turn step - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - PlayerActions - + + + + + grave + + + + + + + + exile + + + + + PlayerDialogs + + View top cards of library - - - - - - - - - - - + + + + + + + + + Number of cards: (max. %1) - + View bottom cards of library - + Shuffle top cards of library - + Shuffle bottom cards of library - + Draw hand - + 0 and lower are in comparison to current hand size - + Draw cards - - Move top cards to grave + + Move top cards to %1 - - Move top cards to exile + + Move bottom cards to %1 - - Move bottom cards to grave - - - - - Move bottom cards to exile - - - - + Draw bottom cards - - - C&reate another %1 token - - - - + Create tokens - - + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - - - Set counters - - PlayerMenu - + Player "%1" - + &Counters + + + PlaymatCollectionDialog - - S&ay + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + PrintingDisabledInfoWidget + + + The Printing Selector is disabled because you have currently enabled the setting to override all selected printings with personal set preferences. + +This setting means you'll only see the default printing for each card, instead of being able to select a printing, and will not see the printings other people have selected. + + + + + + + Enable printings again PrintingSelector - + Display Navigation Buttons + + + Printing Selector + + PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -6182,17 +9013,17 @@ Cockatrice will now reload the card database. PrintingSelectorCardSelectionWidget - + Previous Card in Deck - + Bulk Selection - + Next Card in Deck @@ -6200,124 +9031,140 @@ Cockatrice will now reload the card database. PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending + + PrintingSelectorPlaceholderWidget + + + Select a card to view its available printings + + + PtMenu - + Power / toughness - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -6325,224 +9172,401 @@ Cockatrice will now reload the card database. QObject - + Cockatrice card database (*.xml) - + All files (*.*) - + Cockatrice replays (*.cor) - + Maindeck - + Sideboard - + Tokens - + + Maybeboard + + + + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? + + + Enabling this feature will disable the use of the Printing Selector. + +You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. + +You will have to use the Set Manager, available through Card Database -> Manage Sets. + +Are you sure you would like to enable this feature? + + + + + Disabling this feature will enable the Printing Selector. + +You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. + +You can also use the Set Manager to adjust custom sort order for printings in the Printing Selector (other sort orders like alphabetical or release date are available). + +Are you sure you would like to disable this feature? + + + + + Confirm Change + + + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults + + QuickSetupPanel + + + %1% + + + + + <b>Quick Setup</b> + + + + + Generate all palette roles automatically from a single accent colour + + + + + Accent: + + + + + Primary hue. Used directly for highlights and links. +At high intensity it also tints buttons and backgrounds. + + + + + Intensity: + + + + + Subtle + + + + + Full colour + + + + + 0–30 Subtle tint — only highlights and links change hue +30–70 Accented — buttons, tooltips, and borders join in +70–100 Full colour — backgrounds, everything + + + + + 70% + + + RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name - + ID - + Upload time + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID - + Name - + Players - + Keep - + Time started - + Duration (sec) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu - + &Exile - + &View exile - + &Move exile to... - + &Top of library - + &Bottom of library - + &Hand - + &Graveyard @@ -6550,70 +9574,78 @@ Cockatrice will now reload the card database. RoomSelector - + Rooms - + Joi&n - + Room - + Description - + Permissions - + Players - + Games + + SayMenu + + + S&ay + + + SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - + Invalid key - + Shortcut already in use by: - + Clear - + Restore default @@ -6621,81 +9653,117 @@ Cockatrice will now reload the card database. SetsModel - + Enabled - + Set type - + Set code - + Long name - + Release date + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + + Shortcut editor + + + + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -6703,12 +9771,12 @@ Cockatrice will now reload the card database. ShortcutTreeView - + Action - + Shortcut @@ -6716,13 +9784,13 @@ Cockatrice will now reload the card database. ShortcutsSettings - + Your configuration file contained invalid shortcuts. Please check your shortcut settings! - + The following shortcuts have been set to default: @@ -6749,12 +9817,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -6762,27 +9830,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -6790,48 +9858,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -6839,45 +9907,204 @@ Please check your shortcut settings! StableReleaseChannel - + Default - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. + + StatsTally + + + Total Power + + + + + Total Toughness + + + + + StorageSettingsPage + + + + + Success + + + + + Cached card pictures have been reset. + + + + + Downloaded card pictures have been reset. + + + + + Error + + + + + One or more downloaded card pictures could not be cleared. + + + + + In-memory (currently loaded) card pictures have been reset. + + + + + Card Picture Loader Caching Method: + + + + + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. + + + + + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. + + + + + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. + + + + + Delete Cached Images + + + + + Delete Saved Images + + + + + Clear In-Memory Images + + + + + Card Picture Loader Cache Method + + + + + Network Cache + + + + + Filesystem + + + + + In-Memory Picture Cache + + + + + Network Cache Size: + + + + + On-disk cache for downloaded pictures + + + + + Redirect Cache TTL: + + + + + How long cached redirects for urls are valid for. + + + + + Picture Cache Size: + + + + + In-memory cache for pictures not currently on screen + + + + + Naming scheme: + + + + + Day(s) + + + TabAccount - + + + My Reports + + + + Add to Buddy List - + Add to Ignore List - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account @@ -6995,56 +10222,227 @@ Please check your shortcut settings! + + TabArchidekt + + + + + Desc. + + + + + + AND + + + + + + Require ALL selected colors + + + + + + Deck name... + + + + + + Owner... + + + + + + Packages + + + + + + Advanced Filters + + + + + Bracket: + + + + + + Any + + + + + + Contains card... + + + + + + Commander... + + + + + + Tag... + + + + + + Deck Size + + + + + Cards: + + + + + + Asc. + + + + + Sort by: + + + + + Filter by: + + + + + Display Settings + + + + + + Search + + + + + + Formats + + + + + Archidekt: + + + + + TabCardArtRules + + + Card: + + + + + ProviderId: + + + + + Mode: + + + + + Reason: + + + + + Type a card name... + + + + + Add rule + + + + + Remove rule + + + + + Refresh + + + + + Card Art Rules + + + TabDeckEditor - + Card Info - + Deck - + Filters - + &View - + + Card Database + + + + Printing - - - - + Visible - - - - + Floating - + Reset layout - + Deck: %1 @@ -7052,61 +10450,49 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - - Filters - - - - + &View - + Printing - - - - + Visible - - - - + Floating - + Reset layout @@ -7114,22 +10500,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7137,138 +10523,236 @@ Please check your shortcut settings! TabDeckStorage - + Local file system - + Server deck storage - - + + Open in deck editor - + Rename deck or folder - + Upload deck - + Download deck - - - - + + + + New folder - - + + Delete - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + + + + Error - + Rename failed - - + + + Invalid deck file - + Enter deck name - + This decklist does not have a name. Please enter a name: - + Unnamed deck - + Failed to upload deck to server - + Delete local file - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: - + Deck Storage @@ -7276,20 +10760,271 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -7302,32 +11037,32 @@ Please enter a name: TabEdhRecMain - + &Cards - + Top Commanders - + Tags - + Search for a card ... - + Search - + EDHRec: @@ -7335,197 +11070,207 @@ Please enter a name: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + + Invite + + + + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases - + &Game - + Next &phase - + Next phase with &action - + Next &turn - + Reverse turn order - + &Remove all local arrows - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede - - - + + + &Concede - + &Leave game - + C&lose replay - + &Focus Chat - + &Say: - + Selected cards - + &View - - - - + Visible - - - - + Floating - + Reset layout - + Concede - + Are you sure you want to concede this game? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game - + Are you sure you want to leave this game? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. @@ -7533,7 +11278,7 @@ Please enter a name: TabHome - + Home @@ -7546,152 +11291,152 @@ Please enter a name: - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: - + IP Address: - + Game Name: - + GameID: - + Message: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -7699,218 +11444,558 @@ The more information you put in, the more specific your results will be. TabMessage - + Private &chat - + &Leave - + %1 - Private chat - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from - + %1 has left the server. - + %1 has joined the server. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system - + Server replay storage - - + + Watch replay - + Rename - - + + New folder - - + + Delete - + Open replays folder - + Download replay - + Toggle expiration lock - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay @@ -7920,50 +12005,478 @@ The more information you put in, the more specific your results will be. + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - + &Say: - + Chat - + &Room - + &Leave room - + &Clear chat - + Chat Settings... - + mentioned you. - + Click to view - + You are flooding the chat. Please wait a couple of seconds. @@ -7971,35 +12484,35 @@ The more information you put in, the more specific your results will be. TabServer - + Server - - - - + + + + Error - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -8007,92 +12520,173 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - + + Archidekt + + + + Home - + &Visual Deck Storage - + Visual Database Display - + Server - + Account - + Deck Storage - + Game Replays - + Administration - + Logs - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? - + There are still open games. Are you sure you want to quit? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8100,38 +12694,38 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) @@ -8139,12 +12733,45 @@ Please refrain from engaging in this activity or further actions may be taken ag TabVisualDatabaseDisplay + + + Database Display + + Visual Database Display + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -8159,6 +12786,65 @@ Please refrain from engaging in this activity or further actions may be taken ag + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -8177,42 +12863,42 @@ Please refrain from engaging in this activity or further actions may be taken ag TranslateCounterName - + Life - + White - + Blue - + Black - + Red - + Green - + Colorless - + Other @@ -8220,214 +12906,327 @@ Please refrain from engaging in this activity or further actions may be taken ag UpdateDownloader - + Could not open the file for reading. + + UserCardArtSettingsDialog + + + Card Art Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Remove Banner Card + + + UserContextMenu - + User &details - + Private &chat - + Show this user's &games - - - Add to &buddy list - - - - - Remove from &buddy list - - - - - Add to &ignore list - - - - - Remove from &ignore list - - - Kick from &game + View this user's &public decks - Warn user + Add to &buddy list - View user's war&n history + Remove from &buddy list - Ban from &server + Add to &ignore list - View user's &ban history + Remove from &ignore list - &Promote user to moderator + Kick from &game - Dem&ote user from moderator + Report user - Promote user to &judge + Warn user - Demote user from judge + View user's war&n history + Ban from &server + + + + + View user's &ban history + + + + + &Promote user to moderator + + + + + Dem&ote user from moderator + + + + + Promote user to &judge + + + + + Demote user from judge + + + + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes - - - - Error - - - - - This user does not exist. - - - - - You are being ignored by %1 and can't see their games. + + Investigate user + + + Error + + + + + This user does not exist. + + + + + You are being ignored by %1 and can't see their games. + + + + Could not get %1's games. - + %1's games - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - + Failed to get admin notes. - - + + Success - + Successfully promoted user. - + Successfully demoted user. - - - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + + Kick Player + + + + + Are you sure you want to kick this player from the game? + + + + + + Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -8435,48 +13234,48 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInfoBox - + Location: - + Account Age: - + Edit - + Change password - + Change avatar - + Administrator - + Moderator - + Registered user - - + + Unregistered user @@ -8486,408 +13285,913 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Unknown - + The entered password does not match your account. - - - + + + + Information - + User information updated. - - - - - - - - - - + + + + + + + + + + + Error - + User Information - + Real Name: - + User Level: + + + Edit Banner Card + + + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + + The selected card is blacklisted on this server or another error occurred. + + + + + Banner card removed. + + + + + Banner card updated. + + + + This server does not permit you to update your user informations. - + Password changed. - + This server does not permit you to change your password. - + The new password is too short. - + The old password is incorrect. - + Avatar updated. - + This server does not permit you to update your avatar. - - An error occured while trying to updater your avatar. + + An error occured while trying to update your avatar. + + + + + UserInfoPopup + + + + Games + + + + + Chat + + + + + Open private chat + + + + + Profile + + + + + View user profile + + + + + Show this user's games + + + + + Invite + + + + + Invite to your game + + + + + − Buddy + + + + + Remove from buddy list + + + + + + Buddy + + + + + Add to buddy list + + + + + − Ignore + + + + + Remove from ignore list + + + + + + Ignore + + + + + Add to ignore list + + + + + Ban + + + + + Ban from server + + + + + Warn + + + + + Warn user + + + + + Ban log + + + + + View ban history + + + + + Warn log + + + + + View warning history + + + + + Notes + + + + + View admin notes + + + + + − Mod + + + + + Demote from moderator + + + + + + Mod + + + + + Promote to moderator + + + + + − Judge + + + + + Demote from judge + + + + + + Judge + + + + + Promote to judge + + + + + Join game + + + + + Spectate + + + + + + Loading games… + + + + + Could not load games. + + + + + No active games. UserInterfaceSettingsPage - + General interface settings - + &Double-click cards to play them (instead of single-click) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default - + + Do not delete &arrows inside of subphases + + + + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens - + + Show selection count during drag selection + + + + + Show total selection count + + + + + + Disabled + + + + + + Enabled + + + + + + Automatic + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + Use tear-off menus, allowing right click menus to persist on screen - + + Keep game chat focused when clicking in game (Note: disables card view search bar) + + + + Notifications settings - + Enable notifications in taskbar - + Notify in the taskbar for game events while you are spectating - + Notify in the taskbar when users in your buddy list connect - + Animation settings - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings - + Open deck in new tab by default - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - - do nothing + + Do nothing - - ask to convert to .cod + + Ask to convert to .cod - - always convert to .cod + + Always convert to .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings - + Buffer time for backwards skip via shortcut: + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + + Flip coin + + + + &Create token... - + C&reate another token - + Cr&eate predefined token + + + C&reate another %1 token + + VisualDatabaseDisplayColorFilterWidget - Mode: Exact Match + Exact match - - Mode: Includes + + Includes - - Mode: Include/Exclude + + Include / Exclude + Mode: Includes - - Filter mode (AND/OR/NOT conjunctions of filters) + + How selected and unselected colors are combined in the filter VisualDatabaseDisplayFilterSaveLoadWidget - + + Search filter... + + + + Save Filter - + Save all currently applied filters to a file - + Enter filename... + + VisualDatabaseDisplayFilterToolbarWidget + + + Sort by + + + + + Filter by + + + + + Save and load filters + + + + + Filter by exact card name + + + + + Filter by card main-type + + + + + Filter by card sub-type + + + + + Filter by set + + + + + Filter by format legality + + + + + Save/Load + + + + + Name + + + + + Main Type + + + + + Sub Type + + + + + Sets + + + + + Formats + + + + + VisualDatabaseDisplayFormatLegalityFilterWidget + + + Show formats with at least: + + + + + cards + + + + + Do not display formats with less than this amount of cards in the database + + + + + Filter mode (AND/OR/NOT conjunctions of filters) + + + + + Mode: Exact Match + + + + + Mode: Includes + + + VisualDatabaseDisplayMainTypeFilterWidget - + + Show main types with at least: + + + + + cards + + + + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes VisualDatabaseDisplayNameFilterWidget - - - Filter by name... - - - - - Load from Deck - - - Apply all card names in currently loaded deck as exact match name filters + Filter by name... (Exact match) - Load from Clipboard + Load from Deck + Apply all card names in currently loaded deck as exact match name filters + + + + + Load from Clipboard + + + + Apply all card names in clipboard as exact match name filters @@ -8895,7 +14199,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -8903,19 +14207,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -8923,27 +14227,37 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + + Show sub types with at least: + + + + + cards + + + + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -8951,50 +14265,93 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - + + + Visual + + + + Loading database ... - + Clear all filters - - Save and load filters + + Table + + + + + VisualDeckDisplayOptionsWidget + + + Group by: - - Filter by exact card name + + Change how cards are divided into categories/groups. - - Filter by card sub-type + + Sort by: - - Filter by set + + Click and drag to change the sort order within the groups + + + + + Configure how cards are sorted within their groups + + + + + + Toggle Layout: Overlap + + + + + Change how cards are displayed within zones (i.e. overlapped or fully visible.) + + + + + Toggle Layout: Flat + + + + + VisualDeckEditorPlaceholderWidget + + + Add cards using the search bar or database tab to have them appear here VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9002,46 +14359,25 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - - Click and drag to change the sort order within the groups + + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter - - - Configure how cards are sorted within their groups - - - - - - Overlap Layout - - - - - Change how cards are displayed within zones (i.e. overlapped or fully visible.) - - - - - Flat Layout - - VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -9049,47 +14385,57 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + + Show Color Identity + + + + Show Tags On Deck Previews - + + Show Upload Time + + + + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -9097,7 +14443,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -9105,22 +14451,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -9128,17 +14474,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + + Select decks to share + + + + Visual Deck Storage Settings @@ -9146,46 +14497,71 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Warn user for misconduct - - + + Error - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets @@ -9209,11 +14585,6 @@ Please refrain from engaging in this activity or further actions may be taken ag Move selected set to the bottom - - - Search by set name, code, or type - - Default order @@ -9225,98 +14596,103 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - + + Search by set name, code, type, or release date + + + + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -9324,93 +14700,85 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing - + pile view - - i18n - - - English - Eesti Keel (Estonian) - - main - + Connect on startup - + Debug to file @@ -9418,1005 +14786,1088 @@ Please refrain from engaging in this activity or further actions may be taken ag shortcutsTab - + Main Window - - + + Deck Editor - + Game Lobby - + Card Counters - + Player Counters - + Power and Toughness - + Game Phases - + Playing Area - + Move Selected Card - + View - + Move Top Card - + Move Bottom Card - + Gameplay - + Drawing - + Chat Room - + Game Window - + Load Deck from Clipboard - - + + Replays - + Tabs - + Check for Card Updates... - + Connect... - + Disconnect - + Exit - + Full screen - + Register... - + Settings... - + Start a Local Game... - + Watch Replay... - + Analyze Deck (deckstats.net) - + Analyze Deck (tappedout.net) - + Clear All Filters - + Clear Selected Filter - + Close - + Remove Card - + Manage Sets... - + Edit Custom Tokens... - + Export Deck (decklist.org) - + Export Deck (decklist.xyz) - + Add Card - + Load Deck... - - + + Load Deck from Clipboard... - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck - + Open Custom Pictures Folder - + Print Deck... - + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard - + Save Deck to Clipboard (No Set Info) - + Load Local Deck... - + Load Remote Deck... - + Set Ready to Start - + Toggle Sideboard Lock - + Add Green Counter - + Remove Green Counter - + Set Green Counters... - + Add Red Counter - + Remove Red Counter - + Set Red Counters... - + Add Life Counter - + Show Status Bar - + + Load deck from online service... + + + + + Load from website... + + + + Unload Deck - + Force Start - + Add Card Counter (F) - + Remove Card Counter (F) - + Set Card Counters (F)... - + Add Card Counter (E) - + Remove Card Counter (E) - + Set Card Counters (E)... - + Add Card Counter(D) - + Remove Card Counter (D) - + Set Card Counters (D)... - + Add Card Counter (C) - + Remove Card Counter (C) - + Set Card Counters (C)... - + Add Card Counter (B) - + Remove Card Counter (B) - + Set Card Counters (B)... - + Add Card Counter (A) - + Remove Card Counter (A) - + Set Card Counters (A)... - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Increment all card counters - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap - + Upkeep - + Draw - + First Main Phase - + Start Combat - + Attack - + Block - + Damage - + End Combat - + Second Main Phase - + End - + Next Phase - + Next Phase Action - + Next Turn - + Hide Card in Reveal Window - + Tap / Untap Card - + Untap All - - Toggle Untap + + Toggle Skip Untapping + Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + + Play Card, Face Down + + + + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - + + Reduce Life by Power + + + + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - - + + Reveal Selected Cards to All Players + + + + + Bottom of Library - - - - + + + + Exile - - - - + + + + Graveyard - - + + Report Queue + + + + + Moderation + + + + + + Hand - - + + Top of Library - - - + + Battlefield, Face Down - + Battlefield - - Sort Hand - - - - + Library - + Sideboard - + Top Cards of Library - + Bottom Cards of Library - + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + + Graveyard (Multiple), Face Down + + + + + Exile (Multiple) - + + + Exile (Multiple), Face Down + + + + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede - + Roll Dice... - + + Flip Coin + + + + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan - + + Mulligan (Same hand size) + + + + + Mulligan (Hand size - 1) + + + + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + + Sort Hand by Name + + + + + Sort Hand by Type + + + + + Sort Hand by Mana Value + + + + + Reveal Hand to All Players + + + + + Reveal Random Card to All Players + + + + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_fi.ts b/cockatrice/translations/cockatrice_fi.ts index c317d967a..f5167488e 100644 --- a/cockatrice/translations/cockatrice_fi.ts +++ b/cockatrice/translations/cockatrice_fi.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Lisää countteri... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Lisää countteri - + New value for counter '%1': Countterin uusi arvo '%1': @@ -36,64 +36,113 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. - + There are no cards in your deck to be exported + + AccountSetupPage + + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + AddAnalyticsPanelDialog @@ -105,12 +154,12 @@ Please check that the directory is writable and try again. AdminNotesDialog - + Update Notes - + Admin Notes for %1 @@ -136,218 +185,304 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error - + Could not create themes directory at '%1'. - + Theme settings Teeman asetukset - + Current theme: Nykyinen teema: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering Kortin renderöinti - + Display card names on cards having a picture Näytä kortin nimi korteissa, joissa on kuva - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Light - + Dark - + + System - + + Show all playmats + + + + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + Active theme palette: - + + Active theme style: + + + + + Qt widget style saved to this theme ("System" lets the application decide) + + + + Edit theme palette - + Home tab settings - + Display card name of background in bottom right - + + Dim the home tab background + + + + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + Styling settings - + Style user list - + Card printings - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Suurenna kortti kun hiiri on kortin päällä - + Use rounded card corners - + Card layout - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Käden layout - + Display hand horizontally (wastes space) Näytä käsikortit vierekkäin vaakatasossa (vie enemmän tilaa) - + Enable left justification Järjestä vasemmalta - + Table grid layout Pöydän grid-asetelma - + Invert vertical coordinate Käänteiset pystykoordinaatit - + Minimum player count for multi-column layout: Pelaajien vähimmäismäärä moniosaisessa layoutissa: - + Maximum font size for information displayed on cards: Suurin fonttikoko korteissa näkyville infoteksteille: @@ -355,12 +490,12 @@ Please check that the directory is writable and try again. ArchidektApiResponseDeckDisplayWidget - + Back to results - + Open Deck in Deck Editor @@ -386,112 +521,112 @@ Please check that the directory is writable and try again. BanDialog - + ban &user name Bännää &käyttäjä - + ban &IP address Bännää &IP osoite - + ban client I&D Bännää client I&D - + Ban type Bännäyksen tyyppi - + &permanent ban Bännätty pysyvästi - + &temporary ban Bännätty väliaikaisesti - + &Days: Päivät: - + &Hours: Tunnit: - + &Minutes: Minuutit: - + Duration of the ban Bännin kesto - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Kerro bännäyksen syy. Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. - + Please enter the reason for the ban that will be visible to the banned person. Kerro bännäyksen syy. Tämä näkyy bännätylle henkilölle. - + Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Peruuta - + Ban user from server Bännää pelaaja serveriltä - - - - + + + + Error Virhe - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Valitse nimi, IP -osoite, clientid tai joku näiden yhdistelmä asettaaksesi bännin. - + You must have a value in the name ban when selecting the name ban checkbox. Bännää nimi kentässä pitää olla arvo ennen valintaruudun valitsemista - + You must have a value in the ip ban when selecting the ip ban checkbox. Bännää ip kentässä pitää olla arvo ennen valintaruudun valitsemista. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Bännää ClientID kentässä pitää olla arvo ennen valintaruudun valitsemista. @@ -499,22 +634,22 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. BetaReleaseChannel - + Beta - + No reply received from the release update server. Ei vastausta päivitysserveriltä. - + Invalid reply received from the release update server. Päivitysserveri vastasi virheellisesti - + No reply received from the file update server. Ei vastausta tiedoston päivitysserveriltä. @@ -522,7 +657,20 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CardArtPreviewWidget - + + Banner preview + + + + + + + + Zoom %1× + + + + No card selected @@ -530,22 +678,22 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CardArtRulesModel - + Card - + ProviderId - + Mode - + Reason @@ -553,65 +701,209 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CardDatabaseModel - + Name Nimi - + Sets Setit - + Mana cost Mana cost - + Card type Korttityyppi - + P/T P/T - + Color(s) Väri(t) + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + CardDatabaseView - + Add to Deck - + Add to Sideboard - + Select Printing - + + Add to Zone + + + + + Create &new zone... + + + + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards @@ -721,22 +1013,22 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CardInfoFrameWidget - + Image - + Description - + Both - + View transformation @@ -744,22 +1036,22 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -767,27 +1059,27 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CardInfoTextWidget - + Name: - + Set: - + Collector Number: - + Related cards: - + Unknown card: @@ -795,129 +1087,129 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Reduce life by power - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -938,7 +1230,7 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. CardSizeWidget - + Card Size @@ -1078,6 +1370,24 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser @@ -1107,280 +1417,382 @@ Tallennetaan moderaattoreita varten. Bännätty henkilö ei näe tätä. + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + ConnectionController - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - - - - - - - - - - - + + + + + + + + + + - - + + + Error - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + Server timeout - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Information - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -1388,113 +1800,113 @@ To update your client, go to Help -> Check for Updates. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - + Requesting forgotten password to %1 as %2... - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. @@ -1544,7 +1956,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1560,17 +1972,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add card to &maindeck - + Add card to &sideboard @@ -1578,97 +1990,158 @@ To update your client, go to Help -> Check for Updates. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + Format: - + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1676,17 +2149,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1694,114 +2167,119 @@ To update your client, go to Help -> Check for Updates. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + + Share deck... + + + + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1809,7 +2287,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1817,110 +2295,152 @@ To update your client, go to Help -> Check for Updates. DeckEditorSettingsPage - - + + Update Spoilers Päivitä spoilerit - + Success Onnistui - + Download URLs have been reset. Latauksen URL:t on resetoitu - + Add URL Lisää URL - - + + URL: URL: - - + + Edit URL Muokkaa URL - + Add New URL - + Remove URL - + Updating... Päivitetään... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path Valitse polku - + URL Download Priority URL Latausprioriteetti - + Spoilers Spoilerit - + Download Spoilers Automatically Lataa spoilerit automaattisesti - + Spoiler Location: Spoilerien sijainti: - + Last Change Muokattu viimeksi - + Spoilers download automatically on launch Lataa spoilerit latauksen yhteydessä - + Press the button to manually update without relaunching Lataa spoilerit manuaalisesti ilman uudelleenkäynnistystä - + Do not close settings until manual update is complete Älä sulje asetuksia, ennen kuin manuaalinen päivitys on valmis - + Download card pictures on the fly Lataa korttikuvat kun niitä tarvitaan - + How to add a custom URL Miten lisään oman URL:n - + Reset Download URLs Resetoi latausten URL:t @@ -1928,32 +2448,32 @@ To update your client, go to Help -> Check for Updates. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1961,27 +2481,27 @@ To update your client, go to Help -> Check for Updates. DeckListModel - + Count - + Set - + Number Numero - + Provider ID - + Card Kortti @@ -2002,17 +2522,22 @@ To update your client, go to Help -> Check for Updates. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -2020,7 +2545,7 @@ To update your client, go to Help -> Check for Updates. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -2028,62 +2553,62 @@ To update your client, go to Help -> Check for Updates. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2091,94 +2616,109 @@ To update your client, go to Help -> Check for Updates. DeckPreviewWidget - + Banner Card - + Open in deck editor - + + Share deck... + + + + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Rename deck + + + + + Rename file + + + + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -2186,57 +2726,102 @@ To update your client, go to Help -> Check for Updates. DeckStateManager - + Rename deck to "%1" from "%2" - + Updated comments (was %1 chars, now %2 chars) - + Set banner card to %1 (%2) - + + Set playmat to %1 + + + + Tags changed - + Set format to %1 - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + %1 1 × "%2" (%3) - + Added - + Removed @@ -2258,94 +2843,122 @@ To update your client, go to Help -> Check for Updates. DeckViewContainer - + Load deck... Lataa pakka... - + Load remote deck... Lataa remote pakka... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start Ready to start - + Force start - + Sideboard unlocked Sideboard unlocked - + Sideboard locked Sideboard locked - - + + Error Virhe - + The selected file could not be loaded. Valittua tiedostoa ei voi avata. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? - + Remember and automatically apply choice in the future @@ -2358,143 +2971,143 @@ This will kick all non-ready players from the game. Ladataan... - + Known Hosts Tunnetut Isännät - + Delete the currently selected saved server - + Refresh the server list with known public servers Päivitä servereiden lista tunnetuilla julkisilla servereillä - + New Host Lisää Isäntä - + Name: Nimi: - + &Host: &Host: - + &Port: &Portti: - + Player &name: Pelaajan &nimi: - + P&assword: &Salasana: - + &Save password &Tallenna salasana - + A&uto connect Yhdistä &Automaattisesti - + Automatically connect to the most recent login when Cockatrice opens Kirjaudu sisään automaattisesti kun Cockatrice käynnistyy - + If you have any trouble connecting or registering then contact the server staff for help! Jos sinulla on ongelmia yhdistämisessä tai rekisteröinnissä ota yhteyttä serverin henkilökuntaan! - - + + Webpage Websivu - + Reset Password - + Forgot password? - + &Connect &Yhdistä - + Server Serveri - + Login Kirjaudu sisään - + Server Contact Serverin yhteystiedot - + Connect to Server Yhdistä serveriin - + Server URL Serverin URL - + Communication Port Tietoliikenneportti - + Unique Server Name Uniikki serverin nimi - + Connection Warning Yhteysvaroitus - + You need to name your new connection profile. Anna nimi uudelle yhteysprofiilillesi - + Connect Warning Yhdistämisvaroitus - + The player name can't be empty. Pelaajan nimi ei voi olla tyhjä. @@ -2502,122 +3115,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings &Muista asetukset - + &Description: &Kuvaus: - + P&layers: &Pelaajat: - + General Yleinen - + Game type Pelimuoto - + &Password: &Salasana: - + Only &buddies can join Vain &Kaverit voivat liittyä - + Only &registered users can join Vain &Rekisteröityneet käyttäjät voivat liittyä - + Joining restrictions Liittymisehdot - + &Spectators can watch &Salli katsojat - + Spectators &need a password to watch Katsojilta &vaaditaan salasana - + Spectators can &chat Katsojat voivat &keskustella - + Spectators can see &hands Katsojat &näkevät käsikortit - + Create game as spectator - + Spectators Katsojat - + Starting life total: - + Open decklists in lobby - + Create game as judge - + Game setup options - + &Clear &Tyhjennä - + Create game Luo peli - + Game information Tietoa pelistä - + Error Virhe - + Server error. Serverivirhe. @@ -2625,97 +3238,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Nimi: - + Token Tokeni - + C&olor: &Väri - + white valkoinen - + blue sininen - + black musta - + red punainen - + green vihreä - + multicolor monivärinen - + colorless väritön - + &P/T: P/T: - + &Annotation: &Kommentti: - + &Destroy token when it leaves the table &Tuhoa tokeni kun se lähtee pöydästä - + Create face-down (Only hides name) - + Token data Tokenin data - + Show &all tokens Näytä &kaikki tokenit - + Show tokens from this &deck Näytä kaikki tämän &pakan tokenit - + Choose token from list Valitse tokeni listasta - + Create token Luo token @@ -2723,53 +3336,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + ✖ - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2778,7 +3391,7 @@ This will kick all non-ready players from the game. DlgEditAvatar - + No image chosen. Ei valittua kuvaa. @@ -2790,27 +3403,32 @@ To remove your current avatar, confirm without choosing a new image. Poistaaksesi nykyisen avatarisi: Hyväksy valitsematta uutta kuvaa. - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... Selaa... - + Change avatar Vaihda avatar - + Open Image Avaa Kuva - + Image Files (*.png *.jpg *.bmp) Kuvatiedostot (*.png *.jpg *.bmp) - + Invalid image chosen. Kuva ei kelpaa. @@ -2836,38 +3454,38 @@ Poistaaksesi nykyisen avatarisi: Hyväksy valitsematta uutta kuvaa. DlgEditPassword - + Old password: Vanha salasana: - + New password: Uusi salasana: - + Confirm new password: Vahvista uusi salasana: - + Change password Vaihda salasana - - + + Error Virhe - + Your password is too short. - + The new passwords don't match. Uusi salasana ei täsmää @@ -2875,93 +3493,93 @@ Poistaaksesi nykyisen avatarisi: Hyväksy valitsematta uutta kuvaa. DlgEditTokens - + &Name: &Nimi: - + C&olor: &Väri - + white valkoinen - + blue sininen - + black musta - + red punainen - + green vihreä - + multicolor monivärinen - + colorless väritön - + &P/T: P/T: - + &Annotation: &Kommentti: - + Token data Tokenin data - - + + Add token Lisää token - + Remove token Poista token - + Edit custom tokens Muokkaa tokeneita - + Please enter the name of the token: Anna tokenin nimi: - + Error Virhe - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Nimi on ristiriidassa olemassaolevan kortin tai tokenin kanssa. @@ -3040,7 +3658,8 @@ Varmista että 'Token' setti on asetettu näkyväksi 'Muokkaa set - Hide games that have started + Hide started games + Hide games that have started @@ -3076,8 +3695,9 @@ Varmista että 'Token' setti on asetettu näkyväksi 'Muokkaa set - &Creator name: - &Tekijän nimi: + &Host name: + &Creator name: + @@ -3143,52 +3763,52 @@ Varmista että 'Token' setti on asetettu näkyväksi 'Muokkaa set DlgForgotPasswordChallenge - + Reset Password Challenge Warning - + A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. - + &Host: &Host: - + &Port: &Portti: - + Player &name: Pelaajan &nimi: - + Email: Email: - + Reset Password Challenge - + Reset Password Challenge Error - + The email address can't be empty. Email -osoite ei voi olla tyhjä @@ -3196,37 +3816,37 @@ Varmista että 'Token' setti on asetettu näkyväksi 'Muokkaa set DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. - + &Host: &Host: - + &Port: &Portti: - + Player &name: Pelaajan &nimi: - + Reset Password Request - + Reset Password Error - + The player name can't be empty. Pelaajan nimi ei voi olla tyhjä. @@ -3234,94 +3854,127 @@ Varmista että 'Token' setti on asetettu näkyväksi 'Muokkaa set DlgForgotPasswordReset - + Reset Password Warning - + A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. - + &Host: &Host: - + &Port: &Portti: - + Player &name: Pelaajan &nimi: - + Token: Tokeni - - + + New Password: Uusi Salasana: - + Reset Password - + The player name can't be empty. Pelaajan nimi ei voi olla tyhjä. - - - - + + + + Reset Password Error - + The token can't be empty. Tokeni ei voi olla tyhjä - + The new password can't be empty. Uusi salasana ei voi olla tyhjä - + Error - + Your password is too short. - + The passwords do not match. Salasanat eivät täsmää + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck @@ -3405,165 +4058,566 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLocalGameOptions - + Players: - + General - + Starting life total: - + Game setup options - + Remember settings - + Local game options + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: &Host: - + &Port: &Portti: - + Player &name: Pelaajan &nimi: - + P&assword: &Salasana: - + Password (again): &Salasana (uudelleen): - + Email: Email: - + Email (again): Email (uudelleen): - + Country: Maa: - + Undefined Ei määritelty - + Real name: Oikea nimi: - + Register to server Rekisteröidy serverille - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning Rekisteröintivaroitus - + Your password is too short. - + Your passwords do not match, please try again. Salasanat eivät täsmää, yritä uudelleen - + Your email addresses do not match, please try again. Sähköpostiosoitteet eivät täsmää, yritä uudelleen. - + The player name can't be empty. Pelaajan nimi ei voi olla tyhjä. + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice @@ -3629,46 +4683,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Tuntematon virhe ladattaessa korttitietokantaa - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - Korttitietokantasi on virheellinen - -Cockatrice ei välttämättä toimi kunnolla virheellisellä tietokannalla - -Voit joutua ajamaan Oraclen ja päivittämään korttitietokannan uudelleen - -Haluatko vaihtaa tietokantasi sijaintia? - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - Tietokantasi versio on liian vanha - -Tästä voi seurata ongelmia korttien tietoja tai kuvia ladattaessa - -Yleensä tämä voidaan korjata palaamalla oracleen päivittämään korttitietokanta. - -Haluatko vaihtaa tietokantasi sijaintia? - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3677,7 +4697,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3686,7 +4706,7 @@ Would you like to change your database location setting? Haluatko vaihtaa tietokantasi sijaintia? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3695,7 +4715,7 @@ Would you like to change your database location setting? Haluatko vaihtaa tietokantasi sijaintia? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3704,104 +4724,235 @@ Would you like to change your database location setting? - - - + + + Error Virhe - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Pakkojen tiedostosijainti on väärä. Haluatko palata takaisin ja asettaa oikean sijainnin pakat -kansiolle? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Kuvien tiedostosijainti on väärä. Haluatko palata takaisin ja asettaa oikean sijainnin kuvat -kansiolle? - + Settings Asetukset - + + Search settings... + + + + + OK + + + + General Yleinen - + Appearance Ulkonäkö - + User Interface Käyttöliittymä - + Storage - + Card Sources Korttien lähteet - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat Chat - + Sound Äänet - + Shortcuts Pikanäppäimet + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3809,17 +4960,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next Seuraava - + Previous Edellinen - + Tip of the Day Päivän vinkki @@ -3827,165 +4978,175 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel Tämänhetkinen julkaisukanava - + Reinstall Asenna uudelleen - + Cancel Download Peruuta Lataus - + Open Download Page Avaa Lataussivu - + Check for Client Updates - - - - + + + + Error Virhe - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatricessä ei ole SSL tukea, joten päivityksiä ei voi ladata automaattisesti! Vieraile lataussivulla päivittääksesi ohjelman manuaalisesti. - + Downloading update: %1 - + Checking for updates... Etsitään päivityksiä... - + Finished checking for updates Päivitysten haku on päättynyt - + No Update Available Ei uusia päivityksiä - + Cockatrice is up to date! Cockatrice on ajan tasalla! - + You are already running the latest version available in the chosen release channel. Sinulla on käytössä viimeisin versio, joka on saatavilla valitussa julkaisukanavassa. - + Current version Nykyinen versio - + Selected release channel Valitse julkaisukanava - - + + Update Available Päivitys Saatavilla - - + + A new version of Cockatrice is available! Uusi versio Cockatricesta on saatavilla! - - + + New version Uusi versio - - + + Released Julkaistu - - + + Changelog Muutosloki - + Do you want to update now? Haluatko päivittää nyt? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error Päivitysvirhe - + An error occurred while checking for updates: Virhe päivityksiä tarkistettaessa: - + An error occurred while downloading an update: Virhe tiedostoa ladatessa: - + Installing... Asennetaan... - + Cockatrice is unable to open the installer. Cockatrice ei voi avata asennustiedostoa. - + Try to update manually by closing Cockatrice and running the installer. Yritä asentaa manuaalisesti sulkemalla Cockatrice ja käynnistämällä asennusohjelma. - + Download location Latausten sijainti @@ -3993,17 +5154,17 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing Tyhjennä loki sammutuksen yhteydessä - + Copy to clipboard - + Debug Log Debug loki @@ -4241,7 +5402,7 @@ You may have to manually download the new version. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -4249,7 +5410,7 @@ You may have to manually download the new version. FilterBuilder - + Type your filter here Anna suodatin tähän @@ -4257,45 +5418,88 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -4303,140 +5507,156 @@ You may have to manually download the new version. GameSelector - - - - - - - - - + + + + + + + + + Error Virhe - + Please join the appropriate room first. Liity ensin oikeaan huoneeseen. - + Wrong password. Väärä salasana. - + Spectators are not allowed in this game. Tähän peliin ei päästetä katsojia. - + The game is already full. Peli on jo täynnä. - + The game does not exist any more. Peliä ei enää ole. - + This game is only open to registered users. Peli on tarkoitettu vain rekisteröityneille käyttäjille. - + This game is only open to its creator's buddies. Peli on tarkoitettu vain sen tekijän kavereille. - + You are being ignored by the creator of this game. Pelin luoja ei huomioi sinua. - + Join Game - + Spectate Game - + Game Information - + + Cop&y game link + + + + Join Game as Judge - + Spectate Game as Judge - + + Join game Liity peliin - - Password: - Salasana: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. Liity ensin asianmukaiseen huoneeseen. - + &Filter games &Suodata pelit - + C&lear filter &Poista suodattimet - + C&reate &Luo - + &Join &Liity - + Join as judge - + J&oin as spectator Liity &katsojana - + Join as judge spectator - + Games shown: %1 / %2 Games shown: %1 / %2 - + Games Pelit @@ -4477,105 +5697,105 @@ You may have to manually download the new version. GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password salasana - + buddies only vain kaverit - + reg. users only vain rekisteröityneet käyttäjät - + open decklists - - + + can chat voi jutella - + see hands katso käsikortit - + can see hands voi nähdä käsikortit - + not allowed ei sallittu - + Room Huone - + Age Ikä - + Description Kuvaus - - Creator - Luoja + + Host + - + Type Muoto - + Restrictions Ehdot - + Players Pelaajat - + Spectators Katsojat @@ -4583,158 +5803,275 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + English + Suomi (Finnish) + + + + + + + + + Choose path Valitse polku - + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> + + + + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + Language settings - + Language: Kieli: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + Version settings - + Card database - + Startup settings - + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) Polut (editoiminen poistettu käytöstä kannettavassa versiossa) - + Paths Polut - + How to help with translations - + Decks directory: Pakkojen kansio: - + Filters directory: - + Replays directory: Replay kansio: - + Pictures directory: Kuvakansio: - + Card database: Korttitietokanta: - + Custom database directory: - + Token database: Tokenitietokanta: - + Update channel Päivitykset - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client Ilmoita jos ohjelmastani puuttuu jokin serverin tarjoama ominaisuus - + Automatically run Oracle when running a new version of Cockatrice Suorita Oracle automaattisesti kun Cockatricen uusi versio on asennettu - + Show tips on startup Näytä vinkit käynnistyksen yhteydessä - + Last update check on %1 (%2 days ago) @@ -4742,47 +6079,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4876,59 +6213,353 @@ You may have to manually download the new version. + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu @@ -5146,273 +6777,294 @@ You may have to manually download the new version. MainWindow - - + + Player %1 Pelaaja %1 - + Load replay Lataa replay - + About Cockatrice Tieto sovelluksesta - + Version Versio - + Cockatrice Webpage Cockatricen Webbsivut - + Project Manager: Projektipäällikkö: - + Past Project Managers: Edelliset projektipäälliköt: - + Developers: Kehittäjät: - + Our Developers Kehittäjämme - + Help Develop! Auta kehittämään! - + Translators: Kääntäjät: - + Our Translators Kääntäjät - + Help Translate! Auta Kääntämään! - + Support: Tuki: - + Report an Issue Ilmoita puute - + Troubleshooting Vianhaku - + F.A.Q. F.A.Q. - - + + Error Virhe - + &Connect... &Yhdistä... - + &Disconnect &Katkaise yhteys - + Start &local game... Aloita &paikallinen peli... - + &Watch replay... &Katso replay... - + &Full screen K&oko näyttö - + &Register to server... &Rekisteröidy serverille... - + &Restore password... - + &Settings... &Asetukset - + &Exit &Exit - + A&ctions &Toiminnot - + &Cockatrice &Cockatrice - + C&ard Database &Korttitietokanta - + &Manage sets... &Manage sets... - + Edit custom &tokens... Edit custom &tokens... - + Open custom image folder Avaa mukautettu kuvakansio - + Open custom sets folder Avaa mukautettu setit -kansio - + Add custom sets/cards Lisää mukautettuja settejä/kortteja - + Reload card database - + Tabs - + &Help &Apua - + &About Cockatrice &Tietoa Cockatricesta - + &Tip of the Day &Päivän vinkki - + Check for Client Updates Etsi päivityksiä - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + + Re-run Onboarding Wizard... + + + + Show/Hide - + + Sign-in cancelled + + + + New Version Uusi versio - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed Cockatrice asennettu - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + + Are you sure? + + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database Korttitietokanta - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5421,51 +7073,51 @@ Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes Kyllä - - + + No Ei - + Open settings Avaa asetukset - + New sets found Löytyi uusia settejä! - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + Yes, always enable - + View sets Näytä setit - + Welcome Tervetuloa - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5474,89 +7126,89 @@ All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - + Information Tietoa - + A card database update is already running. Korttitietokannan päivitys on jo käynnissä - + Unable to run the card database updater: Unable to run the card database updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - - - - - + + + + + Load sets/cards Lataa setit/kortit - + Selected file cannot be found. Selected file cannot be found. - + You can only import XML databases at this time. You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. Sets/cards failed to import. @@ -5761,615 +7413,615 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play pelistä - + from their graveyard Graveyardilta - + from exile exilestä - + from their hand kädestään - + the top card of %1's library %1n pakan päällimmäinen kortti - + the top card of their library pakan päällimmäisen kortin - + from the top of %1's library %1n pakan päältä - + from the top of their library Pakkansa päältä - + the bottom card of %1's library %1n pakan alimmainen kortti - + the bottom card of their library pakan alimmaisen kortin - + from the bottom of %1's library %1n pakan pohjalta - + from the bottom of their library pakan alta - + from %1's library %1n pakasta - + from their library pakasta - + from sideboard sideboardilta - + from the stack stäkistä - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 pelaa pakan päällimmäinen kortti %2 esillä. - + %1 is not revealing the top card %2 any longer. %1 ei pelaa enää pakan päällimmäinen kortti %2 esillä, - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. %1 attaches %2 to %3's %4. - + %1 has conceded the game. %1 luovutti. - + %1 has unconceded the game. %1 has unconceded the game. - + %1 has restored connection to the game. %1 on jälleen yhteydessä peliin. - + %1 has lost connection to the game. %1 menetti yhteyden peliin. - + %1 points from their %2 to themselves. %1 osoittaa kortista %2 itseensä. - + %1 points from their %2 to %3. %1 osoittaa kortista %2 korttiin %3. - + %1 points from %2's %3 to themselves. %1 osoittaa %2n kortista %3 itseensä. - + %1 points from %2's %3 to %4. %1 osoittaa %2n kortista %3 korttiin %4 - + %1 points from their %2 to their %3. %1 osoittaa kortistaan %2 korttiin %3. - + %1 points from their %2 to %3's %4. %1 osoittaa kortistaan %2 pelaajan %3 korttiin %4. - + %1 points from %2's %3 to their own %4. %1 osoittaa pelaajan %2 kortista %3 korttiin %4. - + %1 points from %2's %3 to %4's %5. %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. %1 tekee tokenin: %2%3. - + %1 has loaded a deck (%2). %1 on ladannut pakan (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 on ladannut pakan, jossa on %2 sideboard korttia (%3) - + %1 destroys %2. %1 tuhoaa kortin %2. - + a card kortti - + %1 gives %2 control over %3. %1 antaa pelaajalle %2 kontrollin korttiin %3. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1 laittaa kortin %2 peliin%3. - + %1 puts %2%3 into their graveyard face down. - + %1 puts %2%3 into their graveyard. %1 laittaa kortin %2%3 graveyardille - + %1 exiles %2%3 face down. - + %1 exiles %2%3. %1 laittaa exileen kortin %2%3. - + %1 moves %2%3 to their hand. %1 laittaa kortin %2%3 käteen. - + %1 puts %2%3 into their library. %1 laittaa kortin %2%3 pakkaan. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. %1 laittaa kortin %2%3 pakan päälle. - + %1 puts %2%3 into their library %4 cards from the top. %1 laittaa kortin %2%3 pakkaansa %4 kortiksi päältä lukien. - + %1 moves %2%3 to sideboard. %1 siirtää kortin %2%3 sideboardille. - + %1 plays %2%3 face down. - + %1 plays %2%3. %1 pelaa kortin %2%3. - + %1 moves %2%3 to custom zone '%4' face down. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. %1 turns %2 face-down. - + %1 turns %2 face-up. %1 turns %2 face-up. - + The game has been closed. Peli on suljettu. - + The game has started. Peli alkaa. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 on liittynyt peliin. - + %1 is now watching the game. %1 seuraa peliä. - + You have been kicked out of the game. Sinut on kickattu pois pelistä. - + %1 has left the game (%2). %1 on lähtenyt pelistä. (%2). - + %1 is not watching the game any more (%2). %1 ei enää seuraa peliä (%2) - + %1 is not ready to start the game any more. %1 ei ole enää valmis aloittamaan peliä. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. %1 sekoittaa pakkansa ja nostaa uuden käden. - + You are watching a replay of game #%1. Katsot pelin #%1 replayta. - + %1 is ready to start the game. %1 on valmis aloittamaan pelin. - + cards an unknown amount of cards cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. %1 reveals %2 to %3. - + %1 reveals %2. %1 reveals %2. - + %1 randomly reveals %2%3 to %4. %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. %1 reveals %2%3 to %4. - + %1 reveals %2%3. %1 reveals %2%3. - + %1 reversed turn order, now it's %2. %1 käänsi vuorojärjestyksen; nyt se on %2. - + reversed käänteinen - + normal normaali - + Heads Kruuna - + Tails Klaava - + %1 flipped a coin. It landed as %2. %1 heitti kolikkoa ja tulokseksi tuli %2. - + %1 rolls a %2 with a %3-sided die. %1 heitti tuloksen %2. %3 -sivuisella nopalla - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. %1n vuoro. - + %1 sets annotation of %2 to %3. %1 sets annotation of %2 to %3. - + %1 places %2 %3%4 counter(s) on %5 (now %6). - + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + %1 failed to undo their last draw. - + %1 sets counter %2 to %3 (%4%5). %1 asettaa countterin %2 arvoon %3 (%4%5). - + %1 sets %2 to not untap normally. %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. %1 sets %2 to untap normally. - + %1 removes the PT of %2. %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. %1 on lukinnut sideboardinsa. - + %1 has unlocked their sideboard. %1 on avannut sideboardinsa. - + %1 taps their permanents. %1 täppää kaikki permanentit. - + %1 untaps their permanents. %1 untäppää kaikki permanentit. - + %1 taps %2. %1 täppää kortin %2. - + %1 untaps %2. %1 untäppää kortin %2. - + %1 shuffles %2. %1 sekoittaa %2. - + %1 shuffles the bottom %3 cards of %2. %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. %1 irrottaa kortin %2. - + %1 undoes their last draw. %1 peruuttaa viimeisimmän kortinnostonsa. - + %1 undoes their last draw (%2). %1 peruuttaa viimeisimmän kortinnostonsa. (%2). @@ -6377,115 +8029,125 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message Lisää viesti - - + + Message: Viesti: - + Edit message Edit message - + Chat settings Keskusteluasetukset - + Custom alert words Custom alert words - + Enable chat mentions Salli keskustelumaininnat - + Enable mention completer Enable mention completer - + In-game message macros Pelinsisäiset viestimakrot - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users Ignore private messages sent by unregistered users - + Ignore private messages sent by non-buddy users - - + + Invert text color Käänteinen tekstin väri - + Enable desktop notifications for private messages Enable desktop notifications for private messages - + Enable desktop notification for mentions Enable desktop notification for mentions - + Enable room message history on join Enable room message history on join - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) (Väri on heksadesimaaliluku) - + Separate words with a space, alphanumeric characters only Separate words with a space, alphanumeric characters only @@ -6493,42 +8155,42 @@ Cockatrice will now reload the card database. MoveMenu - + Move to - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + T&able - + &Hand - + &Graveyard - + &Exile @@ -6607,148 +8269,134 @@ Cockatrice will now reload the card database. PaletteEditorDialog - - + + Reset - - - Apply - - - - - - Save && Apply - - - - + ▼ Edit Palette - - + + ▶ Edit Palette - + Palette Editor — %1 - + <b>Palette Editor</b> &nbsp;·&nbsp; %1 - - This theme ships no default palette files - - - - - Replace current colours with the theme author's defaults - - - - - Switch between the light and dark palette files + + + Save - Editing: + Delete this scheme's custom palette and revert to the theme default (or the application palette) - Show or hide the per-role colour grid for manual tweaks + Switch between the light and dark palette files + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + ↺ Revert to theme default - + Discard unsaved edits and restore the last saved palette - - Preview this palette without saving to disk + + Cannot save: this theme has no writable directory - + Write palette-%1.toml and reload the theme - - Cannot save: this theme has no directory on disk - - - - + + Save failed - + + Could not write %1 to: %2 - - - No default found - - - - - No default palette file found for the "%1" scheme. - - PaletteGridWidget - + Active - + Disabled - + Inactive - + Normal interactive state - + Widget is disabled / not interactive - + Window is in background / unfocused + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + Phase @@ -6871,30 +8519,21 @@ Cockatrice will now reload the card database. End of turn step - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - PlayerActions - - - - + + + + grave - - - - + + + + exile @@ -7013,16 +8652,272 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters + + PlaymatCollectionDialog + + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + PrintingDisabledInfoWidget @@ -7043,12 +8938,12 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelector - + Display Navigation Buttons - + Printing Selector @@ -7056,25 +8951,65 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -7105,28 +9040,28 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -7142,95 +9077,103 @@ This setting means you'll only see the default printing for each card, instead o PtMenu - + Power / toughness - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services Services - + Hide %1 Hide %1 - + Hide Others Hide Others - + Show All Show All - + Preferences... Preferences... - + Quit %1 Quit %1 - + About %1 About %1 @@ -7238,17 +9181,17 @@ This setting means you'll only see the default printing for each card, instead o QObject - + Cockatrice card database (*.xml) Cockatrice card database (*.xml) - + All files (*.*) All files (*.*) - + Cockatrice replays (*.cor) Cockatrice replays (*.cor) @@ -7268,17 +9211,22 @@ This setting means you'll only see the default printing for each card, instead o Tokens - + + Maybeboard + + + + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7289,7 +9237,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7300,100 +9248,110 @@ Are you sure you would like to disable this feature? - + Confirm Change + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK OK - + Save Save - + Save All Save All - + Open Avaa - + &Yes &Yes - + Yes to &All Yes to &All - + &No &Ei - + N&o to All N&o to All - + Abort Abort - + Retry Retry - + Ignore Sivuuta - + Close Close - + Cancel Cancel - + Discard Discard - + Help Help - + Apply Apply - + Reset Nollaa - + Restore Defaults Restore Defaults @@ -7401,121 +9359,189 @@ Are you sure you would like to disable this feature? QuickSetupPanel - + %1% - + <b>Quick Setup</b> - + Generate all palette roles automatically from a single accent colour - + Accent: - + Primary hue. Used directly for highlights and links. At high intensity it also tints buttons and backgrounds. - + Intensity: - + Subtle - + Full colour - + 0–30 Subtle tint — only highlights and links change hue 30–70 Accented — buttons, tooltips, and borders join in 70–100 Full colour — backgrounds, everything - + 70% - - - Generate ↓ - - - - - Derive all palette roles from the accent colour above. -Fine-tune individual colours in the grid afterwards. - - RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name Nimi - + ID ID - + Upload time Upload time + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID ID - + Name Nimi - + Players Players - + Keep Keep - + Time started Time started - + Duration (sec) Duration (sec) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu @@ -7557,37 +9583,37 @@ Fine-tune individual colours in the grid afterwards. RoomSelector - + Rooms Rooms - + Joi&n Joi&n - + Room Room - + Description Description - + Permissions Permissions - + Players Players - + Games Games @@ -7595,7 +9621,7 @@ Fine-tune individual colours in the grid afterwards. SayMenu - + S&ay @@ -7603,32 +9629,32 @@ Fine-tune individual colours in the grid afterwards. SequenceEdit - + Choose an action from the table Choose an action from the table - + Hit the key/combination of keys you want to set for this action Hit the key/combination of keys you want to set for this action - + Invalid key Invalid key - + Shortcut already in use by: - + Clear Clear - + Restore default Restore default @@ -7661,56 +9687,92 @@ Fine-tune individual colours in the grid afterwards. Release date + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts Restore all default shortcuts - + Do you really want to restore all default shortcuts? Do you really want to restore all default shortcuts? - + Clear all default shortcuts Clear all default shortcuts - + Do you really want to clear all shortcuts? Do you really want to clear all shortcuts? - + Section: Section: - + Action: Action: - + Shortcut: Shortcut: - + + Shortcut editor + + + + How to set custom shortcuts How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -7766,12 +9828,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -7779,27 +9841,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Enable &sounds - + Current sounds theme: Current sounds theme: - + Test system sound engine Test system sound engine - + Sound settings Sound settings - + Master volume Master volume @@ -7807,48 +9869,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed Spoilers download failed - + No internet connection No internet connection - + Error Error - + Spoilers already up to date Spoilers already up to date - + No new spoilers added No new spoilers added - + Spoilers have been updated! Spoilers have been updated! - + Last change: Last change: @@ -7856,157 +9918,170 @@ Please check your shortcut settings! StableReleaseChannel - + Default - + No reply received from the release update server. No reply received from the release update server. - + Invalid reply received from the release update server. Invalid reply received from the release update server. - + No reply received from the tag update server. No reply received from the tag update server. - + Invalid reply received from the tag update server. Invalid reply received from the tag update server. + + StatsTally + + + Total Power + + + + + Total Toughness + + + StorageSettingsPage - - - + + + Success - + Cached card pictures have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + In-memory (currently loaded) card pictures have been reset. - + Card Picture Loader Caching Method: - + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. - + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. - + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. - + Delete Cached Images - + Delete Saved Images - + Clear In-Memory Images - + Card Picture Loader Cache Method - + Network Cache - + Filesystem - + In-Memory Picture Cache - + Network Cache Size: - + On-disk cache for downloaded pictures - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + In-memory cache for pictures not currently on screen - + Naming scheme: - + Day(s) @@ -8014,17 +10089,33 @@ Please check your shortcut settings! TabAccount - + + + My Reports + + + + Add to Buddy List - + Add to Ignore List - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account @@ -8145,118 +10236,118 @@ Please check your shortcut settings! TabArchidekt - - - + + + Desc. - - + + AND - - + + Require ALL selected colors - - + + Deck name... - - + + Owner... - - + + Packages - - + + Advanced Filters - + Bracket: - - + + Any - - + + Contains card... - - + + Commander... - - + + Tag... - - + + Deck Size - + Cards: - - + + Asc. - + Sort by: - + Filter by: - + Display Settings - - + + Search - - + + Formats @@ -8269,42 +10360,42 @@ Please check your shortcut settings! TabCardArtRules - + Card: - + ProviderId: - + Mode: - + Reason: - + Type a card name... - + Add rule - + Remove rule - + Refresh @@ -8317,52 +10408,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info Card Info - + Deck Deck - + Filters Filters - + &View &View - + Card Database - + Printing - + Visible Visible - + Floating Floating - + Reset layout Reset layout - + Deck: %1 Deck: %1 @@ -8370,49 +10461,49 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - + &View - + Printing - + Visible - + Floating - + Reset layout @@ -8420,22 +10511,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -8443,139 +10534,237 @@ Please check your shortcut settings! TabDeckStorage - + Local file system Local file system - + Server deck storage Server deck storage - - + + Open in deck editor Open in deck editor - + Rename deck or folder - + Upload deck Upload deck - + Download deck Download deck - - - - + + + + New folder New folder - - + + Delete Delete - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + + + + Error - + Rename failed - - + + + Invalid deck file - + Enter deck name Enter deck name - + This decklist does not have a name. Please enter a name: This decklist does not have a name. Please enter a name: - + Unnamed deck Unnamed deck - + Failed to upload deck to server - + Delete local file Delete local file - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: Name of new folder: - + Deck Storage @@ -8583,20 +10772,271 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -8609,27 +11049,27 @@ Please enter a name: TabEdhRecMain - + &Cards - + Top Commanders - + Tags - + Search for a card ... - + Search @@ -8642,191 +11082,207 @@ Please enter a name: TabGame - - - + + + Replay Replay - - + + Game Game - - + + Player List Player List - - + + + Invite + + + + + Card Info Card Info - - + + Messages Messages - - + + Replay Timeline Replay Timeline - + &Phases &Phases - + &Game &Game - + Next &phase Next &phase - + Next phase with &action Next phase with &action - + Next &turn Next &turn - + Reverse turn order Käännä vuorojärjestys - + &Remove all local arrows &Remove all local arrows - + Rotate View Cl&ockwise Rotate View Cl&ockwise - + Rotate View Co&unterclockwise Rotate View Co&unterclockwise - + Game &information Game &information - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede - - - + + + &Concede &Concede - + &Leave game &Leave game - + C&lose replay C&lose replay - + &Focus Chat &Focus Chat - + &Say: &Say: - + Selected cards - + &View &View - + Visible Visible - + Floating Floating - + Reset layout Reset layout - + Concede Concede - + Are you sure you want to concede this game? Are you sure you want to concede this game? - + Unconcede Unconcede - + You have already conceded. Do you want to return to this game? You have already conceded. Do you want to return to this game? - + Leave game Poistu pelistä - + Are you sure you want to leave this game? Haluatko varmasti poistua pelistä? - + A player has joined game #%1 Pelaaja on littynyt peliin #%1 - + %1 has joined the game - + You have been kicked out of the game. You have been kicked out of the game. @@ -8842,158 +11298,158 @@ Please enter a name: TabLog - + Logs Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs Room Logs - + Game Logs Game Logs - + Chat Logs Chat Logs - - + + Error Virhe - + You must select at least one filter. You must select at least one filter. - + You have to select a valid number of days to locate. You have to select a valid number of days to locate. - + Username: Käyttäjätunnus: - + IP Address: IP-osoite: - + Game Name: Pelin Nimi: - + GameID: Peli-ID: - + Message: Viesti: - + Main Room Main Room - + Game Room Game Room - + Private Chat Private Chat - + Past X Days: Past X Days: - + Today Tänään - + Last Hour Last Hour - + Maximum Results: Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs Get User Logs - + Clear Filters Clear Filters - + Filters Filters - + Log Locations Log Locations - + Date Range Date Range - + Maximum Results Maximum Results - - + + Message History Message History - + Failed to collect message history information. Failed to collect message history information. - + There are no messages for the selected filters. There are no messages for the selected filters. @@ -9001,218 +11457,558 @@ The more information you put in, the more specific your results will be. TabMessage - + Private &chat Private &chat - + &Leave &Leave - + %1 - Private chat %1 - Private chat - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from Private message from - + %1 has left the server. %1 has left the server. - + %1 has joined the server. %1 has joined the server. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system Local file system - + Server replay storage Server replay storage - - + + Watch replay Watch replay - + Rename - - + + New folder - - + + Delete Delete - + Open replays folder - + Download replay Download replay - + Toggle expiration lock Toggle expiration lock - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file Delete local file - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay Delete remote replay @@ -9222,65 +12018,478 @@ The more information you put in, the more specific your results will be. + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - - Friends - - - - - Online - - - - - Ignored - - - - + &Say: &Say: - + Chat Chat - + &Room &Room - + &Leave room &Leave room - + &Clear chat &Clear chat - + Chat Settings... Chat Settings... - + mentioned you. mentioned you. - + Click to view Click to view - + You are flooding the chat. Please wait a couple of seconds. You are flooding the chat. Please wait a couple of seconds. @@ -9288,35 +12497,35 @@ The more information you put in, the more specific your results will be. TabServer - + Server Server - - - - + + + + Error Virhe - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -9324,97 +12533,173 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - + Archidekt - + Home - + &Visual Deck Storage - + Visual Database Display - + Server - + Account - + Deck Storage - + Game Replays - + Administration - + Logs - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? Oletko varma? - + There are still open games. Are you sure you want to quit? There are still open games. Are you sure you want to quit? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view Click to view - + Your buddy %1 has signed on! Your buddy %1 has signed on! - + Unknown Event Tuntematon Tapahtuma - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -9425,39 +12710,39 @@ This message might mean there is a new version of Cockatrice available or this s To update your client, go to Help -> Check for Updates. - + Idle Timeout Idle Timeout - + You are about to be logged out due to inactivity. You are about to be logged out due to inactivity. - + Promotion Promotion - + You have been promoted. Please log out and back in for changes to take effect. You have been promoted. Please log out and back in for changes to take effect. - + Warned Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) You have received the following message from the server. @@ -9477,6 +12762,34 @@ Please refrain from engaging in this activity or further actions may be taken ag + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -9491,6 +12804,65 @@ Please refrain from engaging in this activity or further actions may be taken ag Unable to analyze the deck. + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -9562,57 +12934,52 @@ Please refrain from engaging in this activity or further actions may be taken ag UserCardArtSettingsDialog - + Card Art Settings - + Type a card name... - + Card name: - - Card ProviderId: + + Printing: - + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + Left margin (%): - + Right margin (%): - - Vertical offset: - - - - - Zoom: - - - - - Parameters - - - - - Preview - - - - + Remove Banner Card @@ -9620,216 +12987,266 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details User &details - + Private &chat Private &chat - + Show this user's &games Show this user's &games - + + View this user's &public decks + + + + Add to &buddy list Add to &buddy list - + Remove from &buddy list Remove from &buddy list - + Add to &ignore list Add to &ignore list - + Remove from &ignore list Remove from &ignore list - + Kick from &game Kick from &game - + + Report user + + + + Warn user Warn user - + View user's war&n history View user's war&n history - + Ban from &server Ban from &server - + View user's &ban history View user's &ban history - + &Promote user to moderator &Promote user to moderator - + Dem&ote user from moderator Dem&ote user from moderator - + Promote user to &judge - + Demote user from judge Demote user from judge - + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes - - + Investigate user + + + + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games %1's games - - - + + + Ban History Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. User has never been banned. - + Failed to collect ban information. Failed to collect ban information. - - - + + + Warning History Warning History - + Warning Time;Moderator;User Name;Reason Warning Time;Moderator;User Name;Reason - + User has never been warned. User has never been warned. - + Failed to collect warning information. Failed to collect warning information. - + Failed to get admin notes. - - + + Success Success - + Successfully promoted user. Successfully promoted user. - + Successfully demoted user. Successfully demoted user. - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + Kick Player - + Are you sure you want to kick this player from the game? - - - + + + Failed Failed - + Failed to promote user. Failed to promote user. - + Failed to demote user. Failed to demote user. - + Copy hash to clipboard Copy hash to clipboard - + Remove this user's messages @@ -9867,61 +13284,61 @@ Please refrain from engaging in this activity or further actions may be taken ag Administrator - + Moderator Moderator - + Registered user Registered user - - + + Unregistered user Unregistered user - + Judge Judge - + Unknown Unknown - + The entered password does not match your account. - - - - + + + + Information Information - + User information updated. User information updated. - - - - - - + + + + + - - - + + + + Error Virhe @@ -9945,283 +13362,292 @@ Please refrain from engaging in this activity or further actions may be taken ag Edit Banner Card + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + The selected card is blacklisted on this server or another error occurred. - + Banner card removed. - + Banner card updated. - + This server does not permit you to update your user informations. This server does not permit you to update your user informations. - + Password changed. Password changed. - + This server does not permit you to change your password. This server does not permit you to change your password. - + The new password is too short. The new password is too short. - + The old password is incorrect. The old password is incorrect. - + Avatar updated. Avatar updated. - + This server does not permit you to update your avatar. This server does not permit you to update your avatar. - + An error occured while trying to update your avatar. - - - This server does not permit you to update your user informations. - An error occured while trying to updater your avatar. - This server does not permit you to update your user informations. - UserInfoPopup - - + + Games - + Chat - + Open private chat - + Profile - + View user profile - + Show this user's games - + + Invite + + + + + Invite to your game + + + + − Buddy - + Remove from buddy list - + + Buddy - + Add to buddy list - + − Ignore - + Remove from ignore list - + + Ignore - + Add to ignore list - + Ban - + Ban from server - + Warn - + Warn user - + Ban log - + View ban history - + Warn log - + View warning history - + Notes - + View admin notes - + − Mod - + Demote from moderator - + + Mod - + Promote to moderator - + − Judge - + Demote from judge - + + Judge - + Promote to judge - + Join game - + Spectate - - + + Loading games… - + Could not load games. - + No active games. @@ -10229,223 +13655,344 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings General interface settings - + &Double-click cards to play them (instead of single-click) &Double-click cards to play them (instead of single-click) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default &Play all nonlands onto the stack (not the battlefield) by default - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens Annotate card text on tokens - + Show selection count during drag selection - + Show total selection count - - Show subtype breakdown in selection tally + + + Disabled - + + + Enabled + + + + + + Automatic + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + Use tear-off menus, allowing right click menus to persist on screen Use tear-off menus, allowing right click menus to persist on screen - + Keep game chat focused when clicking in game (Note: disables card view search bar) - + Notifications settings Ilmoitusasetukset - + Enable notifications in taskbar Ota tehtäväpalkin ilmoitukset käyttöön - + Notify in the taskbar for game events while you are spectating Notify in the taskbar for game events while you are spectating - + Notify in the taskbar when users in your buddy list connect Notify in the taskbar when users in your buddy list connect - + Animation settings Animaatioasetukset - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation &Tap/untap animation - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings - + Open deck in new tab by default - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - - do nothing + + Do nothing - - ask to convert to .cod + + Ask to convert to .cod - - always convert to .cod + + Always convert to .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings - + Buffer time for backwards skip via shortcut: + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + Flip coin - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -10482,22 +14029,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... - + Save Filter - + Save all currently applied filters to a file - + Enter filename... @@ -10505,72 +14052,72 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterToolbarWidget - + Sort by - + Filter by - + Save and load filters - + Filter by exact card name - + Filter by card main-type - + Filter by card sub-type - + Filter by set - + Filter by format legality - + Save/Load - + Name - + Main Type - + Sub Type - + Sets - + Formats @@ -10672,7 +14219,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -10680,19 +14227,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -10738,28 +14285,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - - + + Visual - + Loading database ... - + Clear all filters - + Table @@ -10767,43 +14314,43 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat @@ -10819,12 +14366,12 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -10832,17 +14379,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -10850,7 +14397,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -10858,52 +14405,57 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + Show Color Identity - + Show Tags On Deck Previews - + + Show Upload Time + + + + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -10911,7 +14463,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -10919,22 +14471,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -10942,17 +14494,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + + Select decks to share + + + + Visual Deck Storage Settings @@ -10960,177 +14517,202 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Cancel - + Warn user for misconduct Warn user for misconduct - - + + Error Virhe - + User name to send a warning to can not be blank, please specify a user to warn. User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. Warning to use can not be blank, please select a valid warning to send. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top Move selected set to the top - + Move selected set up Move selected set up - + Move selected set down Move selected set down - + Move selected set to the bottom Move selected set to the bottom - - Search by set name, code, or type - Search by set name, code, or type - - - + Default order Default order - + Restore original art priority order Restore original art priority order - + Enable all sets Enable all sets - + Disable all sets Disable all sets - + Enable selected set(s) Enable selected set(s) - + Disable selected set(s) Disable selected set(s) - + Deck Editor Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art Card Art - + + Search by set name, code, type, or release date + + + + How to use custom card art How to use custom card art - + Hints Hints - + Note Note - + Sorting by column allows you to find a set while not changing set priority. Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead Use the current sorting as the set priority instead - + Sorts the set priority using the same column Sorts the set priority using the same column - + Manage sets Manage sets @@ -11138,93 +14720,85 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing shuffle when closing - + pile view pile view - - i18n - - - English - Suomi (Finnish) - - main - + Connect on startup Connect on startup - + Debug to file Debug to file @@ -12008,6 +15582,16 @@ Please refrain from engaging in this activity or further actions may be taken ag Graveyard Graveyard + + + Report Queue + + + + + Moderation + + diff --git a/cockatrice/translations/cockatrice_fr.ts b/cockatrice/translations/cockatrice_fr.ts index c85b6fbdd..343cf16cc 100644 --- a/cockatrice/translations/cockatrice_fr.ts +++ b/cockatrice/translations/cockatrice_fr.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... Nombre de marqueur&s… @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Nombre de marqueurs - + New value for counter '%1': Nouvelle valeur des marqueurs « %1 » : @@ -36,66 +36,115 @@ AbstractTabDeckEditor - + Open in new tab Ouvrir dans un nouvel onglet - + Are you sure? Êtes-vous sûr ? - + The decklist has been modified. Do you want to save the changes? La liste de deck a été modifiée. Voulez-vous enregistrer les modifications ? - - - - - - + + + + + + Error Erreur - + Could not open deck at %1 N'a pas pu ouvrir le deck à %1 - + Could not save remote deck N'a pas pu sauvegarder le deck distant - - + + The deck could not be saved. Please check that the directory is writable and try again. Le deck n'a pas pu être enregistré. Vérifiez que le répertoire ne soit pas en lecture seule et réessayez. - + Save deck Sauvegarder le deck - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. Le deck n'a pas pu être enregistré. - + There are no cards in your deck to be exported Il n'y a pas de cartes dans le deck à exporter + + AccountSetupPage + + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + AddAnalyticsPanelDialog @@ -107,12 +156,12 @@ Vérifiez que le répertoire ne soit pas en lecture seule et réessayez. AdminNotesDialog - + Update Notes Notes de mises à jour - + Admin Notes for %1 Notes d'administrateur pour %1 @@ -138,218 +187,304 @@ Vérifiez que le répertoire ne soit pas en lecture seule et réessayez. AppearanceSettingsPage - + seconds secondes - + Error Erreur - + Could not create themes directory at '%1'. N'a pas pu créer le dossier Thèmes à '%1' - + Theme settings Paramètres du thème - + Current theme: Thème actuel : - + Open themes folder Ouvrir le dossier des thèmes - + Home tab background source: Source d'arrière-plan de l'onglet Accueil: - + Home tab background shuffle frequency: Fréquence de mélange de l'arrière-plan de l'onglet Accueil: - + Disabled Désactivé - + Menu settings Options du Menu - + Show keyboard shortcuts in right-click menus Montrer les raccourcis clavier dans les menus clic-droit - + Show game filter toolbar above list in room tab Afficher les barres d'outil et de recherche au-dessus de la liste des parties dans l'onglet salon. - + Card rendering Rendu des cartes - + Display card names on cards having a picture Afficher les noms des cartes ayant une image - + Auto-Rotate cards with sideways layout Rotation automatique des cartes avec disposition horizontale - + Override all card art with personal set preference (Pre-ProviderID change behavior) Remplacer toutes les illustrations de cartes par les préférences personnelles (comportement avant modification du ProviderID) - + Light - + Dark - + + System Système - + + Show all playmats + + + + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + Active theme palette: - + + Active theme style: + + + + + Qt widget style saved to this theme ("System" lets the application decide) + + + + Edit theme palette - + Home tab settings - + Display card name of background in bottom right Afficher le nom de la carte d'arrière-plan en bas à droite - + + Dim the home tab background + + + + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + Styling settings - + Style user list - + Card printings Images de la carte - + Bump sets that the deck contains cards from to the top in the printing selector Mettre en avant que ce deck contient des cartes depuis le sommet, dans le sélecteur d'impression - + Scale cards on mouse over Agrandir les cartes lors du survol du curseur - + Use rounded card corners Utiliser des bords de cartes arrondis - + Card layout Disposition de la carte - + Minimum overlap percentage of cards on the stack and in vertical hand Pourcentage minimum de chevauchement des cartes dans la pile et dans la main verticale - + Maximum initial height for card view window: Hauteur initiale maximale pour la fenêtre d'affichage des cartes : - - + + rows lignes - + Maximum expanded height for card view window: Hauteur étendue maximale pour la fenêtre d'affichage des cartes : - + Card counters Marqueurs sur la carte - + Counter %1 Marqueur %1 - + Hand layout Disposition de la main - + Display hand horizontally (wastes space) Afficher la main horizontalement (perte d'espace) - + Enable left justification Activer la justification à gauche - + Table grid layout Disposition en forme de grille - + Invert vertical coordinate Inverser la disposition du champ de bataille - + Minimum player count for multi-column layout: Nombre minimum de joueurs pour la disposition multi-colonnes : - + Maximum font size for information displayed on cards: Taille maximale de la police pour les informations affichées sur les cartes  : @@ -357,12 +492,12 @@ Vérifiez que le répertoire ne soit pas en lecture seule et réessayez. ArchidektApiResponseDeckDisplayWidget - + Back to results Retour aux résultats - + Open Deck in Deck Editor Ouvrir le deck dans l'éditeur de deck @@ -388,112 +523,112 @@ Vérifiez que le répertoire ne soit pas en lecture seule et réessayez. BanDialog - + ban &user name bannir &nom d'utilisateur - + ban &IP address bannir &adresse IP - + ban client I&D bannir &ID du client - + Ban type Type du bannissement - + &permanent ban &bannissement permanent - + &temporary ban bannissement &temporaire - + &Days: &Jours : - + &Hours: &Heures : - + &Minutes: &Minutes : - + Duration of the ban Durée du bannissement - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Veuillez expliquer la raison du bannissement. Cette information sera consultable uniquement par les modérateurs et ne sera pas visible par la personne bannie. - + Please enter the reason for the ban that will be visible to the banned person. Veuillez expliquer la raison du bannissement. Cette information sera visible par la personne bannie. - + Redact all messages from this user in all rooms Censurer tous les messages de cet utilisateur dans toutes les salles - + &OK &OK - + &Cancel &Annuler - + Ban user from server Bannir le joueur du serveur - - - - + + + + Error Erreur - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Vous devez choisir un bannissement à partir du nom, de l'adresse IP, de l'ID du client, ou une combinaison des trois. - + You must have a value in the name ban when selecting the name ban checkbox. Vous devez remplir le champ du nom du banni en le choisissant dans la liste. - + You must have a value in the ip ban when selecting the ip ban checkbox. Vous devez saisir l'adresse IP du banni en le choisissant dans la liste. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Vous devez saisir l'ID du client banni en le choisissant dans la liste. @@ -501,22 +636,22 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa BetaReleaseChannel - + Beta Beta - + No reply received from the release update server. Le serveur de mise à jour ne répond pas. - + Invalid reply received from the release update server. Réponse reçue du serveur de mise à jour de version invalide . - + No reply received from the file update server. Le serveur de mise à jour de fichier ne répond pas. @@ -524,7 +659,20 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CardArtPreviewWidget - + + Banner preview + + + + + + + + Zoom %1× + + + + No card selected Aucune carte sélectionné @@ -532,22 +680,22 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CardArtRulesModel - + Card Carte - + ProviderId - + Mode Mode - + Reason Raison @@ -555,65 +703,209 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CardDatabaseModel - + Name Nom - + Sets Éditions - + Mana cost Coût de mana - + Card type Type de carte - + P/T F/E - + Color(s) Couleur(s) + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + CardDatabaseView - + Add to Deck Ajouter au deck - + Add to Sideboard - + Select Printing Choisir l'image - + + Add to Zone + + + + + Create &new zone... + + + + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards @@ -723,22 +1015,22 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CardInfoFrameWidget - + Image Image - + Description Description - + Both Les deux - + View transformation Voir la transformation @@ -746,22 +1038,22 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CardInfoPictureWidget - + View related cards Voir les cartes associées - + Add card to deck Ajouter la carte au deck - + Mainboard Deck principal - + Sideboard Réserve @@ -769,27 +1061,27 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CardInfoTextWidget - + Name: Nom: - + Set: Édition : - + Collector Number: - + Related cards: Cartes associées : - + Unknown card: Carte inconnue : @@ -797,129 +1089,129 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CardMenu - + Re&veal to... Révéler à... - + &All players &Tous les joueurs - + View related cards Voir les cartes associées - + Token: Jeton : - + All tokens Tout les jetons - + &Select All &Sélectionner Tout - + S&elect Row Sél&ectionne une ligne - + S&elect Column Sél&ectionne une colonne - + &Play &Jouer - + &Hide &Cacher - + Play &Face Down Jouer &Face Cachée - + &Tap / Untap Turn sideways or back again &Engager / Dégager - + Skip &untapping Ne pas dégager - + T&urn Over Turn face up/face down Reto&urner - + &Peek at card face &Regarder furtivement la carte face cachée - + &Clone &Cloner - + Attac&h to card... Attac&her à une carte... - + Unattac&h Détac&her - + &Draw arrow... &Tracer une flèche... - + &Set annotation... &Annoter... - + Reduce life by power - + Ca&rd counters Ma&rqueurs sur la carte - + &Add counter (%1) &Ajouter un marqueur (%1) - + &Remove counter (%1) &Retirer un marqueur (%1) - + &Set counters (%1)... &Changer le nombre de marqueurs (%1)... @@ -940,7 +1232,7 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa CardSizeWidget - + Card Size Taille de la Carte @@ -1080,6 +1372,24 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa La zone personnalisée '%2' de %1 + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser @@ -1109,280 +1419,382 @@ Cette information sera consultable uniquement par les modérateurs et ne sera pa Choisir la couleur + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + ConnectionController - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator Banni par le modérateur - + Expected end time: %1 Heure de fin estimé: %1 - + This ban lasts indefinitely. Ce bannissement dure indéfiniment. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - - - - - - - - - - - + + + + + + + + + + - - + + + Error - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + Server timeout - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Information Information - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -1390,113 +1802,113 @@ To update your client, go to Help -> Check for Updates. - - - + + + Reset Password Réinitialiser le mot de passe - + Your password has been reset successfully, you can now log in using the new credentials. Votre mot de passe à été réinitialisé avec succes, vous pouvez maintenant vous connecter avec le nouveau mot de passe. - + Failed to reset user account password, please contact the server operator to reset your password. Échec de la réinitialisation du mot de passe. Veuillez contacter l'administrateur du serveur pour réinitialiser votre mot de passe. - + Activation request received, please check your email for an activation token. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - + Requesting forgotten password to %1 as %2... - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. @@ -1546,7 +1958,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorCardDatabaseDockWidget - + Card Database Base de données de cartes @@ -1562,17 +1974,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) Rechercher par nom de carte (ou expression de recherche) - + Add card to &maindeck Ajouter la carte au &deck - + Add card to &sideboard Ajouter carte à la ré&serve @@ -1580,97 +1992,158 @@ To update your client, go to Help -> Check for Updates. DeckEditorDeckDockWidget - + Loading Database... Chargement de la base de données... - + Banner Card Carte bannière - + Main Type Type principal - + Mana Cost Coût de mana - + Colors Couleurs - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing Choisir l'impression - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck Deck - + Deck &name: &Nom du deck : - + Banner Card/Tags Visibility Settings Paramètres de visibilité de la carte bannière et des étiquettes - + Show banner card selection menu Montrer le menu de sélection de carte bannière - + Show tags selection menu Montrer le menu de sélection d'étiquettes - + &Comments: &Commentaires : - + Group by: Grouper par : - + Format: Format : - + Hash: Empreinte : - + &Increment number Augmenter quant&ité - + &Decrement number &Diminuer quantité - + &Remove row &Retirer la ligne - + Swap card to/from sideboard Échanger la carte vers/depuis la réserve @@ -1678,17 +2151,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorFilterDockWidget - + Filters Filtres - + &Clear all filters &Effacer tous les filtres - + Delete selected Enlever la sélection @@ -1696,114 +2169,119 @@ To update your client, go to Help -> Check for Updates. DeckEditorMenu - + &Deck Editor &Éditeur de deck - + &New deck &Nouveau deck - + &Load deck... &Charger un deck... - + Load recent deck... Charger un deck récent... - + Clear Effacer - + &Save deck &Sauvegarder le deck - + Save deck &as... S&auvegarder le deck sous... - + + Share deck... + + + + Load deck from cl&ipboard... Charger un deck depuis le presse-pap&ier... - + Edit deck in clipboard Éditer le deck dans le presse-papier - - + + Annotated Avec annotations - - + + Not Annotated Sans annotation - + Save deck to clipboard Copier le deck dans le presse-papier - + Annotated (No set info) Annoté (pas d'info sur l'extension) - + Not Annotated (No set info) Pas annoté (pas d'info sur l'extension) - + &Print deck... Im&primer le deck... - + Load deck from online service... Charger un deck depuis un service en ligne... - + &Send deck to online service Envoyer le deck au service en &ligne - + Create decklist (decklist.org) Créer une liste de deck (decklist.org) - + Create decklist (decklist.xyz) Créer une liste de deck (decklist.xyz) - + Analyze deck (deckstats.net) &Analyser le deck sur deckstats.net - + Analyze deck (tappedout.net) Analyser le deck (tappedout.net) - + &Close &Fermer @@ -1811,7 +2289,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorPrintingSelectorDockWidget - + Printing Selector Sélection d'impression @@ -1819,110 +2297,152 @@ To update your client, go to Help -> Check for Updates. DeckEditorSettingsPage - - + + Update Spoilers Mettre à jour les spoilers - + Success Réussite - + Download URLs have been reset. Les URLs de téléchargement ont été réinitialisées. - + Add URL Ajouter une URL - - + + URL: URL : - - + + Edit URL Modifier l'URL - + Add New URL Ajouter Nouveau Lien - + Remove URL Effacer Lien - + Updating... Mise à jour... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path Choisir le chemin - + URL Download Priority URL de téléchargement prioritaire - + Spoilers Spoilers - + Download Spoilers Automatically Télécharger automatiquement les spoilers - + Spoiler Location: Emplacement des spoilers : - + Last Change Dernier changement - + Spoilers download automatically on launch Télécharger automatiquement les spoilers au lancement - + Press the button to manually update without relaunching Appuyez sur le bouton pour mettre à jour manuellement sans redémarrer. - + Do not close settings until manual update is complete Ne pas fermer la fenêtre des paramètres avant la mise à jour manuelle complète. - + Download card pictures on the fly Télécharger les images des cartes à la volée - + How to add a custom URL Comment ajouter une URL personnalisée - + Reset Download URLs Réinitialiser les URL de téléchargement @@ -1930,32 +2450,32 @@ To update your client, go to Help -> Check for Updates. DeckListHistoryManagerWidget - + Undo Défaire - + Redo Refaire - + Undo/Redo history Historique - + Click on an entry to revert to that point in the history. Clickez sur une ligne pour retourner à ce point dans l'historique. - + [redo] [refaire] - + [undo] [défaire] @@ -1963,27 +2483,27 @@ To update your client, go to Help -> Check for Updates. DeckListModel - + Count Compter - + Set Édition - + Number Nombre - + Provider ID Provider ID - + Card Carte @@ -2004,17 +2524,22 @@ To update your client, go to Help -> Check for Updates. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match Mode : correspondance exacte - + Mode: Includes Mode : inclure - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) Mode de filtre par identité de couleur (conjonctions AND/OR/NOT de filtres) @@ -2022,7 +2547,7 @@ To update your client, go to Help -> Check for Updates. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... Éditer les étiquettes... @@ -2030,62 +2555,62 @@ To update your client, go to Help -> Check for Updates. DeckPreviewTagDialog - + Deck Tags Manager Gestionnaire d'étiquettes de decks - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: Gérer vos étiquettes de decks. Ajouter ou retirer les étiquettes comme nécessaire, ou en créer de nouveaux : - + Add a new tag (e.g., Aggro️) Ajouter une nouvelle étiquette (e.g., Elfes) - + Add Tag Ajouter Étiquette - + Filter tags... Filtrer les étiquettes... - + OK OK - + Edit default tags Éditer les étiquettes par défaut - + Cancel Annuler - + Invalid Input Entrée invalide - + Tag name cannot be empty! Le nom de l'étiquette ne peut pas être vide ! - + Duplicate Tag Étiquette dupliquée - + This tag already exists. Cette étiquette existe déjà. @@ -2093,94 +2618,109 @@ To update your client, go to Help -> Check for Updates. DeckPreviewWidget - + Banner Card Carte bannière - + Open in deck editor Ouvrir dans l'éditeur de deck - + + Share deck... + + + + Edit Tags Éditer les étiquettes - + Rename Deck Renommer le deck - + Save Deck to Clipboard Copier le deck dans le presse-papier - + Annotated Avec annotations - + Annotated (No set info) Avec annotations (pas d'info sur l'extension) - + Not Annotated Sans annotation - + Not Annotated (No set info) Sans annotation (pas d'info sur l'extension) - + Rename File Renommer le fichier - + Delete File Supprimer le fichier - + Set Banner Card Choisir la carte bannière - - + + New name: Nouveau nom : - - + + Rename deck + + + + + Rename file + + + + + Error Erreur - + Rename failed Renommage échoué - + Delete file Supprimer le fichier - + Are you sure you want to delete the selected file? Êtes-vous certain de vouloir supprimer le fichier sélectionné ? - + Delete failed Suppression échouée @@ -2188,57 +2728,102 @@ To update your client, go to Help -> Check for Updates. DeckStateManager - + Rename deck to "%1" from "%2" Renommer le deck de "%2" à "%1" - + Updated comments (was %1 chars, now %2 chars) Mis à jour les commentaires (étaient %1 caractères, maintenant %2) - + Set banner card to %1 (%2) Définir %1 (%2) comme carte de couverture - + + Set playmat to %1 + + + + Tags changed Tags modifiés - + Set format to %1 Changer le format à %1 - + Added (%1): %2 (%3) %4 Ajouté (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) Bougé %1 1 × "%2" (%3) - + Removed "%1" (all copies) Retiré "%1" (toutes les copies) - + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + %1 1 × "%2" (%3) %1 1 × "%2" (%3) - + Added Ajoutée - + Removed Retirée @@ -2260,88 +2845,116 @@ To update your client, go to Help -> Check for Updates. DeckViewContainer - + Load deck... Charger un deck... - + Load remote deck... Charger un deck distant... - + Load from clipboard... Charger depuis le presse-papier... - + Load from website... Charger depuis un site web... - + Unload deck Annuler le chargement du deck - + Ready to start Prêt à démarrer - + Force start Forcer le démarrage - + Sideboard unlocked Réserve déverrouillée - + Sideboard locked Réserve verrouillée - - + + Error Erreur - + The selected file could not be loaded. Le fichier sélectionné n'a pas pu être chargé. - + Deck is greater than maximum file size. Le deck est plus grand que la taille maximum de fichier. - + Are you sure you want to force start? This will kick all non-ready players from the game. Êtes-vous sûr de vouloir forcer le démarrage ? Cela va éjecter de la partie tous les joueurs non prêts à démarrer. - + Cockatrice Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion Conversion de format de deck - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? @@ -2350,7 +2963,7 @@ Les étiquettes ne peuvent être ajoutées qu'aux decks au format .cod. Voulez-vous convertir le deck au format .cod ? - + Remember and automatically apply choice in the future Mémoriser et appliquer automatiquement le choix à l'avenir @@ -2363,143 +2976,143 @@ Voulez-vous convertir le deck au format .cod ? Téléchargement - + Known Hosts Hôtes connus - + Delete the currently selected saved server Supprimer le serveur enregistré actuellement sélectionné - + Refresh the server list with known public servers Actualiser la liste de serveurs avec des serveurs publics connus - + New Host Nouvel hôte - + Name: Nom  : - + &Host: &Hôte : - + &Port: &Port : - + Player &name: &Nom du joueur : - + P&assword: Mot de p&asse : - + &Save password &Se souvenir du mot de passe - + A&uto connect Connexion a&utomatique - + Automatically connect to the most recent login when Cockatrice opens Se connecter automatiquement avec les identifiants les plus récents à l'ouverture de Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! Si vous rencontrez des difficultés pour vous connecter ou vous inscrire, contactez le personnel du serveur pour obtenir de l'aide ! - - + + Webpage Page web - + Reset Password Réinitialiser le mot de masse - + Forgot password? Mot de passe oublié ? - + &Connect &Connecter - + Server Serveur - + Login Identifiants - + Server Contact Contact du serveur - + Connect to Server Se connecter au serveur - + Server URL URL du serveur - + Communication Port Port de communication - + Unique Server Name Nom de serveur unique - + Connection Warning Avertissement de connexion - + You need to name your new connection profile. Vous devez attribuer un nom à votre nouveau profil de connexion. - + Connect Warning Avertissement de connexion - + The player name can't be empty. Le nom du joueur ne peut être vide. @@ -2507,122 +3120,122 @@ Voulez-vous convertir le deck au format .cod ? DlgCreateGame - + Re&member settings Conserver les para&mètres - + &Description: &Description : - + P&layers: &Joueurs : - + General Général - + Game type Type de partie - + &Password: Mot de &passe : - + Only &buddies can join Seuls les &amis peuvent rejoindre - + Only &registered users can join Seuls les joueurs en&registrés peuvent rejoindre - + Joining restrictions Conditions pour rejoindre - + &Spectators can watch Les &spectateurs peuvent observer - + Spectators &need a password to watch Les spectateurs ont besoin d'un &mot de passe pour observer - + Spectators can &chat Les spectateurs peuvent dis&cuter - + Spectators can see &hands Les spectateurs peuvent voir les &mains des joueurs - + Create game as spectator Créer la partie en tant que spectateur - + Spectators Spectateurs - + Starting life total: Total initial de point de vie - + Open decklists in lobby Ouvrir les listes de deck dans le lobby - + Create game as judge Créer une partie en tant que juge - + Game setup options Options de configuration de la partie - + &Clear Effa&cer - + Create game Créer une partie - + Game information Informations sur la partie - + Error Erreur - + Server error. Erreur serveur. @@ -2630,97 +3243,97 @@ Voulez-vous convertir le deck au format .cod ? DlgCreateToken - + &Name: &Nom : - + Token Jeton - + C&olor: C&ouleur : - + white blanc - + blue bleu - + black noir - + red rouge - + green vert - + multicolor multicolore - + colorless incolore - + &P/T: &F/E : - + &Annotation: &Annotation : - + &Destroy token when it leaves the table &Detruire le jeton lorsqu'il quitte le champ de bataille - + Create face-down (Only hides name) Créer face-cachée (cache seulement le nom) - + Token data Données du jeton - + Show &all tokens Afficher &tous les jetons - + Show tokens from this &deck Afficher les jetons présents dans ce &deck - + Choose token from list Choisir un jeton dans la liste - + Create token Créer un jeton @@ -2728,53 +3341,53 @@ Voulez-vous convertir le deck au format .cod ? DlgDefaultTagsEditor - + Edit Tags Éditer les étiquettes - + Add Ajouter - + Confirm Confirmer - + Cancel Annuler - + Enter a tag and press Enter Entrer une étiquette et valider avec Entrée - - + + ✖ ✖ - + Invalid Input Entrée invalide - + Tag name cannot be empty! Le nom de l'étiquette ne peut pas être vide ! - + Duplicate Tag Étiquette dupliquée - + This tag already exists. Cette étiquette existe déjà. @@ -2783,7 +3396,7 @@ Voulez-vous convertir le deck au format .cod ? DlgEditAvatar - + No image chosen. Aucune image choisie. @@ -2795,27 +3408,32 @@ To remove your current avatar, confirm without choosing a new image. Pour enlever votre avatar actuel, confirmez sans choisir une nouvelle image. - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... Parcourir... - + Change avatar Changer d'avatar - + Open Image Ouvrir l'image - + Image Files (*.png *.jpg *.bmp) Fichiers image (*.png *.jpg *.bmp) - + Invalid image chosen. L'image choisie est invalide. @@ -2841,38 +3459,38 @@ Pour enlever votre avatar actuel, confirmez sans choisir une nouvelle image. DlgEditPassword - + Old password: Ancien mot de passe : - + New password: Nouveau mot de passe : - + Confirm new password: Confirmer le nouveau mot de passe : - + Change password Changer le mot de passe - - + + Error Erreur - + Your password is too short. Mot de passe trop court - + The new passwords don't match. Les nouveaux mots de passe ne correspondent pas. @@ -2880,93 +3498,93 @@ Pour enlever votre avatar actuel, confirmez sans choisir une nouvelle image. DlgEditTokens - + &Name: &Nom : - + C&olor: C&ouleur : - + white blanc - + blue bleu - + black noir - + red rouge - + green vert - + multicolor multicolore - + colorless incolore - + &P/T: &F/E : - + &Annotation: &Annotation : - + Token data Données du jeton - - + + Add token Ajouter un jeton - + Remove token Retirer le jeton - + Edit custom tokens Éditer les jetons personnalisés - + Please enter the name of the token: Entrez le nom du jeton : - + Error Erreur - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Le nom choisi est en conflit avec une carte ou un jeton existant. @@ -3045,8 +3663,9 @@ Assurez-vous d'activer l'édition « Fausse édition contenant les je - Hide games that have started - Cacher les parties qui ont commencées + Hide started games + Hide games that have started + @@ -3081,8 +3700,9 @@ Assurez-vous d'activer l'édition « Fausse édition contenant les je - &Creator name: - Nom du &créateur : + &Host name: + &Creator name: + @@ -3148,52 +3768,52 @@ Assurez-vous d'activer l'édition « Fausse édition contenant les je DlgForgotPasswordChallenge - + Reset Password Challenge Warning Avertissement Réinitialisation du mot de passe Challenge - + A problem has occurred. Please try to request a new password again. Un probleme est survenu. Veuillez réessayer de demander un nouveau mot de passe. - + Enter the information of the server and the account you'd like to request a new password for. Veuillez entrer les informations du serveur et du compte pour lesquels vous voulez demander un nouveau mot de passe. - + &Host: &Hôte : - + &Port: &Port : - + Player &name: &Nom du joueur : - + Email: Adresse e-mail : - + Reset Password Challenge Réinitialisation du mot de passe Challenge - + Reset Password Challenge Error Erreur Réinitialisation du mot de passe Challenge - + The email address can't be empty. L'adresse e-mail ne peut être vide. @@ -3201,37 +3821,37 @@ Assurez-vous d'activer l'édition « Fausse édition contenant les je DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. Entrez les informations du serveur pour lequel vous souhaitez demander un nouveau mot de passe - + &Host: &Hôte : - + &Port: &Port : - + Player &name: &Nom du joueur : - + Reset Password Request Demande de réinitialisation du mot de passe - + Reset Password Error Erreur lors de la réinitialisation du mot de passe - + The player name can't be empty. Le nom du joueur ne peut être vide. @@ -3239,94 +3859,127 @@ Assurez-vous d'activer l'édition « Fausse édition contenant les je DlgForgotPasswordReset - + Reset Password Warning Avertissement lors de la réinitialisation du mot de passe - + A problem has occurred. Please try to request a new password again. Un problème est survenu. Veuillez demander un nouveau mot de passe. - + Enter the received token and the new password in order to set your new password. Entrez le code reçu et le nouveau mot de passe afin d'initialiser le nouveau mot de passe - + &Host: &Hôte : - + &Port: &Port : - + Player &name: &Nom du joueur : - + Token: Jeton : - - + + New Password: Nouveau mot de passe : - + Reset Password Réinitialiser le mot de passe - + The player name can't be empty. Le nom du joueur ne peut être vide. - - - - + + + + Reset Password Error Erreur de réinitialisation du mot de passe - + The token can't be empty. Le jeton ne peut pas être vide. - + The new password can't be empty. Le nouveau mot de passe ne peut être vide. - + Error Erreur - + Your password is too short. Mot de passe trop court - + The passwords do not match. Les mots de passe ne correspondent pas. + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck Charger un deck @@ -3416,165 +4069,566 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLocalGameOptions - + Players: Joueurs : - + General Général - + Starting life total: Total de vie initial : - + Game setup options Options de configuration de la partie - + Remember settings Conserver les paramètres - + Local game options Options de configuration de partie locale + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): Nom de la carte (ou expression de recherche) : - + Number of hits: Nombre de cartes trouvées : - + Auto play hits Jouer les cartes trouvées automatiquement - + Put top cards on stack until... Placer la carte du dessus sur la pile jusqu'à... - + No cards matching the search expression exists in the card database. Proceed anyways? Aucune carte ne correspondant à l'expression recherchée n'existe dans la base de données de cartes. Continuer malgré tout ? - + Cockatrice Cockatrice - + Invalid filter Filtre invalide + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. Entrez votre identifiant et l'identifiant du serveur auquel vous voulez vous connecter. Un e-mail va vous être envoyé pour vérifier votre compte. - + &Host: &Hôte : - + &Port: &Port : - + Player &name: &Nom du joueur : - + P&assword: Mot de p&asse : - + Password (again): Mot de passe (encore) : - + Email: Adresse mail : - + Email (again): Adresse mail (encore) : - + Country: Pays : - + Undefined Indéfini - + Real name: Nom réel : - + Register to server S'enregistrer sur le serveur - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning Avertissement d'enregistrement - + Your password is too short. Mot de passe trop court - + Your passwords do not match, please try again. Vos mots de passe ne correspondent pas, veuillez réessayer. - + Your email addresses do not match, please try again. Vos adresses mail ne correspondent pas, veuillez réessayer. - + The player name can't be empty. Le nom du joueur ne peut pas être vide. + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice @@ -3640,46 +4694,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Erreur inconnue lors du chargement de la base de données de cartes. - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - Votre base de données de cartes est invalide. - -Cockatrice peut ne pas fonctionner correctement avec une base de données invalide. - -Vous devrez peut-être redémarrer Oracle pour mettre à jour votre base de données de cartes. - -Voulez-vous changer l'emplacement de votre base de données ? - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - La version de votre base de données de cartes est trop ancienne. - -Cela peut causer des problèmes lors du chargement des images ou des informations des cartes. - -Généralement, il suffit de redémarrer oracle pour mettre à jour votre base de données de cartes. - -Voulez-vous changer l'emplacement de votre base de données ? - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3692,7 +4712,7 @@ Veuillez ouvrir un ticket sur https://github.com/Cockatrice/Cockatrice/issues av Voulez-vous changer l'emplacement des données ? - + File Error loading your card database. Would you like to change your database location setting? @@ -3701,7 +4721,7 @@ Would you like to change your database location setting? Voulez-vous changer l'emplacement de votre base de données ? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3710,7 +4730,7 @@ Would you like to change your database location setting? Voulez-vous changer l'emplacement de votre base de données ? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3723,77 +4743,208 @@ Veuillez ouvrir un ticket sur https://github.com/Cockatrice/Cockatrice/issues Voulez-vous changer l'emplacement de votre base de données ? - - - + + + Error Erreur - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Le chemin d'accès vers votre répertoire de decks est invalide. Voulez-vous redéfinir le chemin d'accès ? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Le chemin d'accès vers votre répertoire d'illustrations de cartes est invalide. Voulez-vous redéfinir le chemin d'accès ? - + Settings Paramètres - + + Search settings... + + + + + OK + + + + General Général - + Appearance Apparence - + User Interface Interface utilisateur - + Storage - + Card Sources Origine de carte - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat Chat - + Sound Son - + Shortcuts Raccourcis + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check Vérification de la mise à jour des cartes - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. @@ -3802,27 +4953,27 @@ Choisissez comment vous souhaitez exécuter le programme de mise à jour de la b Vous pouvez toujours modifier ce comportement dans l'onglet « Général » des paramètres. - + Run in foreground S'exécuter en tâche principale - + Run in background S'exécuter en tâche de fond - + Run in background and always from now on S'exécuter en tâche de fond, et toujours le faire à partir de maintenant - + Don't prompt again and don't run Ne pas demander à nouveau et ne pas exécuter - + Don't run this time Ne pas exécuter cette fois @@ -3830,17 +4981,17 @@ Vous pouvez toujours modifier ce comportement dans l'onglet « Général » DlgTipOfTheDay - + Next suivant - + Previous précédent - + Tip of the Day Conseil du jour @@ -3848,166 +4999,176 @@ Vous pouvez toujours modifier ce comportement dans l'onglet « Général » DlgUpdate - + Current release channel Branche logicielle actuelle - + Reinstall Réinstaller - + Cancel Download Annuler le téléchargement - + Open Download Page Ouvrir la page de téléchargement - + Check for Client Updates Vérification des mises à jour - - - - + + + + Error Erreur - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice n'as pas été construit avec le support du SSL, donc il n'est pas possible de télécharger les mises à jour automatiquement. Veuillez s'il vous plaît visiter la page de téléchargement, pour mettre à jour manuellement. - + Downloading update: %1 Téléchargement de la mise à jour : %1 - + Checking for updates... Vérification des mises à jour... - + Finished checking for updates Fin de la recherche des mises à jour - + No Update Available Pas de mises à jour disponibles - + Cockatrice is up to date! Cockatrice est à jour ! - + You are already running the latest version available in the chosen release channel. Vous avez déjà la dernière version correspondant à votre branche. - + Current version Version actuelle - + Selected release channel Branche logicielle sélectionnée - - + + Update Available Mise à jour disponible - - + + A new version of Cockatrice is available! Une nouvelle version de Cockatrice est disponible ! - - + + New version Nouvelle version - - + + Released Sortie - - + + Changelog Notes de version - + Do you want to update now? Voulez-vous mettre à jour maintenant ? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. Malheureusement, la mise à jour automatique n'a pas pu trouver un téléchargement compatible. Vous allez peut-être devoir télécharger manuellement la nouvelle version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. Veuillez vérifier la <a href="%1">page "releases"</a> sur notre Github et télécharger le programme pour votre système. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error Erreur de mise à jour - + An error occurred while checking for updates: Une erreur est survenur pendant la vérification des mises à jour : - + An error occurred while downloading an update: Une erreur est survenue pendant le téléchargement de la mise à jour : - + Installing... Installation... - + Cockatrice is unable to open the installer. Cockatrice n'a pas pu ouvrir l'installateur. - + Try to update manually by closing Cockatrice and running the installer. Essayer de mettre à jour manuellement en fermant Cockatrice et en exécutant l'installateur. - + Download location Répertoire de téléchargement @@ -4015,17 +5176,17 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. DlgViewLog - + Clear log when closing Effacer le journal lors de la fermeture - + Copy to clipboard Charger vers le presse-papier - + Debug Log Journal de debug @@ -4263,7 +5424,7 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: Sel : @@ -4271,7 +5432,7 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. FilterBuilder - + Type your filter here Tapez votre filtre ici @@ -4279,45 +5440,88 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. FilterDisplayWidget - + Confirm Delete Confirmer la suppression - + Are you sure you want to delete the filter '%1'? Êtes-vous sûr de vouloir supprimer le filtre '%1' ? - + Delete Failed Suppression échouée - + Failed to delete filter '%1'. Échec de la suppression du filtre '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator Éjecté par l'hôte de la partie ou un modérateur - + player left the game joueur a quitté la partie - + player disconnected from server joueur déconnecté du serveur - + reason unknown raison inconnue @@ -4325,140 +5529,156 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. GameSelector - - - - - - - - - + + + + + + + + + Error Erreur - + Please join the appropriate room first. Veuillez d'abord rejoindre le bon salon. - + Wrong password. Mot de passe incorrect. - + Spectators are not allowed in this game. Les spectateurs ne sont pas autorisés dans cette partie. - + The game is already full. Cette partie est déjà complète. - + The game does not exist any more. La partie n'existe plus. - + This game is only open to registered users. Cette partie n'est accessible qu'aux joueurs enregistrés. - + This game is only open to its creator's buddies. Cette partie n'est accessible qu'aux amis de son créateur. - + You are being ignored by the creator of this game. Vous avez été ignoré par le créateur de la partie. - + Join Game Rejoindre la partie - + Spectate Game Rejoindre la partie comme spectateur - + Game Information Informations sur la partie - + + Cop&y game link + + + + Join Game as Judge Joindre la partie comme juge - + Spectate Game as Judge Joindre la partie comme juge spectateur - + + Join game Rejoindre partie - - Password: - Mot de passe : + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. Veuillez d'abord rejoindre le bon salon. - + &Filter games &Filtrer les jeux - + C&lear filter Effacer les fi&ltres - + C&reate C&réer - + &Join Re&joindre - + Join as judge Joindre en tant que juge - + J&oin as spectator Rej&oindre en tant que spectateur - + Join as judge spectator Joindre comme juge spectateur - + Games shown: %1 / %2 Parties affichées : %1 / %2 - + Games Parties @@ -4499,105 +5719,105 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. GamesModel - + >1 day > 1 jour - + %1%2 hr short age in hours %1%2 h%1%2 h%1%2 h - + new Nouveau - + %1%2 min short age in minutes %1%2 min%1%2 min%1%2 min - + password mot de passe - + buddies only amis uniquement - + reg. users only joueurs enregistrés uniquement - + open decklists Ouvrir les listes de deck - - + + can chat peut discuter - + see hands voir les mains - + can see hands peut voir les mains - + not allowed non autorisé - + Room Salon - + Age Âge - + Description Description - - Creator - Créateur + + Host + - + Type Type - + Restrictions Restrictions - + Players Joueurs - + Spectators Spectateurs @@ -4605,158 +5825,275 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. GeneralSettingsPage - + Reset all paths Réinitialiser tous les chemins - + All paths have been reset Les chemins ont été réinitialisés - - - - - - - + + + + English + Français (French) + + + + + + + + + Choose path Choisir un chemin d'accès - + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> + + + + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + Language settings - + Language: Langue : - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + Version settings - + Card database - + Startup settings - + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) Chemins (édition impossible en mode portable) - + Paths Chemins d’accès - + How to help with translations Comment aider avec les traductions - + Decks directory: Répertoire des decks : - + Filters directory: Répertoire des filtres : - + Replays directory: Répertoire des replays : - + Pictures directory: Répertoire des images : - + Card database: Base de données des cartes : - + Custom database directory: Répertoire de la base de données personnalisé - + Token database: Bases de données des jetons : - + Update channel Branche de la mise à jour : - + Check for client updates on startup Vérifier les mises à jour du client au démarrage - + Check for card database updates on startup Vérifier les mises à jour de la base de données des cartes au démarrage... - + Don't check Ne pas vérifier - + Prompt for update Confirmer les mises à jour - + Always update in the background Toujours mettre à jour en tâche de fond - + Check for card database updates every Vérifier les mises à jour de la base de données des cartes tous les - + days jour(s) - + Notify if a feature supported by the server is missing in my client M'avertir si une fonctionnalité supportée par le serveur est manquante sur mon client. - + Automatically run Oracle when running a new version of Cockatrice Lancer Oracle automatiquement quand Cockatrice a été mis à jour. - + Show tips on startup Afficher les astuces au démarrage. - + Last update check on %1 (%2 days ago) Dernière vérification de mise à jour le %1 (il y a %2 jours) @@ -4764,47 +6101,47 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. GraveyardMenu - + &Graveyard &Cimetière - + &View graveyard &Voir le cimetière - + &Move graveyard to... &Déplacer le cimetière vers... - + &Top of library &Dessus de la bibliothèque - + &Bottom of library &Dessous de la bibliothèque - + &All players &Tous les joueurs - + &Hand &Main - + &Exile &Exil - + Reveal random card to... Révéler une carte au hasard à... @@ -4898,59 +6235,353 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version.Révéler une c&arte au hasard à... + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck Créer un Nouveau Deck - + Browse Decks Parcourir les Decks - + Browse Card Database Parcourir la Base de Carte - + Browse EDHRec Parcourir EDHRec - + Browse Archidekt Parcourir Archidekt - + View Replays Voir les replays - + Quit Quitter - + Connecting... Connection... - + Connect Se connecter - + Play Jouer + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu @@ -5168,273 +6799,294 @@ Vous allez peut-être devoir télécharger manuellement la nouvelle version. MainWindow - - + + Player %1 Joueur %1 - + Load replay Charger replay - + About Cockatrice À propos de Cockatrice - + Version Version - + Cockatrice Webpage Page web de Cockatrice - + Project Manager: Chef de projet: - + Past Project Managers: Anciens chefs de projet: - + Developers: Développeurs : - + Our Developers Nos développeurs - + Help Develop! Aidez à développer ! - + Translators: Traducteurs : - + Our Translators Nos traducteurs - + Help Translate! Aidez à traduire ! - + Support: Assistance : - + Report an Issue Signaler un problème - + Troubleshooting Dépannage - + F.A.Q. F.À.Q. - - + + Error Erreur - + &Connect... &Connecter... - + &Disconnect &Déconnecter - + Start &local game... Démarrer une partie &locale... - + &Watch replay... &Regarder un replay... - + &Full screen &Plein écran - + &Register to server... S'enregistrer sur le serveur... - + &Restore password... &Restaurer le mot de passe... - + &Settings... &Paramètres... - + &Exit &Quitter - + A&ctions Actions - + &Cockatrice &Cockatrice - + C&ard Database B&ase de données de cartes - + &Manage sets... &Gérer les éditions... - + Edit custom &tokens... Éditer les &jetons personnalisés… - + Open custom image folder Ouvrir le dossier d'images personnalisées - + Open custom sets folder Ouvrir le dossier d'éditions personnalisées - + Add custom sets/cards Ajouter des éditions / cartes personnalisées - + Reload card database Recharger la base de cartes - + Tabs Onglets - + &Help A&ide - + &About Cockatrice À propos de Cock&atrice - + &Tip of the Day &Conseil du jour - + Check for Client Updates Vérifier les mises à jour - + Check for Card Updates... Vérifier les mises à jour des cartes… - + Check for Card Updates (Automatic) Vérifier les mises à jour des cartes (automatiquement) - + Show Status Bar Montrer la barre de statut - + View &Debug Log Voir journal de &Debug - + Open Settings Folder Ouvrir le dossier des paramètres - + + Re-run Onboarding Wizard... + + + + Show/Hide Montrer/Cacher - + + Sign-in cancelled + + + + New Version Nouvelle version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Bravo pour avoir mis à jour Cockatrice %1 ! Oracle va maintenant se lancer pour mettre à jour votre base de données de cartes. - + Cockatrice installed Installation de Cockatrice terminée - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Félicitations ! Vous venez d'installer Cockatrice %1 ! Oracle va maintenant se lancer pour installer la base de données de cartes initiale. - + + Are you sure? + Êtes-vous sûr? + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database Base de données de cartes - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5443,29 +7095,29 @@ Voulez-vous la mettre à jour maintenant ? Si vous n’êtes pas sûr, ou si c'est la première fois que vous jouez, choisissez « Oui ». - - + + Yes Oui - - + + No Non - + Open settings Ouvrir les paramètres - + New sets found Nouvelles éditions détectées - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5478,22 +7130,22 @@ Code(s) d'édition(s) : %1 Voulez-vous l'(les) activer ? - + Yes, always enable - + View sets Voir les éditions - + Welcome Bienvenue - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5502,90 +7154,90 @@ Toutes les éditions de la base de données de cartes ont été activées. Pour plus d'informations sur la modification de l'ordre des éditions, la désactivation d'éditions spécifiques, et leurs effets, consultez le menu « Gérer les éditions… ». - + Information Informations - + A card database update is already running. Une mise à jour de la base de données de cartes est déjà en cours. - + Unable to run the card database updater: Impossible de lancer la mise à jour de la base de données de cartes : - + Card database update running. Une mise à jour de la base de données de cartes est en cours. - + Failed to start. The file might be missing, or permissions might be incorrect. Échec au démarrage. Le fichier peut être manquant, ou les permissions sont incorrectes. - + The process crashed some time after starting successfully. Le processus a échoué quelque temps après avoir démarré avec succès. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. Temps limite dépassé. Le processus a pris trop longtemps pour répondre. La dernière fonction waitFor...() a terminé. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. Une erreur a eu lieu en essayant d'écrire vers le processus. Par exemple, le processus pourrait ne pas tourner, ou il pourrait avoir son canal d'entrée fermé. - + An error occurred when attempting to read from the process. For example, the process may not be running. Une erreur a eu lieu en essayant d'écrire vers le processus. Par exemple, le processus pourrait ne pas tourner, ou il pourrait avoir son canal de sortie inactif. - + Unknown error occurred. Une erreur inconnue est arrivée. - + The card database updater exited with an error: %1 L'outil de mise à jour de la base de données de cartes s'est arrêté avec l'erreur : %1 - - - - - + + + + + Load sets/cards Charger des éditions/cartes - + Selected file cannot be found. Le fichier sélectionné n'a pas pu être trouvé. - + You can only import XML databases at this time. Il n'est actuellement possible que d'importer des bases de données XML. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Les nouvelles éditions/cartes ont été ajoutées avec succès. Cockactrice va maintenant recharger la base de données de cartes. - + Sets/cards failed to import. Échec de l'importation des éditions/cartes. @@ -5790,615 +7442,615 @@ Cockactrice va maintenant recharger la base de données de cartes. MessageLogWidget - + from play depuis le jeu - + from their graveyard depuis son cimetière - + from exile depuis l'exil - + from their hand depuis sa main - + the top card of %1's library la carte du dessus de la bibliothèque de %1 - + the top card of their library le carte du dessus de sa bibliothèque - + from the top of %1's library à partir du dessus de la bibliothèque de %1 - + from the top of their library du dessus de sa bibliothèque - + the bottom card of %1's library la carte du dessous de la bibliothèque de %1 - + the bottom card of their library la carte du dessous de sa bibliothèque - + from the bottom of %1's library à partir du dessous de la bibliothèque de %1 - + from the bottom of their library du dessous de sa bibliothèque - + from %1's library de la bibliothèque de %1 - + from their library depuis sa bibliothèque - + from sideboard depuis sa réserve - + from the stack depuis la pile - + from custom zone '%1' depuis la zone personnalisée '%1' - + %1 is now keeping the top card %2 revealed. %1 garde maintenant la carte du dessus de sa bibliothèque %2 révélée. - + %1 is not revealing the top card %2 any longer. %1 ne révèle plus la carte du dessus de sa bibliothèque %2. - + %1 can now look at top card %2 at any time. %1 peut maintenant regarder la carte du dessus de sa bibliothèque %2 à n'importe quel moment. - + %1 no longer can look at top card %2 at any time. %1 ne peut plus regarder la carte du dessus de sa bibliothèque %2 à n'importe quel moment. - + %1 attaches %2 to %3's %4. %1 attache %2 à %4 de %3. - + %1 has conceded the game. %1 a concédé la partie. - + %1 has unconceded the game. %1 reste dans la partie. - + %1 has restored connection to the game. %1 est revenu dans la partie. - + %1 has lost connection to the game. %1 a perdu la connexion à la partie. - + %1 points from their %2 to themselves. %1 se cible avec son %2. - + %1 points from their %2 to %3. %1 cible %3 avec son %2. - + %1 points from %2's %3 to themselves. %1 se cible avec %3 de %2. - + %1 points from %2's %3 to %4. %1 cible %4 avec %3 de %2. - + %1 points from their %2 to their %3. %1 cible de son %3 vers son %2. - + %1 points from their %2 to %3's %4. %1 cible %3 de %4 avec %2. - + %1 points from %2's %3 to their own %4. %1 cible son %4 avec %3 de %2. - + %1 points from %2's %3 to %4's %5. %1 cible %5 de %4 avec %3 de %2. - + %1 creates a face down token. %1 crée un jeton face-cachée. - + %1 creates token: %2%3. %1 crée un jeton %2%3. - + %1 has loaded a deck (%2). %1 a chargé un deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 a chargé un deck avec %2 cartes en réserve (%3). - + %1 destroys %2. %1 détruit %2. - + a card une carte - + %1 gives %2 control over %3. %1 donne le contrôle de %2 à %3. - + %1 puts %2 into play%3 face down. %1 met %2 en jeu%3 face cachée. - + %1 puts %2 into play%3. %1 met %2 en jeu %3. - + %1 puts %2%3 into their graveyard face down. %1 met %2%3 dans leur cimetière face cachée. - + %1 puts %2%3 into their graveyard. %1 met %2%3 dans son cimetière. - + %1 exiles %2%3 face down. %1 exile %2%3 face cachée. - + %1 exiles %2%3. %1 exile %2%3. - + %1 moves %2%3 to their hand. %1 met %2%3 dans sa main. - + %1 puts %2%3 into their library. %1 met %2%3 dans sa bibliothèque. - + %1 puts %2%3 onto the bottom of their library. %1 met %2%3 au-dessous de sa bibliothèque. - + %1 puts %2%3 on top of their library. %1 met %2%3 au-dessus de sa bibliothèque. - + %1 puts %2%3 into their library %4 cards from the top. %1 met %2%3 dans sa bibliothèque %4 cartes depuis le dessus - + %1 moves %2%3 to sideboard. %1 met %2%3 à sa réserve. - + %1 plays %2%3 face down. %1 joue %2%3 face cachée. - + %1 plays %2%3. %1 joue %2%3. - + %1 moves %2%3 to custom zone '%4' face down. %1 déplace %2%3 vers la zone personnalisée %4 face cachée. - + %1 moves %2%3 to custom zone '%4'. %1 déplace %2%3 vers la zone personnalisée '%4'. - + %1 tries to draw from an empty library %1 essaie de piocher dans une bibliothèque vide - + %1 draws %2 card(s). %1 pioche %2 carte.%1 pioche %2 cartes.%1 pioche %2 carte(s). - + %1 is looking at %2. %1 regarde %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 regarde la %4 %3 carte %2.%1 regarde les %4 %3 cartes %2.%1 regarde la / les %4 %3 carte(s) %2. - + bottom dessous - + top dessus - + %1 turns %2 face-down. %1 retourne %2 face cachée. - + %1 turns %2 face-up. %1 retourne %2 face visible. - + The game has been closed. La partie a été fermée. - + The game has started. La partie commence. - + You are flooding the game. Please wait a couple of seconds. Vous floodez la partie. Veuillez patienter quelques secondes. - + %1 has joined the game. %1 a rejoint la partie. - + %1 is now watching the game. %1 est maintenant spectateur. - + You have been kicked out of the game. Vous avez été expulsé de la partie. - + %1 has left the game (%2). %1 a quitté la partie (%2). - + %1 is not watching the game any more (%2). %1 n'observe plus la partie (%2). - + %1 is not ready to start the game any more. %1 n'est plus prêt à démarrer la partie. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 mélange son deck et pioche une nouvelle main de %2 carte.%1 mélange sa bibliothèque et pioche une nouvelle main de %2 cartes.%1 mélange sa bibliothèque et pioche une nouvelle main de %2 carte(s). - + %1 shuffles their deck and draws a new hand. %1 mélange sa bibliothèque et pioche une nouvelle main. - + You are watching a replay of game #%1. Vous regardez un replay de la partie n° %1. - + %1 is ready to start the game. %1 est prêt à démarrer la partie. - + cards an unknown amount of cards cartes - + %1 card(s) a card for singular, %1 cards for plural %1 carte%1 cartes%1 carte(s) - + %1 lends %2 to %3. %1 prêtes %2 à %3. - + %1 reveals %2 to %3. %1 révèle %2 à %3. - + %1 reveals %2. %1 révèle %2. - + %1 randomly reveals %2%3 to %4. %1 révèle au hasard %2%3 à %4. - + %1 randomly reveals %2%3. %1 révèle au hasard %2%3. - + %1 peeks at face down card #%2. %1 regarde furtivement la carte face cachée n° %2. - + %1 peeks at face down card #%2: %3. %1 regarde furtivement la carte face cachée n° %2 : %3. - + %1 reveals %2%3 to %4. %1 révèle %2%3 à %4. - + %1 reveals %2%3. %1 révèle %2%3. - + %1 reversed turn order, now it's %2. %1 a inversé l'ordre des tours, c'est maintenant %2. - + reversed inversé - + normal normal - + Heads Face - + Tails Pile - + %1 flipped a coin. It landed as %2. %1 a lancé une pièce. Il a fait %2. - + %1 rolls a %2 with a %3-sided die. %1 a fait %2 avec un dé %3 faces. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 lances %2 pièces. Il y'a %3 faces et %4 piles. - + %1 rolls a %2-sided dice %3 times: %4. %1 lances un dé à %2-faces %3 fois: %4. - + %1's turn. Tour de %1 - + %1 sets annotation of %2 to %3. %1 met l'annotation %3 à %2. - + %1 places %2 %3%4 counter(s) on %5 (now %6). - + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + %1 failed to undo their last draw. - + %1 sets counter %2 to %3 (%4%5). %1 met les marqueurs %2 à %3 (%4%5). - + %1 sets %2 to not untap normally. %2 de %1 ne se dégagera pas lors de l'étape de dégagement. - + %1 sets %2 to untap normally. %2 de %1 se dégagera lors de l'étape de dégagement. - + %1 removes the PT of %2. %1 retire la F/E de %2. - + %1 changes the PT of %2 from nothing to %4. %1 met la F/E de %2 à %4. - + %1 changes the PT of %2 from %3 to %4. %1 passe la F/E de %2 de %3 à %4. - + %1 has locked their sideboard. %1 a verrouillé sa réserve. - + %1 has unlocked their sideboard. %1 a déverrouillé sa réserve. - + %1 taps their permanents. %1 engage ses permanents. - + %1 untaps their permanents. %1 dégage ses permanents. - + %1 taps %2. %1 engage %2. - + %1 untaps %2. %1 dégage %2. - + %1 shuffles %2. %1 mélange %2. - + %1 shuffles the bottom %3 cards of %2. %1 mélange les %3 cartes du dessous de la bibliothèque de %2. - + %1 shuffles the top %3 cards of %2. %1 mélange les %3 cartes du dessus de la bibliothèque de %2. - + %1 shuffles cards %3 - %4 of %2. %1 mélange les cartes %3 - %4 de %2. - + %1 unattaches %2. %1 détache %2. - + %1 undoes their last draw. %1 annule sa dernière pioche. - + %1 undoes their last draw (%2). %1 annule sa dernière pioche (%2). @@ -6406,115 +8058,125 @@ Cockactrice va maintenant recharger la base de données de cartes. MessagesSettingsPage - + Word1 Word2 Word3 Mot1 Mot2 Mot3 - + Add New Message Ajouter un message - + Edit Message Éditer le message - + Remove Message Supprimer le message - + Add message Ajouter message - - + + Message: Message : - + Edit message Éditer le message - + Chat settings Paramètres du chat - + Custom alert words Mots d'alertes personnalisées - + Enable chat mentions Activer les mentions dans le chat - + Enable mention completer Activer les mentions dans le chat - + In-game message macros Macros de message en jeu - + How to use in-game message macros Comment utiliser les macro pour les messages en jeu - + Ignore chat room messages sent by unregistered users Ignorer les messages dans le chat principal envoyés par des joueurs non enregistrés. - + Ignore private messages sent by unregistered users Ignorer les messages privés envoyés par des joueurs non enregistrés. - + Ignore private messages sent by non-buddy users - - + + Invert text color Inverser la couleur du texte - + Enable desktop notifications for private messages Activer les notifications de bureau pour les messages privés. - + Enable desktop notification for mentions Activer les notifications de bureau pour les mentions. - + Enable room message history on join Activer l'historique des messages du chat principal dès la connexion - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) (La couleur est hexadécimale) - + Separate words with a space, alphanumeric characters only Séparer les mots avec un espace, seulement les caractères alphanumériques @@ -6522,42 +8184,42 @@ Cockactrice va maintenant recharger la base de données de cartes. MoveMenu - + Move to Déplacer vers - + &Top of library in random order &Dessus de la bibliothèque dans un ordre aléatoire - + X cards from the top of library... X cartes du dessus de la bibliothèque... - + &Bottom of library in random order &Dessous de la bibliothèque dans un ordre aléatoire - + T&able Tableau - + &Hand &Main - + &Graveyard &Cimetière - + &Exile &Exil @@ -6636,148 +8298,134 @@ Cockactrice va maintenant recharger la base de données de cartes. PaletteEditorDialog - - + + Reset Réinitialiser - - - Apply - Appliquer - - - - - Save && Apply - Sauvegarder && Appliquer - - - + ▼ Edit Palette - - + + ▶ Edit Palette - + Palette Editor — %1 - + <b>Palette Editor</b> &nbsp;·&nbsp; %1 - - This theme ships no default palette files - - - - - Replace current colours with the theme author's defaults - - - - - Switch between the light and dark palette files + + + Save - Editing: + Delete this scheme's custom palette and revert to the theme default (or the application palette) - Show or hide the per-role colour grid for manual tweaks + Switch between the light and dark palette files + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + ↺ Revert to theme default - + Discard unsaved edits and restore the last saved palette - - Preview this palette without saving to disk + + Cannot save: this theme has no writable directory - + Write palette-%1.toml and reload the theme - - Cannot save: this theme has no directory on disk - - - - + + Save failed - + + Could not write %1 to: %2 - - - No default found - - - - - No default palette file found for the "%1" scheme. - - PaletteGridWidget - + Active Actif - + Disabled Désactivé - + Inactive Inactif - + Normal interactive state - + Widget is disabled / not interactive - + Window is in background / unfocused + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + Phase @@ -6900,30 +8548,21 @@ Cockactrice va maintenant recharger la base de données de cartes. Phase de fin de tour - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - fr - - PlayerActions - - - - + + + + grave cimetière - - - - + + + + exile exile @@ -7042,16 +8681,272 @@ Cockactrice va maintenant recharger la base de données de cartes. PlayerMenu - + Player "%1" Joueur "%1" - + &Counters &Marqueurs + + PlaymatCollectionDialog + + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + PrintingDisabledInfoWidget @@ -7076,12 +8971,12 @@ Cette option fait que vous ne verrez que l'impression par défaut pour chaq PrintingSelector - + Display Navigation Buttons Afficher les boutons de navigation - + Printing Selector Sélection d'impression @@ -7089,25 +8984,65 @@ Cette option fait que vous ne verrez que l'impression par défaut pour chaq PrintingSelectorCardOverlayWidget - + Preference Préférence - + Pin Printing Épingler Imprimer - + Unpin Printing Désépingler Imprimer - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards Afficher les cartes associées + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -7138,28 +9073,28 @@ Cette option fait que vous ne verrez que l'impression par défaut pour chaq PrintingSelectorCardSortingWidget - + Alphabetical Alphabétiquement - + Preference Préférences - + Release Date Date de Sortie - - + + Descending Descendant - + Ascending Ascendant @@ -7175,95 +9110,103 @@ Cette option fait que vous ne verrez que l'impression par défaut pour chaq PtMenu - + Power / toughness Force / Endurance - + &Increase power &Augmenter la force - + &Decrease power &Diminuer la force - + I&ncrease toughness A&ugmenter l'endurance - + D&ecrease toughness D&iminuer l'endurance - + In&crease power and toughness Au&gmenter la force et l'endurance - + Dec&rease power and toughness Dim&inuer la force et l'endurance - + Increase power and decrease toughness Augmenter la force et diminuer l'endurance - + Decrease power and increase toughness Diminuer la force et augmenter l'endurance - + Set &power and toughness... Changer la &force et l'endurance... - + Reset p&ower and toughness Réinitialiser la f&orce et l'endurance + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services Services - + Hide %1 Cacher %1 - + Hide Others Cacher les autres - + Show All Montrer tout - + Preferences... Préférences... - + Quit %1 Quitter %1 - + About %1 À propos %1 @@ -7271,17 +9214,17 @@ Cette option fait que vous ne verrez que l'impression par défaut pour chaq QObject - + Cockatrice card database (*.xml) Base de données de cartes Cockatrice (*.xml) - + All files (*.*) Tous les fichiers (*.*) - + Cockatrice replays (*.cor) Replays Cockatrice (*.cor) @@ -7301,17 +9244,22 @@ Cette option fait que vous ne verrez que l'impression par défaut pour chaq Jetons - + + Maybeboard + + + + Overwrite Existing File? Écraser le fichier existant ? - + A .cod version of this deck already exists. Overwrite it? Une version en fichier .cod existe déjà pour ce deck. L'écraser ? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7328,7 +9276,7 @@ Vous devrez utiliser le Gestionnaire d'extensions, accessible via : Base de Êtes-vous sûr de vouloir activer cette option ? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7345,100 +9293,110 @@ Vous pouvez également utiliser le gestionnaire de d'extension pour personn Êtes-vous sûr de vouloir désactiver cette option ? - + Confirm Change Confirmer le changement + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK OK - + Save Sauvegarder - + Save All Sauvegarder tout - + Open Ouvrir - + &Yes &Oui - + Yes to &All Dire oui à tout - + &No &Non - + N&o to All Dire non à tout - + Abort Abandonner - + Retry Réessayer - + Ignore Ignorer - + Close Fermer - + Cancel Annuler - + Discard Effacer - + Help Aide - + Apply Appliquer - + Reset Réinitialiser - + Restore Defaults Remettre à zéro @@ -7446,121 +9404,189 @@ Vous pouvez également utiliser le gestionnaire de d'extension pour personn QuickSetupPanel - + %1% - + <b>Quick Setup</b> - + Generate all palette roles automatically from a single accent colour - + Accent: - + Primary hue. Used directly for highlights and links. At high intensity it also tints buttons and backgrounds. - + Intensity: - + Subtle - + Full colour - + 0–30 Subtle tint — only highlights and links change hue 30–70 Accented — buttons, tooltips, and borders join in 70–100 Full colour — backgrounds, everything - + 70% - - - Generate ↓ - - - - - Derive all palette roles from the accent colour above. -Fine-tune individual colours in the grid afterwards. - - RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name Nom - + ID ID - + Upload time Temps de chargement + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID ID - + Name Nom - + Players Joueurs - + Keep Garder - + Time started Le temps a commencé - + Duration (sec) Durée (sec) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu @@ -7602,37 +9628,37 @@ Fine-tune individual colours in the grid afterwards. RoomSelector - + Rooms Salons - + Joi&n &Rejoindre - + Room Salon - + Description Description - + Permissions Permissions - + Players Joueurs - + Games Parties @@ -7640,7 +9666,7 @@ Fine-tune individual colours in the grid afterwards. SayMenu - + S&ay Dire @@ -7648,32 +9674,32 @@ Fine-tune individual colours in the grid afterwards. SequenceEdit - + Choose an action from the table Choisir une action de la table - + Hit the key/combination of keys you want to set for this action Choisissez la combinaison de touches que vous voulez utiliser pour cette action - + Invalid key Touche invalide - + Shortcut already in use by: Raccourci déjà utilisé par : - + Clear Effacer - + Restore default Options par défaut @@ -7706,56 +9732,92 @@ Fine-tune individual colours in the grid afterwards. Date de sortie + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts Réinitialiser tous les raccourcis - + Do you really want to restore all default shortcuts? Êtes-vous sûr de vouloir réinitialiser tous les raccourcis ? - + Clear all default shortcuts Effacer tous les raccourcis par défaut - + Do you really want to clear all shortcuts? Êtes-vous sûr de vouloir effacer tous les raccourcis ? - + Section: Section : - + Action: Action : - + Shortcut: Raccourci : - + + Shortcut editor + + + + How to set custom shortcuts Comment assigner des raccourcis personnalisés - + Clear all shortcuts Effacer tous les raccourcis - + Search by shortcut name Rechercher par nom de raccourci @@ -7810,12 +9872,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard &Réserve - + &View sideboard &Voir la réserve @@ -7823,27 +9885,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Activer les &sons - + Current sounds theme: Thème sonore actuel: - + Test system sound engine Tester les effets sonores - + Sound settings Paramètres de son - + Master volume Volume principal @@ -7851,48 +9913,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended La saison des spoilers est terminée - + Deleting spoiler.xml. Please run Oracle Suppression de spoiler.xml. S'il vous plaît exécuter Oracle - - + + Spoilers download failed Échec du téléchargement des spoilers - + No internet connection Pas de connexion Internet - + Error Erreur - + Spoilers already up to date Spoilers déjà à jour - + No new spoilers added Aucun nouveau spoilers ajouté - + Spoilers have been updated! Les spoilers ont été mis à jour! - + Last change: Dernier changement: @@ -7900,157 +9962,170 @@ Please check your shortcut settings! StableReleaseChannel - + Default Défaut - + No reply received from the release update server. Aucune réponse reçue de la part du serveur de mise à jour. - + Invalid reply received from the release update server. Réponse invalide reçue de la part du serveur de mise à jour. - + No reply received from the tag update server. Aucune réponse reçue de la part du serveur de mise à jour des tags. - + Invalid reply received from the tag update server. Réponse invalide reçue de la part du serveur de mise à jour des tags. + + StatsTally + + + Total Power + + + + + Total Toughness + + + StorageSettingsPage - - - + + + Success - + Cached card pictures have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + In-memory (currently loaded) card pictures have been reset. - + Card Picture Loader Caching Method: - + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. - + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. - + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. - + Delete Cached Images - + Delete Saved Images - + Clear In-Memory Images - + Card Picture Loader Cache Method - + Network Cache - + Filesystem - + In-Memory Picture Cache - + Network Cache Size: - + On-disk cache for downloaded pictures - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + In-memory cache for pictures not currently on screen - + Naming scheme: - + Day(s) Jour(s) @@ -8058,17 +10133,33 @@ Please check your shortcut settings! TabAccount - + + + My Reports + + + + Add to Buddy List Ajouter à la liste d'amis - + Add to Ignore List Ajouter à la liste noire - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account Compte @@ -8189,118 +10280,118 @@ Please check your shortcut settings! TabArchidekt - - - + + + Desc. Desc. - - + + AND ET - - + + Require ALL selected colors TOUTES les couleurs sélectionnées obligatoires - - + + Deck name... Nom du deck... - - + + Owner... Propriétaire... - - + + Packages - - + + Advanced Filters Filtres avancés - + Bracket: Catégorie : - - + + Any Tous - - + + Contains card... Contient la carte... - - + + Commander... Commandant... - - + + Tag... Tag... - - + + Deck Size Taille du deck - + Cards: Cartes : - - + + Asc. Asc. - + Sort by: Trier par : - + Filter by: Filtrer par : - + Display Settings Options d'affichage - - + + Search Rechercher - - + + Formats Formats @@ -8313,42 +10404,42 @@ Please check your shortcut settings! TabCardArtRules - + Card: Carte: - + ProviderId: - + Mode: - + Reason: - + Type a card name... - + Add rule - + Remove rule - + Refresh @@ -8361,52 +10452,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info Infos de la carte - + Deck Bibliothèque - + Filters Filtres - + &View &Voir - + Card Database Base de données de cartes - + Printing Imprimer - + Visible Visible - + Floating Flottant - + Reset layout Réinitialiser l'interface - + Deck: %1 Deck : %1 @@ -8414,49 +10505,49 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 Deck visuel : %1 - + &Visual Deck Editor &Éditeur de deck visuel - - + + Card Info Infos de la carte - - + + Deck Deck - + &View &Voir - + Printing Imprimer - + Visible Visible - + Floating Flottant - + Reset layout Réinitialiser l'interface @@ -8464,22 +10555,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View Vue de deck visuel - + Visual Database Display Affichage visuel de la base de données - + Deck Analytics Analyse de deck - + Sample Hand Exemple de main @@ -8487,139 +10578,237 @@ Please check your shortcut settings! TabDeckStorage - + Local file system Système de fichier local - + Server deck storage Serveur de stockage de deck - - + + Open in deck editor Ouvrir dans l'éditeur de deck - + Rename deck or folder Renommer le deck ou le dossier - + Upload deck Envoyer deck - + Download deck Télécharger deck - - - - + + + + New folder Nouveau dossier - - + + Delete Supprimer - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder Ouvrir le dossier des decks - + Rename local folder Renommer le dossier local - + Rename local file Renommer le fichier local - + New name: Nouveau nom : - - - - + + + + + + + Error Érreur - + Rename failed Renommage échoué - - + + + Invalid deck file Fichier de deck invalide - + Enter deck name Entrez le nom du deck - + This decklist does not have a name. Please enter a name: Ce deck n'a pas de nom. Veuillez entrer un nom: - + Unnamed deck Deck sans nom - + Failed to upload deck to server Erreur lors de l'importation du deck sur le serveur - + Delete local file Supprimer fichier local - + Are you sure you want to delete the selected files? Êtes-vous certain de vouloir supprimer les fichiers sélectionnés ? - + Delete remote decks Supprimer les decks dinstants - + Are you sure you want to delete the selected decks? Êtes-vous certain de vouloir supprimer les decks sélectionnés ? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: Nom du nouveau dossier : - + Deck Storage Stockage de deck @@ -8627,20 +10816,271 @@ Veuillez entrer un nom: TabDeckStorageVisual - + Visual Deck Storage Stockage visuel de deck - + Error Erreur - + Could not open deck at %1 N'a pas pu ouvrir le deck à %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -8653,27 +11093,27 @@ Veuillez entrer un nom: TabEdhRecMain - + &Cards &Cartes - + Top Commanders Commandants populaires - + Tags Étiquettes - + Search for a card ... Rechercher une carte ... - + Search Rechercher @@ -8686,191 +11126,207 @@ Veuillez entrer un nom: TabGame - - - + + + Replay Replay - - + + Game Partie - - + + Player List Liste des joueurs - - + + + Invite + + + + + Card Info Infos de la carte - - + + Messages Messages - - + + Replay Timeline Historique des replays - + &Phases &Phases - + &Game &Partie - + Next &phase Étape &suivante - + Next phase with &action Étape suivante avec &action - + Next &turn &Tour suivant - + Reverse turn order Inverser l'ordre des tours - + &Remove all local arrows &Retirer toutes les flèches locales - + Rotate View Cl&ockwise Pivoter la vue dans le sens h&oraire - + Rotate View Co&unterclockwise Pivoter la vue dans le sens &anti-horaire - + Game &information &Informations sur la partie - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede Annuler la &concession - - - + + + &Concede &Concéder - + &Leave game &Quitter la partie - + C&lose replay Fermer &le replay - + &Focus Chat &Focus sur le chat - + &Say: &Dire : - + Selected cards Cartes sélectionnées - + &View &Voir - + Visible Visible - + Floating Flottant - + Reset layout Réinitialiser l'interface - + Concede Concéder - + Are you sure you want to concede this game? Êtes-vous sûr de vouloir concéder la partie ? - + Unconcede Rester dans la partie - + You have already conceded. Do you want to return to this game? Vous avez déjà concédé la partie. Voulez-vous la reprendre ? - + Leave game Quitter la partie - + Are you sure you want to leave this game? Êtes-vous sûr de vouloir quitter la partie ? - + A player has joined game #%1 Un joueur a rejoint la partie #%1. - + %1 has joined the game %1 a rejoint la partie. - + You have been kicked out of the game. Vous avez été expulsé de la partie. @@ -8886,158 +11342,158 @@ Veuillez entrer un nom: TabLog - + Logs Journaux - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Durée;Expéditeur;IPExpéditeur;Message;IDCible;NomCible - + Room Logs Journaux du salon - + Game Logs Journaux de la partie - + Chat Logs Journaux du chat - - + + Error Erreur - + You must select at least one filter. Vous devez sélectionner au moins un filtre. - + You have to select a valid number of days to locate. Vous devez sélectionner un nombre valide de jours à identifier. - + Username: Nom d'utilisateur: - + IP Address: Adresse IP : - + Game Name: Nom de la partie : - + GameID: ID de la partie : - + Message: Message : - + Main Room Salon principal - + Game Room Salon de jeu - + Private Chat Chat privé - + Past X Days: Les X derniers jours : - + Today Aujourd'hui - + Last Hour La dernière heure - + Maximum Results: Résultats maximum : - + At least one filter is required. The more information you put in, the more specific your results will be. Au moins un filtre est requis. Plus vous entrez d'informations, meilleurs seront les résultats. - + Get User Logs Obtenir les journaux de l'utilisateur - + Clear Filters Effacer les filtres - + Filters Filtres - + Log Locations Emplacement des journaux - + Date Range Intervalle de dates - + Maximum Results Résultats maximum - - + + Message History Historique des messages - + Failed to collect message history information. Impossible de récupérer l'historique des messages. - + There are no messages for the selected filters. Il n'y a pas de messages pour les filtres sélectionnés. @@ -9045,218 +11501,558 @@ Plus vous entrez d'informations, meilleurs seront les résultats. TabMessage - + Private &chat &Chat privé - + &Leave &Quitter - + %1 - Private chat %1 - Chat privé - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. Cet utilisateur vous ignore, il ne peut pas voir vos messages dans le chat principal et vous ne pouvez pas rejoindre ses parties. - + Private message from Message privé de - + %1 has left the server. %1 a quitté le serveur. - + %1 has joined the server. %1 a rejoint le serveur. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system Système de fichier local - + Server replay storage Stockage du serveur de replays - - + + Watch replay Voir replay - + Rename Renommer - - + + New folder Nouveau fichier - - + + Delete Supprimer - + Open replays folder Ouvrir le dossier des Replays - + Download replay Télécharger replay - + Toggle expiration lock Changer le verrouillage de l'expiration - + Get replay share code Obtenir le code de partage du replay - - + + Look up replay by share code Regarder un replay depuis un code de partage - + Rename local folder Renommer le dossier local - + Rename local file Renommer le fichier local - + New name: Nouveau nom: - + Error Erreur - + Rename failed Renommage échoué - + Name of new folder: Nom du nouveau fichier: - + Delete local file Supprimer fichier local - + Are you sure you want to delete the selected files? Êtes-vous certain de vouloir supprimer les fichiers sélectionnés? - + Are you sure you want to delete the selected replays? Êtes-vous certain de vouloir supprimer cet enregistrement? - + Failed to get code Echec d'obtention du code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. Soit ce serveur ne prend pas en charge le partage de replay, soit il ne vous autorise pas à partager des replays. - - - + + + Failed Échec - + Could not get replay code Impossible d'obtenir le code de replay - + Replay Share Code Code de Partage de Replay - + Others can use this code to add the replay to their list of remote replays: %1 D'autres peuvent utiliser ce code pour ajouter la replay à leur liste de replay distantes :%1 - + Copy to clipboard Copier vers le presse-papier - + Replay share code Code de partage de replay - + Replay code found Code de replay trouvé - + Replay was added, or you already had access to it. Le replay a été ajoutée, ou vous y aviez déjà accès. - + Replay code not found Code de replay non trouvé - + Failed to submit code Échec de l'envoi du code - + Unexpected error Erreur inattendue - + Delete remote replay Supprimer le replay distant @@ -9266,65 +12062,478 @@ Plus vous entrez d'informations, meilleurs seront les résultats.Replays de parties + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - - Friends - - - - - Online - - - - - Ignored - - - - + &Say: &Dire : - + Chat Chat - + &Room &Salon - + &Leave room &Quitter le salon - + &Clear chat Effacer le &Chat - + Chat Settings... Paramètres du chat... - + mentioned you. vous a mentionné. - + Click to view Cliquer pour voir - + You are flooding the chat. Please wait a couple of seconds. Vous floodez le chat. Veuillez patienter quelques secondes. @@ -9332,35 +12541,35 @@ Plus vous entrez d'informations, meilleurs seront les résultats. TabServer - + Server Serveur - - - - + + + + Error Erreur - + Failed to join the server room: it doesn't exist on the server. Impossible de rejoindre le salon: il n'existe pas sur le serveur - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. Le serveur considère que vous êtes dans le salon mais votre client est incapable de l'afficher. Essayez en relançant votre client. - + You do not have the required permission to join this server room. Vous n'avez pas la permission requise pour rejoindre ce salon. - + Failed to join the server room due to an unknown error: %1. Impossible de rejoindre le salon à cause d'un erreur inconnue: %1 @@ -9368,97 +12577,173 @@ Plus vous entrez d'informations, meilleurs seront les résultats. TabSupervisor - + Deck Editor Éditeur de deck - + Visual Deck Editor Éditeur de deck visuel - + EDHRec EDHrec - + Archidekt Archidekt - + Home Accueil - + &Visual Deck Storage Stockage &visuel de decks - + Visual Database Display Affichage visuel de la base de données - + Server Serveur - + Account Compte - + Deck Storage Stockage de deck - + Game Replays Replays de parties - + Administration Administration - + Logs Journaux - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? Êtes-vous sûr ? - + There are still open games. Are you sure you want to quit? Il y a encore des parties en cours. Êtes-vous sûr de vouloir quitter ? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view Cliquer pour regarder - + Your buddy %1 has signed on! Votre ami %1 s'est inscrit ! - + Unknown Event Événement inconnu - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -9469,39 +12754,39 @@ Ce message peut signifier qu'il y a une nouvelle version de Cockatrice disp Pour mettre à jour votre client, allez dans « Aide -> Vérifier les mises à jour ». - + Idle Timeout Temporisation inactive - + You are about to be logged out due to inactivity. Vous allez d'être déconnecté en raison de l'inactivité. - + Promotion Promotion - + You have been promoted. Please log out and back in for changes to take effect. Vous avez été promu modérateur. Veuillez vous reloguer pour que les changements prennent effet. - + Warned Averti - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. You avez reçu un avertissement pour la raison : %1. Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre vous. Si vous avez des questions, veuillez contacter un modérateur en privé. - + You have received the following message from the server. (custom messages like these could be untranslated) Vous avez reçu le message suivant du serveur. @@ -9521,6 +12806,34 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre Affichage visuel de la base de données + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -9535,6 +12848,65 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre Impossible d'analyser le Deck. + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -9606,57 +12978,52 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre UserCardArtSettingsDialog - + Card Art Settings - + Type a card name... - + Card name: - - Card ProviderId: + + Printing: - + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + Left margin (%): - + Right margin (%): - - Vertical offset: - - - - - Zoom: - - - - - Parameters - - - - - Preview - - - - + Remove Banner Card @@ -9664,216 +13031,266 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre UserContextMenu - + User &details &Détails utilisateur - + Private &chat &Chat privé - + Show this user's &games Voir les parties du &joueur - + + View this user's &public decks + + + + Add to &buddy list Ajouter à la liste d'&amis - + Remove from &buddy list Retirer de la liste d'&amis - + Add to &ignore list Ajouter à la l&iste noire - + Remove from &ignore list Retirer de la l&iste noire - + Kick from &game Exclure de la &partie - + + Report user + + + + Warn user Avertir le joueur - + View user's war&n history Voir l'historique des avertisseme&nts du joueur - + Ban from &server Bannir du &serveur - + View user's &ban history Voir l'historique de &bannissement du joueur - + &Promote user to moderator &Promouvoir d'utilisateur à modérateur - + Dem&ote user from moderator Rétr&ograder de modérateur à utilisateur - + Promote user to &judge Promouvoir utilisateur à &arbitre - + Demote user from judge Destituer arbitre - + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes Voir les notes d'administrateur - - + Investigate user + + + + + + Error Erreur - + This user does not exist. Cet utilisateur n'existe pas. - + You are being ignored by %1 and can't see their games. Vous avez été ignoré par %1 et ne pouvez pas voir ses parties. - + Could not get %1's games. Impossible d'obtenir les parties de %1. - + %1's games Parties de %1 - - - + + + Ban History Historique des bannissements - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Heure de bannissement;Modérateur;Durée de bannissement;Raison du bannissement;Raison visible - + User has never been banned. Le joueur n'a jamais été banni. - + Failed to collect ban information. Impossible de collecter les informations de bannissement. - - - + + + Warning History Historique des avertissements - + Warning Time;Moderator;User Name;Reason Heure d'avertissement;Modérateur;Nom d'utilisateur;Raison - + User has never been warned. Le joueur n'a jamais reçu d'avertissement. - + Failed to collect warning information. Impossible de collecter les informations d'avertissements. - + Failed to get admin notes. Impossible de récupérer les notes d'administrateur. - - + + Success Succès - + Successfully promoted user. L'utilisateur a été promu avec succès. - + Successfully demoted user. L'utilisateur a été rétrogradé avec succès. - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + Kick Player - + Are you sure you want to kick this player from the game? - - - + + + Failed Échec - + Failed to promote user. L'utilisateur n'a pas été promu. - + Failed to demote user. L'utilisateur n'a pas été rétrogradé. - + Copy hash to clipboard Copier le hash - + Remove this user's messages Supprimer les messages de cet utilisateur @@ -9911,61 +13328,61 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre Administrateur - + Moderator Modérateur - + Registered user Joueur enregistré - - + + Unregistered user Joueur non enregistré - + Judge Arbitre - + Unknown Inconnu - + The entered password does not match your account. Le mot de passe est incorrect - - - - + + + + Information Information - + User information updated. Informations de l'utilisateur mises à jour. - - - - - - + + + + + - - - + + + + Error Erreur @@ -9989,283 +13406,292 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre Edit Banner Card + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) %n année.%n années.%n année(s). - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format %10%n jour %20%10%n jours %20%10%n jour(s) %20 - + Enter Password Saisir le mot de passe - + Password verification is required in order to change your email address La vérification du mot de passe est requise pour changer votre adresse e-mail - - - + + + An error occurred while trying to update your user information. Une erreur est survenue lors de la mise à jour de vos informations personnelles. - + The selected card is blacklisted on this server or another error occurred. - + Banner card removed. - + Banner card updated. - + This server does not permit you to update your user informations. Ce serveur ne vous permet pas de mettre à jour vos informations personnelles. - + Password changed. Mot de passe changé. - + This server does not permit you to change your password. Ce serveur ne vous permet pas de changer votre mot de passe. - + The new password is too short. Le nouveau mot de passe est trop court. - + The old password is incorrect. L'ancien mot de passe est incorrect. - + Avatar updated. Avatar mis à jour. - + This server does not permit you to update your avatar. Ce serveur ne vous permet pas de mettre à jour votre avatar. - + An error occured while trying to update your avatar. - - - This server does not permit you to update your user informations. - An error occured while trying to updater your avatar. - Ce serveur ne vous permet pas de mettre à jour vos informations personnelles. - UserInfoPopup - - + + Games - + Chat - + Open private chat - + Profile - + View user profile - + Show this user's games - + + Invite + + + + + Invite to your game + + + + − Buddy - + Remove from buddy list - + + Buddy - + Add to buddy list - + − Ignore - + Remove from ignore list - + + Ignore - + Add to ignore list - + Ban - + Ban from server - + Warn - + Warn user - + Ban log - + View ban history Voir l'historique de bannissements - + Warn log - + View warning history Voir l'historique d'avertissements - + Notes - + View admin notes - + − Mod - + Demote from moderator - + + Mod - + Promote to moderator - + − Judge - + Demote from judge - + + Judge - + Promote to judge - + Join game - + Spectate - - + + Loading games… - + Could not load games. - + No active games. @@ -10273,223 +13699,344 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre UserInterfaceSettingsPage - + General interface settings Paramètres généraux de l'interface - + &Double-click cards to play them (instead of single-click) &Double cliquer sur les cartes pour les jouer (au lieu d'un simple clic) - + &Clicking plays all selected cards (instead of just the clicked card) &Cliquer joue toutes les cartes sélectionnées (à la place de seulement la carte cliquée) - + &Play all nonlands onto the stack (not the battlefield) by default &Jouer toutes les cartes non terrain dans la pile (pas sur le champ de bataille) par défaut - + Do not delete &arrows inside of subphases Ne pas effacer les flèches lors de sous-phases - + Close card view window when last card is removed Fermer la fenêtre d'affichage des cartes lorsque la dernière carte est supprimée - + Auto focus search bar when card view window is opened Focalise automatiquement la barre de recherche lorsque la fenêtre d'affichage des cartes est ouverte - + Annotate card text on tokens Annoter le texte des cartes sur les jetons - + Show selection count during drag selection - + Show total selection count - - Show subtype breakdown in selection tally + + + Disabled - + + + Enabled + + + + + + Automatic + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + Use tear-off menus, allowing right click menus to persist on screen Utiliser les menus détachables, permettant les clics droits sur les rendre persistants - + Keep game chat focused when clicking in game (Note: disables card view search bar) - + Notifications settings Paramètres des notifications - + Enable notifications in taskbar Activer les notifications dans la barre des tâches - + Notify in the taskbar for game events while you are spectating Notifier dans la barre des tâches pour les évènements des parties quand vous y êtes spectateur - + Notify in the taskbar when users in your buddy list connect Notifier dans la barre des tâches quand un ami se connecte - + Animation settings Paramètres des animations - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation &Animation d'engagement / dégagement - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings Paramètres d'éditeur et de stockage de deck - + Open deck in new tab by default Ouvre le deck dans un nouvel onglet par défaut - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby Utiliser le stockage visuel de decks dans le lobby des parties - + Use selection animation for Visual Deck Storage Utilisez l'animation de sélection pour le stockage visuel des deck - + When adding a tag in the visual deck storage to a .txt deck: Lorsque vous ajoutez une étiquette dans le stockage visuel du deck à un deck stocké comme un fichier .txt : - - do nothing - ne rien faire + + Do nothing + - - ask to convert to .cod - demander à convertir en fichier .cod + + Ask to convert to .cod + - - always convert to .cod - toujours convertir en fichier .cod + + Always convert to .cod + - + Default deck editor type Type d'éditeur de deck par défaut - + Classic Deck Editor Éditeur de deck classique - + Visual Deck Editor Éditeur de deck visuel - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings Paramètres du replay - + Buffer time for backwards skip via shortcut: Temps de tampon pour les sauts en arrière via un raccourci : + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 Joueurs connectés au serveur : %1 - + Users in this room: %1 Joueurs dans ce salon : %1 - + Buddies online: %1 / %2 Amis connectés : %1 / %2 - + Ignored users online: %1 / %2 Joueurs sur liste noire connectés : %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters Incrémenter tous les marqueurs de la carte - + &Untap all permanents &Dégager tous les permanents - + R&oll die... L&ancer un dé... - + Flip coin - + &Create token... &Créer un jeton... - + C&reate another token C&réer un autre jeton - + Cr&eate predefined token C&réer un jeton prédéfini @@ -10526,22 +14073,22 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... Filtre de recherche... - + Save Filter Sauvegarder le filtre - + Save all currently applied filters to a file Sauvegarder tous les filtres actuellement appliqués dans un fichier - + Enter filename... Entrer le nom du fichier... @@ -10549,72 +14096,72 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDatabaseDisplayFilterToolbarWidget - + Sort by Trier par - + Filter by Filtrer par - + Save and load filters Sauvegarder et charger les filtres - + Filter by exact card name Filtrer par nom exact de carte - + Filter by card main-type Filtrer par type principal de carte - + Filter by card sub-type Filtrer par sous-type de carte - + Filter by set Filtrer par extension - + Filter by format legality Filtrer par légalité dans le format - + Save/Load Sauvegarder/Charger - + Name Nom - + Main Type Type Principal - + Sub Type Sous-type - + Sets Éditions - + Formats Formats @@ -10716,7 +14263,7 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets Filtrer par extensions récentes @@ -10724,19 +14271,19 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDatabaseDisplaySetFilterWidget - + Search sets... Rechercher les extensions... - - + + Mode: Exact Match Mode : correspondance exacte - - + + Mode: Includes Mode : inclure @@ -10782,28 +14329,28 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDatabaseDisplayWidget - + Search by card name (or search expressions) Rechercher par nom de carte (ou expression de recherche) - - + + Visual Visuel - + Loading database ... Chargement de la base de données... - + Clear all filters Effacer tous les filtres - + Table Tableau @@ -10811,43 +14358,43 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDeckDisplayOptionsWidget - + Group by: Grouper par : - + Change how cards are divided into categories/groups. Configurez comment les cartes sont divisées en groupes/catégories. - + Sort by: Trier par : - + Click and drag to change the sort order within the groups Cliquer et glisser pour changer l'ordre de tri dans les groupes. - + Configure how cards are sorted within their groups Configurez le mode de tri des cartes dans leurs groupes - - + + Toggle Layout: Overlap Mode d'affichage : Superposé - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) Modifier l'affichage des cartes dans les zones (i.e. superposées ou entièrement visibles). - + Toggle Layout: Flat Mode d'affichage : Plat @@ -10863,12 +14410,12 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDeckEditorSampleHandWidget - + Draw a new sample hand Piocher un nouvel exemple de main - + Sample hand size Taille de l'exemple de main @@ -10876,17 +14423,17 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDeckEditorWidget - + Type a card name here for suggestions from the database... Tapez un nom de carte ici pour des suggestions de la base de données... - + Quick search and add card Recherche et ajout rapide d'une carte - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter Recherchez la correspondance la plus proche dans la base de données (avec suggestions automatiques) et ajoutez l'impression souhaitée au deck en appuyant sur Entrée @@ -10894,7 +14441,7 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDeckStorageFolderDisplayWidget - + Deck Storage Stockage de deck @@ -10902,52 +14449,57 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDeckStorageQuickSettingsWidget - + Show Folders Montrer les dossiers - + Show Tag Filter Montrer le filtre à étiquette - + Show Color Identity Montrer l'identité de couleur - + Show Tags On Deck Previews Montrer les étiquettes sur les aperçus de decks - + + Show Upload Time + + + + Show Banner Card Selection Option Montrer l'option de sélection de carte bannière - + Draw unused Color Identities Affiche les identités de couleurs non utilisées - + Unused Color Identities Opacity Opacité des identités de couleur inutilisées - + Deck tooltip: Aide contextuelle du deck : - + None Aucun(e) - + Filepath Chemin de fichier @@ -10955,7 +14507,7 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDeckStorageSearchWidget - + Search by filename (or search expression) Rechercher par chemin de fichier (ou expressions de recherche) @@ -10963,22 +14515,22 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) Trier par ordre alphabétique (nom de deck) - + Sort Alphabetically (Filename) Trier par ordre alphabétique (nom de fichier) - + Sort by Last Modified Trier par date de dernière modification - + Sort by Last Loaded Trier par date de dernier chargement @@ -10986,17 +14538,22 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre VisualDeckStorageWidget - + Loading database ... Chargement de la base de données ... - + Refresh loaded files Recharger les fichiers chargés - + + Select decks to share + + + + Visual Deck Storage Settings Paramètres du stockage visuel des decks @@ -11004,177 +14561,202 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre WarningDialog - + Which warning would you like to send? Quel avertissement voudriez-vous envoyer ? - + Redact all messages from this user in all rooms Censurer tous les messages de cet utilisateur dans toutes les salles - + &OK &OK - + &Cancel &Annuler - + Warn user for misconduct Envoyer un avertissement au joueur pour mauvaise conduite - - + + Error Erreur - + User name to send a warning to can not be blank, please specify a user to warn. Le nom d'utilisateur du joueur à avertir ne peut pas être vide, merci d'entrer un joueur à avertir. - + Warning to use can not be blank, please select a valid warning to send. L'avertissement à utiliser ne peut pas être vide, merci de sélectionner un avertissement valide à envoyer. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top Déplacer l'édition sélectionnée tout en haut - + Move selected set up Déplacer l'édition sélectionnée vers le haut - + Move selected set down Déplacer l'édition sélectionnée vers le bas - + Move selected set to the bottom Déplacer l'édition sélectionnée tout en bas - - Search by set name, code, or type - Rechercher par ensemble de nom, code ou type - - - + Default order Ordre par défaut - + Restore original art priority order Restaurer l'ordre original de priorité artistique - + Enable all sets Activer toutes les éditions - + Disable all sets Désactiver toutes les éditions - + Enable selected set(s) Activer l(es) édition(s) sélectionnée(s) - + Disable selected set(s) Désactiver l(es) éditions(s) sélectionné(s) - + Deck Editor Éditeur de deck - + Use CTRL+A to select all sets in the view. Utiliser Ctrl+A pour sélectionner toutes les éditions dans la vue actuelle. - + Only cards in enabled sets will appear in the card list of the deck editor. Seules les cartes des éditions activées apparaitront dans la liste de cartes de l'éditeur de deck. - + Image priority is decided in the following order: La priorité des images est décidé dans l'ordre suivant: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki D'abord le dossier PERSONNALISÉ (%1), puis les éditions activées dans cette fenêtre (de haut en bas) - + Include cards rebalanced for Alchemy [requires restart] Inclure les cartes rééquilibrées pour Alchemy [nécessite un redémarrage] - + Card Art Images des cartes - + + Search by set name, code, type, or release date + + + + How to use custom card art Comment utiliser une illustration personnalisée de carte - + Hints Astuces - + Note Note - + Sorting by column allows you to find a set while not changing set priority. Trier par colonne vous permet de trouver une édition sans changer la priorité des éditions. - + To enable ordering again, click the column header until this message disappears. Pour trier à nouveau, cliquez sur l'entête de la colonne jusqu'à ce que ce message disparaisse. - + Use the current sorting as the set priority instead Utiliser le tri actuel comme ordre de priorité des éditions - + Sorts the set priority using the same column Sortir la priorité sur la même colonne - + Manage sets Gérer les éditions @@ -11182,93 +14764,85 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre ZoneViewWidget - + Search by card name (or search expressions) Rechercher par nom de carte (ou expression de recherche) - + Ungrouped Non groupé - + Group by Type Grouper par Types - + Group by Mana Value Grouper par Valeur de Mana - + Group by Color Grouper par Couleurs - + Unsorted Non trié - + Sort by Name Trier par Nom - + Sort by Type Trier par Type - + Sort by Mana Cost Trier par Coût de Mana - + Sort by Colors Trier par Couleurs - + Sort by P/T Trier par F/E - + Sort by Set Trier par extension - + shuffle when closing mélanger en quittant - + pile view vue de la pile - - i18n - - - English - Français (French) - - main - + Connect on startup Connecter au démarrage - + Debug to file Débuguer dans journal @@ -12052,6 +15626,16 @@ Merci de ne pas recommencer ou d'autres mesures peuvent être prises contre Graveyard Cimetière + + + Report Queue + + + + + Moderation + + diff --git a/cockatrice/translations/cockatrice_it.ts b/cockatrice/translations/cockatrice_it.ts index e370878b9..6054c5683 100644 --- a/cockatrice/translations/cockatrice_it.ts +++ b/cockatrice/translations/cockatrice_it.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... Imposta &contatore... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Imposta contatore - + New value for counter '%1': Nuovo valore per il contatore '%1': @@ -36,66 +36,115 @@ AbstractTabDeckEditor - + Open in new tab Apri in una nuova scheda - + Are you sure? Sei sicuro? - + The decklist has been modified. Do you want to save the changes? La lista del mazzo è stata modificata. Vuoi salvare i cambiamenti? - - - - - - + + + + + + Error Errore - + Could not open deck at %1 Impossibile aprire il mazzo a %1 - + Could not save remote deck Impossibile salvare mazzo remoto - - + + The deck could not be saved. Please check that the directory is writable and try again. Il mazzo non può essere salvato. Controlla se la cartella è valida e prova ancora. - + Save deck Salva mazzo - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. Il mazzo non può essere salvato. - + There are no cards in your deck to be exported Non ci sono carte da esportare nel tuo mazzo + + AccountSetupPage + + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + AddAnalyticsPanelDialog @@ -107,12 +156,12 @@ Controlla se la cartella è valida e prova ancora. AdminNotesDialog - + Update Notes Aggiorna note - + Admin Notes for %1 Note amministratori su %1 @@ -132,224 +181,310 @@ Controlla se la cartella è valida e prova ancora. Tokens - + Pedine AppearanceSettingsPage - + seconds secondi - + Error Errore - + Could not create themes directory at '%1'. Impossibile creare la cartella dei temi in '%1'. - + Theme settings Impostazioni temi - + Current theme: Tema attuale: - + Open themes folder Apri cartella temi - + Home tab background source: Sorgente dello sfondo della Home: - + Home tab background shuffle frequency: Frequenza di modifica dello sfondo della Home: - + Disabled Disattivato - + Menu settings Impostazioni menù - + Show keyboard shortcuts in right-click menus Mostra scorciatoie da tastiera nel menù del tasto destro del mouse - + Show game filter toolbar above list in room tab Mostra barra di filtro sopra la lista delle partite - + Card rendering Visualizzazione delle carte - + Display card names on cards having a picture Visualizza nome delle carte sopra le immagini - + Auto-Rotate cards with sideways layout Ruota automaticamente carte con disposizione orizzontale - + Override all card art with personal set preference (Pre-ProviderID change behavior) Determina l'illustrazione della carta attraverso la priorità dei set (come prima dell'implementazione del ProviderID) - + Light - + Chiaro - + Dark - + Scuro - + + System + Sistema + + + + Show all playmats - + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + Active theme palette: + Palette del tema attivo: + + + + Active theme style: - + + Qt widget style saved to this theme ("System" lets the application decide) + + + + Edit theme palette - + Modifica palette del tema - + Home tab settings - + Impostazioni tab Home - + Display card name of background in bottom right + Mostra nome della carta in basso a destra sullo sfondo + + + + Dim the home tab background - + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + Styling settings - + Style user list - + Card printings - + Bump sets that the deck contains cards from to the top in the printing selector Nel selettore di stampa, mostra per primi i set delle carte che il mazzo contiene - + Scale cards on mouse over Ingrandisci la carta sotto il mouse - + Use rounded card corners Usa i bordi delle carte arrotondati - + Card layout - + Minimum overlap percentage of cards on the stack and in vertical hand Sovrapposizione % minima delle carte in pila e nella mano verticale: - + Maximum initial height for card view window: Altezza iniziale massima per la finestra di visualizzazione delle carte: - - + + rows file - + Maximum expanded height for card view window: Altezza massima per la finestra di visualizzazione delle carte: - + Card counters Segnalini della carta - + Counter %1 Segnalino %1 - + Hand layout Disposizione della mano - + Display hand horizontally (wastes space) Disponi la mano orizzontalmente (spreca spazio) - + Enable left justification Allinea a sinistra - + Table grid layout Disposizione delle aree di gioco - + Invert vertical coordinate Inverti disposizione verticale - + Minimum player count for multi-column layout: Numero di giocatori minimo per disposizione multicolonna: - + Maximum font size for information displayed on cards: Dimensione massima carattere per le informazioni mostrate sulle carte: @@ -357,12 +492,12 @@ Controlla se la cartella è valida e prova ancora. ArchidektApiResponseDeckDisplayWidget - + Back to results Torna ai risultati - + Open Deck in Deck Editor Apri mazzo nell'editor dei mazzi @@ -388,112 +523,112 @@ Controlla se la cartella è valida e prova ancora. BanDialog - + ban &user name Banna &user name - + ban &IP address Banna indirizzo &IP - + ban client I&D Banna client I&D - + Ban type Tipo di ban - + &permanent ban Ban &permanente - + &temporary ban Ban &temporaneo - + &Days: &Giorni: - + &Hours: &Ore: - + &Minutes: &Minuti: - + Duration of the ban Durata del ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Per favore inserisci la ragione del ban. Questa è visibile solo ai moderatori e non alla persona bannata. - + Please enter the reason for the ban that will be visible to the banned person. Per favore inserisci la ragione del ban che sarà visibile alla persona bannata. - + Redact all messages from this user in all rooms Elimina tutti i messaggi di questo utente in tutte le stanze - + &OK &OK - + &Cancel &Annulla - + Ban user from server Banna utente dal server - - - - + + + + Error Errore - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Seleziona un nome, un IP, un client ID o una combinazione di questi per impostare un ban. - + You must have a value in the name ban when selecting the name ban checkbox. Devi inserire un valore nel campo nome quando il ban per nome è selezionato. - + You must have a value in the ip ban when selecting the ip ban checkbox. Devi inserire un valore nel campo ip quando il ban per ip è selezionato. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Devi inserire un valore nel campo clientid quando il ban per clientid è selezionato. @@ -501,22 +636,22 @@ Questa è visibile solo ai moderatori e non alla persona bannata. BetaReleaseChannel - + Beta Beta - + No reply received from the release update server. Nessuna risposta ricevuta dal server degli aggiornamenti. - + Invalid reply received from the release update server. Risposta non valida ricevuta dal server degli aggiornamenti. - + No reply received from the file update server. Nessuna risposta ricevuta dal server degli aggiornamenti. @@ -524,7 +659,20 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardArtPreviewWidget - + + Banner preview + + + + + + + + Zoom %1× + + + + No card selected @@ -532,22 +680,22 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardArtRulesModel - + Card - + Carta - + ProviderId - + ProviderId - + Mode - + Reason @@ -555,65 +703,209 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardDatabaseModel - + Name Nome - + Sets Set - + Mana cost Costo - + Card type Tipo - + P/T F/C - + Color(s) Colore + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + CardDatabaseView - + Add to Deck - + Aggiungi al mazzo - + Add to Sideboard - + Aggiungi alla Sideboard - + Select Printing - + + Add to Zone + + + + + Create &new zone... + + + + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards @@ -723,22 +1015,22 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardInfoFrameWidget - + Image Immagine - + Description Descrizione - + Both Entrambi - + View transformation Visualizza trasformazione @@ -746,22 +1038,22 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardInfoPictureWidget - + View related cards Guarda carte correlate - + Add card to deck Aggiungi la carta al &mazzo - + Mainboard Mazzo - + Sideboard Sideboard @@ -769,27 +1061,27 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardInfoTextWidget - + Name: Nome: - + Set: Set: - + Collector Number: Numero di collezione: - + Related cards: Carte correlate: - + Unknown card: Carta sconosciuta: @@ -797,129 +1089,129 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardMenu - + Re&veal to... Ri&vela a... - + &All players &Tutti i giocatori - + View related cards Guarda carte correlate - + Token: Pedina: - + All tokens Tutte le pedine - + &Select All &Seleziona tutto - + S&elect Row S&eleziona fila - + S&elect Column S&eleziona colonna - + &Play &Gioca - + &Hide &Nascondi - + Play &Face Down Gioca a &faccia in giù - + &Tap / Untap Turn sideways or back again &TAPpa/STAPpa - + Skip &untapping Salta lo &STAP - + T&urn Over Turn face up/face down Capovolgi - + &Peek at card face &Sbircia la faccia della carta - + &Clone &Copia - + Attac&h to card... Asse&gna alla carta... - + Unattac&h Tog&li - + &Draw arrow... &Disegna una freccia... - + &Set annotation... &Imposta note... - + Reduce life by power - + Ca&rd counters Segnalini delle ca&rte - + &Add counter (%1) &Aggiungi segnalino (%1) - + &Remove counter (%1) &Rimuovi segnalino (%1) - + &Set counters (%1)... Imposta &segnalini (%1)... @@ -940,7 +1232,7 @@ Questa è visibile solo ai moderatori e non alla persona bannata. CardSizeWidget - + Card Size Dimensioni carte @@ -1080,6 +1372,24 @@ Questa è visibile solo ai moderatori e non alla persona bannata. la zona personalizzata '%2' di %1 + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser @@ -1109,280 +1419,382 @@ Questa è visibile solo ai moderatori e non alla persona bannata. + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + ConnectionController - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - - - - - - - - - - - + + + + + + + + + + - - + + + Error - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + Server timeout - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Information - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -1390,113 +1802,113 @@ To update your client, go to Help -> Check for Updates. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - + Requesting forgotten password to %1 as %2... - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. @@ -1546,7 +1958,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorCardDatabaseDockWidget - + Card Database Database carte @@ -1562,17 +1974,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) Cerca per nome della carta (o espressioni di ricerca) - + Add card to &maindeck Aggiungi carta al &mazzo - + Add card to &sideboard Aggiungi carta alla &sideboard @@ -1580,97 +1992,158 @@ To update your client, go to Help -> Check for Updates. DeckEditorDeckDockWidget - + Loading Database... Caricamento database... - + Banner Card Carta copertina - + Main Type Tipo - + Mana Cost Costo di mana - + Colors Colori - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing Seleziona stampa - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck Mazzo - + Deck &name: &Nome mazzo: - + Banner Card/Tags Visibility Settings Impostazioni carta copertina/etichette - + Show banner card selection menu Visualizza menu di selezione carta copertina - + Show tags selection menu Visualizza menu di selezione etichette - + &Comments: &Commenti: - + Group by: Raggruppa per: - + Format: Formato: - + Hash: Hash: - + &Increment number &Aumenta il numero - + &Decrement number &Diminuisci il numero - + &Remove row &Rimuovi carta - + Swap card to/from sideboard Sposta carta in maindeck/sideboard @@ -1678,17 +2151,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorFilterDockWidget - + Filters Filtri - + &Clear all filters &Elimina tutti i filtri - + Delete selected Elimina filtro @@ -1696,114 +2169,119 @@ To update your client, go to Help -> Check for Updates. DeckEditorMenu - + &Deck Editor &Editor dei mazzi - + &New deck &Nuovo mazzo - + &Load deck... &Carica mazzo... - + Load recent deck... Carica mazzo recente... - + Clear Svuota - + &Save deck &Salva mazzo - + Save deck &as... Salva mazzo &con nome... - + + Share deck... + + + + Load deck from cl&ipboard... Carica mazzo dagli app&unti... - + Edit deck in clipboard Modifica mazzo negli appunti - - + + Annotated Con annotazioni - - + + Not Annotated Senza annotazioni - + Save deck to clipboard Salva il mazzo negli appunti - + Annotated (No set info) Con annotazioni (senza info set) - + Not Annotated (No set info) Senza annotazioni (senza info set) - + &Print deck... Stam&pa mazzo... - + Load deck from online service... Carica mazzo dal servizio online... - + &Send deck to online service &Invia mazzo al servizio online - + Create decklist (decklist.org) Crea una decklist (decklist.org) - + Create decklist (decklist.xyz) Crea una decklist (decklist.xyz) - + Analyze deck (deckstats.net) Analizza mazzo (deckstats.net) - + Analyze deck (tappedout.net) Analizza mazzo (tappedout.net) - + &Close &Chiudi @@ -1811,7 +2289,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorPrintingSelectorDockWidget - + Printing Selector Selettore di stampa @@ -1819,110 +2297,152 @@ To update your client, go to Help -> Check for Updates. DeckEditorSettingsPage - - + + Update Spoilers Aggiorna Spoiler - + Success Fatto - + Download URLs have been reset. Gli indirizzi di download sono stati resettati. - + Add URL Aggiungi indirizzo - - + + URL: Indirizzo: - - + + Edit URL Modifica indirizzo: - + Add New URL Aggiungi indirizzo URL - + Remove URL Elimina indirizzo URL - + Updating... Aggiornando... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path Scegli il percorso - + URL Download Priority Ordine di priorità degli indirizzi - + Spoilers Spoiler - + Download Spoilers Automatically Scarica spoiler automaticamente - + Spoiler Location: Indirizzo spoiler: - + Last Change Ultima modifica - + Spoilers download automatically on launch Scarica spoiler automaticamente all'avvio - + Press the button to manually update without relaunching Premi il pulsante per aggiornare manualmente senza riavviare - + Do not close settings until manual update is complete Non chiudere le impostazioni fino a che l'aggiornamento manuale sia completato - + Download card pictures on the fly Scarica immagini delle carte in tempo reale - + How to add a custom URL Come aggiungere indirizzi personalizzati - + Reset Download URLs Resetta indirizzi di download @@ -1930,32 +2450,32 @@ To update your client, go to Help -> Check for Updates. DeckListHistoryManagerWidget - + Undo Annulla - + Redo Ripeti - + Undo/Redo history Annulla/Ripeti Storico - + Click on an entry to revert to that point in the history. Clicca su una riga per ritornare a quel punto nello storico delle modifiche. - + [redo] [ripeti] - + [undo] [annulla] @@ -1963,27 +2483,27 @@ To update your client, go to Help -> Check for Updates. DeckListModel - + Count Quantità - + Set Set - + Number Numero - + Provider ID ID Provider - + Card Carta @@ -2004,17 +2524,22 @@ To update your client, go to Help -> Check for Updates. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match Modalità: Corrispondenza esatta - + Mode: Includes Modalità: Include - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) Filtraggio identità di colore (utilizzabile con connettivi logici AND/OR/NOT) @@ -2022,7 +2547,7 @@ To update your client, go to Help -> Check for Updates. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... Imposta etichette... @@ -2030,62 +2555,62 @@ To update your client, go to Help -> Check for Updates. DeckPreviewTagDialog - + Deck Tags Manager Gestore etichette del mazzo - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: Gestisci le etichette del mazzo. Assegna o rimuovi etichette, oppure creane di nuove. - + Add a new tag (e.g., Aggro️) Aggiungi una nuova etichetta (Es.: Aggro️) - + Add Tag Aggiungi etichetta - + Filter tags... Filtra etichette... - + OK Conferma - + Edit default tags Modifica etichette - + Cancel Annulla - + Invalid Input Input non valido - + Tag name cannot be empty! Il nome dell'etichetta non può essere vuoto! - + Duplicate Tag Duplica Etichetta - + This tag already exists. Questa etichetta esiste già. @@ -2093,94 +2618,109 @@ To update your client, go to Help -> Check for Updates. DeckPreviewWidget - + Banner Card Carta copertina - + Open in deck editor Apri nell'editor dei mazzi - + + Share deck... + + + + Edit Tags Modifica etichette - + Rename Deck Rinomina mazzo - + Save Deck to Clipboard Salva il mazzo negli appunti - + Annotated Con annotazioni - + Annotated (No set info) Con annotazioni (senza info set) - + Not Annotated Senza annotazioni - + Not Annotated (No set info) Senza annotazioni (senza info set) - + Rename File Rinomina file - + Delete File Elimina file - + Set Banner Card Imposta carta copertina - - + + New name: Nuovo nome: - - + + Rename deck + + + + + Rename file + + + + + Error Errore - + Rename failed Rinomina non riuscita - + Delete file Elimina file - + Are you sure you want to delete the selected file? Vuoi davvero eliminare il file selezionato? - + Delete failed Eliminazione non riuscita @@ -2188,57 +2728,102 @@ To update your client, go to Help -> Check for Updates. DeckStateManager - + Rename deck to "%1" from "%2" Rinominato mazzo in "%1" da "%2" - + Updated comments (was %1 chars, now %2 chars) Commenti aggiornati (prima %1 caratteri, ora %2 caratteri) - + Set banner card to %1 (%2) Impostata carta copertina a %1 (%2) - + + Set playmat to %1 + + + + Tags changed Etichette modificate - + Set format to %1 Formato impostato a %1 - + Added (%1): %2 (%3) %4 Aggiunto (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) Spostato in %1 1 × "%2" (%3) - + Removed "%1" (all copies) Rimosso "%1" (tutte le copie) - + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + %1 1 × "%2" (%3) %1 1 × "%2" (%3) - + Added Aggiunto - + Removed Rimosso @@ -2260,88 +2845,116 @@ To update your client, go to Help -> Check for Updates. DeckViewContainer - + Load deck... Carica mazzo... - + Load remote deck... Carica mazzo remoto... - + Load from clipboard... Carica dagli appunti... - + Load from website... Carica dal sito... - + Unload deck Deseleziona mazzo - + Ready to start Pronto ad iniziare - + Force start Forza avvio - + Sideboard unlocked Sideboard sbloccata - + Sideboard locked Sideboard bloccata - - + + Error Errore - + The selected file could not be loaded. I file selezionati non posso essere caricati. - + Deck is greater than maximum file size. Il mazzo è più grande della dimensione massima del file consentita. - + Are you sure you want to force start? This will kick all non-ready players from the game. Sicuro di voler forzare l'avvio? Ciò espellerà dalla partita tutti i giocatori che non sono pronti. - + Cockatrice Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion Conversione formato mazzo - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? @@ -2350,7 +2963,7 @@ Le etichette possono essere aggiunte solo a mazzi in formato .cod. Vuoi convertire il mazzo al formato .cod? - + Remember and automatically apply choice in the future Ricorda e applica automaticamente la scelta in futuro @@ -2363,143 +2976,143 @@ Vuoi convertire il mazzo al formato .cod? Scaricamento... - + Known Hosts Host Conosciuti - + Delete the currently selected saved server Elimina il server salvato attualmente selezionato - + Refresh the server list with known public servers Aggiorna la lista dei server con server pubblici - + New Host Nuovo indirizzo - + Name: Nome: - + &Host: &Host: - + &Port: &Porta: - + Player &name: &Nome giocatore: - + P&assword: P&assword: - + &Save password &Salva password - + A&uto connect Connetti a&utomaticamente - + Automatically connect to the most recent login when Cockatrice opens Connetti automaticamente all'ultimo server utilizzato all'avvio di Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! Se hai problemi a connetterti o registrarti contatta lo staff del server per aiuto! - - + + Webpage Sito web - + Reset Password Resetta Password - + Forgot password? Hai dimenticato la password? - + &Connect &Connetti - + Server Server - + Login Login - + Server Contact Contatto server - + Connect to Server Connetti al server - + Server URL URL Server - + Communication Port Numero porta - + Unique Server Name Nome univoco del server - + Connection Warning Avviso di connessione - + You need to name your new connection profile. È necessario impostare un nome per il nuovo profilo di connessione. - + Connect Warning Problema di connessione - + The player name can't be empty. Il nome del giocatore non può essere vuoto. @@ -2507,122 +3120,122 @@ Vuoi convertire il mazzo al formato .cod? DlgCreateGame - + Re&member settings Ricorda impostazioni - + &Description: &Descrizione: - + P&layers: G&iocatori: - + General Generali - + Game type Formato di gioco - + &Password: &Password: - + Only &buddies can join Solo gli &amici possono entrare - + Only &registered users can join Solo &utenti registrati possono entrare - + Joining restrictions Restrizioni d'ingresso - + &Spectators can watch &Gli spettatori possono osservare - + Spectators &need a password to watch Gli spettatori &necessitano di password - + Spectators can &chat Gli spettatori possono &chattare - + Spectators can see &hands Gli spettatori vedono le mani - + Create game as spectator Crea partita come spettatore - + Spectators Spettatori - + Starting life total: Punti vita iniziali: - + Open decklists in lobby Apri mazzi nella lobby - + Create game as judge Crea partita come arbitro - + Game setup options Configurazione partita - + &Clear Pulisci - + Create game Crea partita - + Game information Informazioni partita - + Error Errore - + Server error. Errore del server. @@ -2630,97 +3243,97 @@ Vuoi convertire il mazzo al formato .cod? DlgCreateToken - + &Name: &Nome: - + Token Pedina - + C&olor: C&olore: - + white bianco - + blue blu - + black nero - + red rosso - + green verde - + multicolor multicolore - + colorless incolore - + &P/T: &F/C: - + &Annotation: &Note: - + &Destroy token when it leaves the table &Elimina la pedina quando lascia il campo - + Create face-down (Only hides name) Crea a faccia in giù (nasconde solo il nome) - + Token data Dati pedina - + Show &all tokens Mostra &tutte le pedine - + Show tokens from this &deck Mostra pedine di questo &mazzo - + Choose token from list Scegli pedina dalla lista - + Create token Crea pedina @@ -2728,53 +3341,53 @@ Vuoi convertire il mazzo al formato .cod? DlgDefaultTagsEditor - + Edit Tags Modifica etichette - + Add Aggiungi - + Confirm Conferma - + Cancel Annulla - + Enter a tag and press Enter Scrivi il nome dell'etichetta e premi Invio - - + + ✖ ✖ - + Invalid Input Input non valido - + Tag name cannot be empty! Il nome dell'etichetta non può essere vuoto! - + Duplicate Tag Duplica Etichetta - + This tag already exists. Questa etichetta esiste già. @@ -2783,7 +3396,7 @@ Vuoi convertire il mazzo al formato .cod? DlgEditAvatar - + No image chosen. Nessuna immagine selezionata. @@ -2795,27 +3408,32 @@ To remove your current avatar, confirm without choosing a new image. Per rimuovere il tuo avatar attuale, conferma senza scegliere una nuova immagine. - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... Cerca... - + Change avatar Cambia avatar - + Open Image Apri immagine - + Image Files (*.png *.jpg *.bmp) File immagine (*.png *.jpg *.bmp) - + Invalid image chosen. Immagine selezionata non valida. @@ -2841,38 +3459,38 @@ Per rimuovere il tuo avatar attuale, conferma senza scegliere una nuova immagine DlgEditPassword - + Old password: Vecchia password: - + New password: Nuova password: - + Confirm new password: Conferma nuova password: - + Change password Cambia password - - + + Error Errore - + Your password is too short. La nuova password è troppo corta. - + The new passwords don't match. Le password non coincidono. @@ -2880,93 +3498,93 @@ Per rimuovere il tuo avatar attuale, conferma senza scegliere una nuova immagine DlgEditTokens - + &Name: &Nome: - + C&olor: C&olore: - + white bianco - + blue blu - + black nero - + red rosso - + green verde - + multicolor multicolore - + colorless incolore - + &P/T: &F/C: - + &Annotation: &Note: - + Token data Dati pedina - - + + Add token Aggiungi pedina - + Remove token Rimuovi pedina - + Edit custom tokens Modifica pedine personalizzate - + Please enter the name of the token: Inserisci il nome della pedina: - + Error Errore - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Il nome scelto è in conflitto con una carta o pedina esistente. @@ -3045,8 +3663,9 @@ Assicurati di abilitare il set "Pedine" nella finestra "Gestisci - Hide games that have started - Nascondi partite iniziate + Hide started games + Hide games that have started + @@ -3081,8 +3700,9 @@ Assicurati di abilitare il set "Pedine" nella finestra "Gestisci - &Creator name: - Nome &creatore: + &Host name: + &Creator name: + @@ -3148,52 +3768,52 @@ Assicurati di abilitare il set "Pedine" nella finestra "Gestisci DlgForgotPasswordChallenge - + Reset Password Challenge Warning Avviso di reimpostazione della password di verifica della sfida - + A problem has occurred. Please try to request a new password again. Si è verificato un problema. Prova a richiedere di nuovo una nuova password. - + Enter the information of the server and the account you'd like to request a new password for. Inserisci le informazioni del server e dell'account per cui desideri richiedere una nuova password. - + &Host: &Host: - + &Port: &Porta: - + Player &name: &Nome giocatore: - + Email: Email: - + Reset Password Challenge Reimposta password sfida - + Reset Password Challenge Error Errore di reimpostazione della password di verifica della sfida - + The email address can't be empty. L'indirizzo email non può essere vuoto. @@ -3201,37 +3821,37 @@ Assicurati di abilitare il set "Pedine" nella finestra "Gestisci DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. Inserisci le informazioni del server per il quale desideri richiedere una nuova password. - + &Host: &Host: - + &Port: &Porta: - + Player &name: &Nome giocatore: - + Reset Password Request Richiesta di reimpostazione della password - + Reset Password Error Errore di ripristino della password - + The player name can't be empty. Il nome del giocatore non può essere vuoto. @@ -3239,94 +3859,127 @@ Assicurati di abilitare il set "Pedine" nella finestra "Gestisci DlgForgotPasswordReset - + Reset Password Warning Avviso di reimpostazione della password - + A problem has occurred. Please try to request a new password again. Si è verificato un problema. Prova a richiedere di nuovo una nuova password. - + Enter the received token and the new password in order to set your new password. Inserisci il token ricevuto e la nuova password per impostare la tua nuova password. - + &Host: &Host: - + &Port: &Porta: - + Player &name: &Nome giocatore: - + Token: Token: - - + + New Password: Nuova password: - + Reset Password Resetta la password - + The player name can't be empty. Il nome del giocatore non può essere vuoto. - - - - + + + + Reset Password Error Errore di ripristino della password - + The token can't be empty. Il token non può essere vuoto. - + The new password can't be empty. La nuova password non può essere vuota. - + Error Errore - + Your password is too short. La tua password è troppo corta. - + The passwords do not match. Le password non coincidono. + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck Carica mazzo @@ -3418,166 +4071,567 @@ https://tappedout.net/mtg-decks/nome-del-tuo-mazzo/ DlgLocalGameOptions - + Players: Giocatori: - + General Generale - + Starting life total: Punti vita iniziali: - + Game setup options Configurazione partita - + Remember settings Ricorda impostazioni - + Local game options Configurazione partita locale + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): Nome della carta (o ricerca espressioni): - + Number of hits: Numero di risultati: - + Auto play hits Riproduzione automatica dei successi - + Put top cards on stack until... Metti le carte in cima alla pila fino a... - + No cards matching the search expression exists in the card database. Proceed anyways? Non esiste alcuna carta corrispondente agli estremi di ricerca nel database. Procedere comunque? - + Cockatrice Cockatrice - + Invalid filter Filtro non valido + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. Inserisci le tue informazioni e le informazioni del server a cui desideri registrarti. La tua email verrà utilizzata per verificare il tuo account. - + &Host: &Host: - + &Port: &Porta: - + Player &name: &Nome giocatore: - + P&assword: P&assword: - + Password (again): Password (conferma): - + Email: Email: - + Email (again): Email (conferma): - + Country: Stato: - + Undefined Non definito - + Real name: Nome reale: - + Register to server Registrati sul server - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning Avviso Registrazione - + Your password is too short. La tua password è troppo corta. - + Your passwords do not match, please try again. Le password non combaciano, riprova. - + Your email addresses do not match, please try again. Gli indirizzi email non combaciano, riprova. - + The player name can't be empty. Il nome del giocatore non può essere vuoto. + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice @@ -3643,46 +4697,12 @@ La tua email verrà utilizzata per verificare il tuo account. DlgSettings - + Unknown Error loading card database Errore sconosciuto durante il caricamento del database delle carte - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - Il tuo database delle carte non è valido. - -Cockatrice non può funzionare correttamente con un database delle carte non valido. - -Ti consigliamo di avviare oracle per aggiornare il tuo database delle carte. - -Vuoi modificare le impostazioni della posizione del database della carte? - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - Il tuo database delle carte è troppo vecchio. - -Questo può creare problemi nel caricamento dell informazioni e delle immagini delle carte. - -Ti consigliamo di avviare oracle per aggiornare il tuo database delle carte. - -Vuoi modificare le impostazioni della posizione del database della carte? - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3694,7 +4714,7 @@ Per favore crea un ticket di assistenza su https://github.com/Cockatrice/Cockatr Desideri modificare l'impostazione della posizione del database? - + File Error loading your card database. Would you like to change your database location setting? @@ -3703,7 +4723,7 @@ Would you like to change your database location setting? Vuoi modificare le impostazioni della posizione del database della carte? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3712,7 +4732,7 @@ Would you like to change your database location setting? Vuoi modificare le impostazioni della posizione del database della carte? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3725,77 +4745,208 @@ https://github.com/Cockatrice/Cockatrice/issues Desideri modificare l'impostazione della posizione del database? - - - + + + Error Errore - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Il percorso della cartella del mazzo non è valido. Vuoi tornare in dietro e impostare il percorso corretto? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Il percorso della cartella delle immagini delle carte è invilido. Vuoi tornare indietro e impostare il percorso corretto? - + Settings Impostazioni - + + Search settings... + + + + + OK + + + + General Generale - + Appearance Aspetto - + User Interface Interfaccia - + Storage - + Card Sources Immagini carte - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat Chat - + Sound Suoni - + Shortcuts Scorciatoie + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check Controllo aggiornamenti carte - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. @@ -3804,27 +4955,27 @@ Seleziona la modalità di esecuzione dell'aggiornamento. Potrai comunque cambiarla in seguito dalle impostazioni, nella scheda 'Generale'. - + Run in foreground Esegui in primo piano - + Run in background Esegui in background - + Run in background and always from now on Esegui in background da ora in poi - + Don't prompt again and don't run Non eseguire e non chiedere più - + Don't run this time Non eseguire stavolta @@ -3832,17 +4983,17 @@ Potrai comunque cambiarla in seguito dalle impostazioni, nella scheda 'Gene DlgTipOfTheDay - + Next Prossimo - + Previous Precedente - + Tip of the Day Suggerimento del giorno @@ -3850,40 +5001,40 @@ Potrai comunque cambiarla in seguito dalle impostazioni, nella scheda 'Gene DlgUpdate - + Current release channel Canale della versione corrente - + Reinstall Reinstalla - + Cancel Download Annulla download - + Open Download Page Apri pagina di download - + Check for Client Updates Verifica la presenza di aggiornamenti del client - - - - + + + + Error Errore - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice non è stato creato con il supporto SSL, quindi gli aggiornamenti non possono essere scaricati automaticamente! @@ -3891,126 +5042,136 @@ Please visit the download page to update manually. Per favore, visitate la pagina di download per aggiornare manualmente. - + Downloading update: %1 Download dell'aggiornamento: %1 - + Checking for updates... Ricerca aggiornamenti... - + Finished checking for updates Controllo aggiornamenti terminato - + No Update Available Nessun aggiornamento disponibile - + Cockatrice is up to date! Cockatrice è aggiornato! - + You are already running the latest version available in the chosen release channel. Hai già la versione più recente nel canale della versione scelto. - + Current version Versione corrente - + Selected release channel Canale della versione selezionato - - + + Update Available Aggiornamento disponibile - - + + A new version of Cockatrice is available! Una nuova versione di Cockatrice è disponibile! - - + + New version Nuova versione - - + + Released Data di rilascio - - + + Changelog Changelog - + Do you want to update now? Vuoi aggiornare adesso? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. Purtroppo l'aggiornamento automatico non è riuscito a trovare una versione compatibile. Dovrai scaricare la nuova versione manualmente. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. Per favore, controlla la <a href="%1">pagina delle release</a> sul nostro Github e scarica la versione giusta per il tuo sistema. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error Errore durante l'aggiornamento - + An error occurred while checking for updates: Errore durante il controllo degli aggiornamenti: - + An error occurred while downloading an update: Errore durante il download di un aggiornamento: - + Installing... Installazione... - + Cockatrice is unable to open the installer. Cockatrice non riesce ad aprire il file di installazione. - + Try to update manually by closing Cockatrice and running the installer. Prova ad aggiornare manualmente chiudendo Cockatrice ed eseguento il file di installazione. - + Download location Percorso download @@ -4018,17 +5179,17 @@ Dovrai scaricare la nuova versione manualmente. DlgViewLog - + Clear log when closing Pulisci la cronologia al riavvio - + Copy to clipboard Copia negli appunti - + Debug Log Registro di debug @@ -4266,7 +5427,7 @@ Dovrai scaricare la nuova versione manualmente. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: Sale: @@ -4274,7 +5435,7 @@ Dovrai scaricare la nuova versione manualmente. FilterBuilder - + Type your filter here Scrivi il filtro qui @@ -4282,45 +5443,88 @@ Dovrai scaricare la nuova versione manualmente. FilterDisplayWidget - + Confirm Delete Conferma eliminazione - + Are you sure you want to delete the filter '%1'? Vuoi davvero eliminare il filtro '%1'? - + Delete Failed Eliminazione non riuscita - + Failed to delete filter '%1'. Impossibile eliminare il filtro '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator espulso da proprietario del gioco o moderatore - + player left the game il giocatore ha lasciato la partita - + player disconnected from server il giocatore si è disconnesso dal server - + reason unknown ragione sconosciuta @@ -4328,140 +5532,156 @@ Dovrai scaricare la nuova versione manualmente. GameSelector - - - - - - - - - + + + + + + + + + Error Errore - + Please join the appropriate room first. Si prega di entrare prima in una stanza adeguata. - + Wrong password. Password errata. - + Spectators are not allowed in this game. Spettatori non ammessi in questa partita. - + The game is already full. La partita è piena. - + The game does not exist any more. Questa partita non esiste più. - + This game is only open to registered users. Questa partita è solo per utenti registrati. - + This game is only open to its creator's buddies. Questa stanza è aperta solo agli amici del suo creatore. - + You are being ignored by the creator of this game. Sei stato ingnorato dal creatore di questa partita. - + Join Game Entra nella partita - + Spectate Game Osserva partita - + Game Information Informazioni partita - + + Cop&y game link + + + + Join Game as Judge Unisciti a una partita come arbitro - + Spectate Game as Judge Osserva partita come arbitro - + + Join game Entra nella partita - - Password: - Password: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. Si prega di entrare prima nella rispettiva stanza. - + &Filter games &Filtra partite - + C&lear filter E&limina filtri - + C&reate Cr&ea - + &Join &Entra - + Join as judge Unisciti come arbitro - + J&oin as spectator Entra c&ome spettatore - + Join as judge spectator Unisciti come arbitro spettatore - + Games shown: %1 / %2 Partite mostrate: %1 / %2 - + Games Partite @@ -4502,105 +5722,105 @@ Dovrai scaricare la nuova versione manualmente. GamesModel - + >1 day >1 giorno - + %1%2 hr short age in hours %1%2 ora%1%2 ore%1%2 ora(e) - + new nuovo - + %1%2 min short age in minutes %1%2 min%1%2 min%1%2 min - + password password - + buddies only solo amici - + reg. users only solo utenti registrati - + open decklists Apri impostazioni - - + + can chat può chattare - + see hands vede mani - + can see hands può vedere mani - + not allowed non ammessi - + Room Stanza - + Age Età - + Description Descrizione - - Creator - Creatore + + Host + - + Type Tipo - + Restrictions Restrizioni - + Players Giocatori - + Spectators Spettatori @@ -4608,158 +5828,275 @@ Dovrai scaricare la nuova versione manualmente. GeneralSettingsPage - + Reset all paths Reimposta tutti i percorsi - + All paths have been reset I percorsi sono stati resettati - - - - - - - + + + + English + Italiano (Italian) + + + + + + + + + Choose path Seleziona il percorso - - Language settings + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> - + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + + Language settings + Impostazioni lingua + + + Language: Lingua: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + Version settings - + Card database - + Startup settings - + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) Destinazioni (non si può personalizzare in modalità portatile) - + Paths Percorsi - + How to help with translations Come aiutare con le traduzioni - + Decks directory: Cartella mazzi: - + Filters directory: Cartella filtri: - + Replays directory: Cartella replay: - + Pictures directory: Cartella immagini: - + Card database: Database carte: - + Custom database directory: Cartella database personalizzata: - + Token database: Database pedine: - + Update channel Canale di aggiornamento: - + Check for client updates on startup Controlla gli aggiornamenti del client all'avvio - + Check for card database updates on startup Controlla gli aggiornamenti delle carte all'avvio - + Don't check Non controllare - + Prompt for update Chiedi se aggiornare - + Always update in the background Aggiorna sempre in background - + Check for card database updates every Controlla gli aggiornamenti delle carte ogni - + days giorni - + Notify if a feature supported by the server is missing in my client Avvisami se una funzionalità supportata dal server manca nel mio programma - + Automatically run Oracle when running a new version of Cockatrice Avvia automaticamente Oracle se Cockatrice è stato aggiornato - + Show tips on startup Mostra suggerimenti all'avvio - + Last update check on %1 (%2 days ago) Ultimo controllo %1 (%2 giorni fa) @@ -4767,47 +6104,47 @@ Dovrai scaricare la nuova versione manualmente. GraveyardMenu - + &Graveyard &Cimitero - + &View graveyard &Guarda il cimitero - + &Move graveyard to... &Muovi cimitero in... - + &Top of library &Cima al grimorio - + &Bottom of library &Fondo al grimorio - + &All players &Tutti i giocatori - + &Hand &Mano - + &Exile &Esilio - + Reveal random card to... Rivela carta casuale a... @@ -4901,59 +6238,353 @@ Dovrai scaricare la nuova versione manualmente. Rivela carta c&asuale a... + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck Crea un nuovo mazzo - + Browse Decks Esplora i mazzi - + Browse Card Database Esplora il database delle carte - + Browse EDHRec Esplora EDHRec - + Browse Archidekt Esplora Archidekt - + View Replays Guarda i replay - + Quit Esci - + Connecting... Connessione in corso... - + Connect Connetti - + Play Gioca + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu @@ -5171,273 +6802,294 @@ Dovrai scaricare la nuova versione manualmente. MainWindow - - + + Player %1 Giocatore %1 - + Load replay Carica replay - + About Cockatrice Info su Cockatrice - + Version Versione - + Cockatrice Webpage Sito di Cockatrice - + Project Manager: Manager del progetto: - + Past Project Managers: Manager precedenti: - + Developers: Sviluppatori: - + Our Developers I nostri sviluppatori - + Help Develop! Aiutaci nello sviluppo! - + Translators: Traduttori: - + Our Translators I nostri traduttori - + Help Translate! Aiutaci nella traduzione! - + Support: Supporto: - + Report an Issue Segnala un problema - + Troubleshooting Risoluzione dei problemi - + F.A.Q. F.A.Q. - - + + Error Errore - + &Connect... &Connetti... - + &Disconnect &Disconnetti - + Start &local game... Inizia partita in &locale... - + &Watch replay... &Guarda replay... - + &Full screen &Schermo intero - + &Register to server... &Registrati sul server... - + &Restore password... Recupera &password... - + &Settings... &Impostazioni... - + &Exit &Esci - + A&ctions A&zioni - + &Cockatrice &Cockatrice - + C&ard Database &Database carte - + &Manage sets... &Gestisci Espansioni... - + Edit custom &tokens... Modifica pedine personalizzate - + Open custom image folder Apri cartella immagini personalizzate - + Open custom sets folder Apri cartella set personalizzati - + Add custom sets/cards Aggiungi set di carte personalizzato - + Reload card database Ricarica database carte - + Tabs &Schede - + &Help &Aiuto - + &About Cockatrice &Info su Cockatrice - + &Tip of the Day Suggerimento del giorno - + Check for Client Updates Controlla aggiornamenti client - + Check for Card Updates... Controlla aggiornamenti carte... - + Check for Card Updates (Automatic) Controlla aggiornamenti carte in background - + Show Status Bar Mostra barra di stato - + View &Debug Log Vedi Registro di &debug - + Open Settings Folder Apri cartella impostazioni - + + Re-run Onboarding Wizard... + + + + Show/Hide Mostra/Nascondi - + + Sign-in cancelled + + + + New Version Nuova versione - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Congratulazioni per aver aggiornato Cockatrice alla versione %1! Oracle verrà avviato per aggiornare anche il database delle carte. - + Cockatrice installed Cockatrice installata - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Congratulazioni per aver installato Cockatrice %1! Oracle verrà avviato per installare il database delle carte. - + + Are you sure? + Sei sicuro? + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database Database carte - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5446,29 +7098,29 @@ Vuoi provare ad aggiornare il database adesso? Se è la prima volta che usi Cockatrice o non sei sicuro, scegli "Sì" - - + + Yes Sì - - + + No No - + Open settings Apri impostazioni - + New sets found Nuovi set trovati - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5481,22 +7133,22 @@ Codici dei set: %1 Desideri attivarli? - + Yes, always enable - + View sets Vedi set - + Welcome Benvenuto - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5505,90 +7157,90 @@ Tutti i set nell'archivio delle carte sono stati abilitati. Scopri metodi alternativi per visualizzare i set o disabilitare set ed effetti nella finestra "Gestisci Espansioni". - + Information Informazione - + A card database update is already running. L'aggiornamento delle carte è già in corso. - + Unable to run the card database updater: Impossibile avviare l'aggiornamento delle carte: - + Card database update running. Aggiornamento delle carte in corso. - + Failed to start. The file might be missing, or permissions might be incorrect. Avvio non riuscito. Il file potrebbe essere assente, o i permessi errati. - + The process crashed some time after starting successfully. Il processo si è arrestato in modo anomalo poco dopo essersi avviato con successo. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. Richiesta scaduta. Il processo ci ha messo troppo a rispondere. L'ultima funzione waitFor...() è scaduta. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. Si è verificato un errore nel cercare di scrivere al processo. Ad esempio, il processo potrebbe non essere in esecuzione, o potrebbe aver chiuso il suo canale di input. - + An error occurred when attempting to read from the process. For example, the process may not be running. Si è verificato un errore nel cercare di leggere dal processo. Ad esempio, il processo potrebbe non essere in esecuzione. - + Unknown error occurred. Si è verificato un errore sconosciuto. - + The card database updater exited with an error: %1 L'aggiornamento del database delle carte è terminato con un errore: %1 - - - - - + + + + + Load sets/cards Carica set di carte - + Selected file cannot be found. Il file selezionato non può essere caricato. - + You can only import XML databases at this time. E' possibile importare solamente database XML. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Il nuovo set di carte è stato aggiunto correttamente. Il database delle carte verrà ricaricato. - + Sets/cards failed to import. Il nuovo set di carte non è stato importato a causa di un errore. @@ -5793,615 +7445,615 @@ Il database delle carte verrà ricaricato. MessageLogWidget - + from play dal campo di battaglia - + from their graveyard dal suo cimitero - + from exile dall'esilio - + from their hand dalla sua mano - + the top card of %1's library la prima carta del grimorio di %1 - + the top card of their library la prima carta del suo grimorio - + from the top of %1's library dalla cima del grimorio di %1 - + from the top of their library dalla cima del suo grimorio - + the bottom card of %1's library l'ultima carta del grimorio di %1 - + the bottom card of their library l'ultima carta del suo grimorio - + from the bottom of %1's library dal fondo del grimorio di %1 - + from the bottom of their library dal fondo del suo grimorio - + from %1's library dal grimorio di %1 - + from their library dal suo grimorio - + from sideboard dalla sideboard - + from the stack dalla pila - + from custom zone '%1' dalla zona personalizzata '%1' - + %1 is now keeping the top card %2 revealed. %1 sta tenendo la prima carta %2 rivelata. - + %1 is not revealing the top card %2 any longer. %1 non sta più rivelando la prima carta %2. - + %1 can now look at top card %2 at any time. %1 può guardare la prima carta %2 della libreria in qualunque momento. - + %1 no longer can look at top card %2 at any time. %1 non può più guardare la prima carta %2 del mazzo in qualunque momento. - + %1 attaches %2 to %3's %4. %1 assegna %2 a %4 di %3. - + %1 has conceded the game. %1 ha concesso la partita. - + %1 has unconceded the game. %1 ha annullato la concessione della partita. - + %1 has restored connection to the game. %1 ha ripristinato il collegamento alla partita. - + %1 has lost connection to the game. %1 ha perso il collegamento alla partita. - + %1 points from their %2 to themselves. %1 disegna una freccia dal suo %2 a sé stesso. - + %1 points from their %2 to %3. %1 disegna una freccia dal suo %2 a %3. - + %1 points from %2's %3 to themselves. %1 disegna una freccia dal %3 di %2 a sé stesso. - + %1 points from %2's %3 to %4. %1 disegna una freccia dal %3 di %2 a %4. - + %1 points from their %2 to their %3. %1 disegna una freccia dal suo %2 al suo %3. - + %1 points from their %2 to %3's %4. %1 disegna una freccia dal suo %2 a %4 di %3. - + %1 points from %2's %3 to their own %4. %1 disegna una freccia da %3 di %2 al suo %4. - + %1 points from %2's %3 to %4's %5. %1 disegna una freccia dal %3 di %2 al %5 di %4. - + %1 creates a face down token. %1 crea una pedina a faccia in giù. - + %1 creates token: %2%3. %1 crea una pedina: %2%3. - + %1 has loaded a deck (%2). %1 ha caricato un mazzo (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 ha caricato un mazzo con %2 carte nella sideboard (%3). - + %1 destroys %2. %1 distrugge %2. - + a card una carta - + %1 gives %2 control over %3. %1 da il controllo di %3 a %2. - + %1 puts %2 into play%3 face down. %1 mette %2 sul campo di battaglia %3 a faccia in giù. - + %1 puts %2 into play%3. %1 mette %2 sul campo di battaglia%3. - + %1 puts %2%3 into their graveyard face down. %1 mette %2%3 nel suo cimitero a faccia in giù. - + %1 puts %2%3 into their graveyard. %1 mette %2 nel suo cimitero%3. - + %1 exiles %2%3 face down. %1 esilia %2%3 a faccia in giù. - + %1 exiles %2%3. %1 esilia %2%3. - + %1 moves %2%3 to their hand. %1 mette %2 in mano%3. - + %1 puts %2%3 into their library. %1 mette %2 nel suo grimorio%3. - + %1 puts %2%3 onto the bottom of their library. %1 mette %2%3 in fondo al proprio grimorio. - + %1 puts %2%3 on top of their library. %1 mette %2 in cima al suo grimorio%3. - + %1 puts %2%3 into their library %4 cards from the top. %1 mette %2%3 nel suo grimorio %4 carte dalla cima. - + %1 moves %2%3 to sideboard. %1 mette %2 nella sideboard%3. - + %1 plays %2%3 face down. %1 gioca %2%3 a faccia in giù. - + %1 plays %2%3. %1 gioca %2%3. - + %1 moves %2%3 to custom zone '%4' face down. %1 mette %2 nella zona personalizzata '%4'%3 a faccia in giù. - + %1 moves %2%3 to custom zone '%4'. %1 mette %2 nella zona personalizzata '%4'%3. - + %1 tries to draw from an empty library %1 prova a pescare da un grimorio vuoto - + %1 draws %2 card(s). %1 pesca una carta.%1 pesca %2 carte.%1 pesca %2 carte. - + %1 is looking at %2. %1 sta guardando %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 sta guardando %3 carta %4 %2.%1 sta guardando %3 carte %4 %2.%1 sta guardando %3 carte %4 %2. - + bottom in fondo - + top in cima - + %1 turns %2 face-down. %1 gira %2 a faccia in giù. - + %1 turns %2 face-up. %1 gira %2 a faccia in su. - + The game has been closed. La partita è stata chiusa. - + The game has started. La partita è iniziata. - + You are flooding the game. Please wait a couple of seconds. Stai spammando la partita. Attendi un paio di secondi. - + %1 has joined the game. %1 è entrato nella partita. - + %1 is now watching the game. %1 sta osservando la partita. - + You have been kicked out of the game. Sei stato cacciato dalla partita. - + %1 has left the game (%2). %1 ha abbandonato la partita (%2). - + %1 is not watching the game any more (%2). %1 non sta più guardando la partita (%2). - + %1 is not ready to start the game any more. %1 non è più pronto a iniziare la partita. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 mischia il proprio mazzo e pesca una nuova mano di %2 carta.%1 mischia il proprio mazzo e pesca una nuova mano di %2 carte.%1 mischia il proprio mazzo e pesca una nuova mano di %2 carte. - + %1 shuffles their deck and draws a new hand. %1 mischia il proprio mazzo e pesca una nuova mano. - + You are watching a replay of game #%1. Stai guardando il replay della partita #%1. - + %1 is ready to start the game. %1 è pronto a iniziare la partita. - + cards an unknown amount of cards carte - + %1 card(s) a card for singular, %1 cards for plural una carta%1 carte%1 carte - + %1 lends %2 to %3. %1 presta %2 a %3. - + %1 reveals %2 to %3. %1 rivela %2 a %3. - + %1 reveals %2. %1 rivela %2. - + %1 randomly reveals %2%3 to %4. %1 rivela a caso %2%3 a %4. - + %1 randomly reveals %2%3. %1 rivela a caso %2%3. - + %1 peeks at face down card #%2. %1 sbircia la carta a faccia in giù #%2. - + %1 peeks at face down card #%2: %3. %1 sbircia la carta a faccia in giù #%2: %3. - + %1 reveals %2%3 to %4. %1 rivela %2%3 a %4. - + %1 reveals %2%3. %1 rivela %2%3. - + %1 reversed turn order, now it's %2. %1 ha rovesciato l'ordine dei turni, ora è %2. - + reversed invertito - + normal normale - + Heads Testa - + Tails Croce - + %1 flipped a coin. It landed as %2. %1 ha lanciato una moneta. Il risultato è %2. - + %1 rolls a %2 with a %3-sided die. %1 lancia un dado a %3 facce e ottiene %2. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 lancia %2 monete. Sono uscite %3 teste e %4 croci. - + %1 rolls a %2-sided dice %3 times: %4. %1 lancia un dado a %2 facce %3 volte: %4. - + %1's turn. Turno di %1 - + %1 sets annotation of %2 to %3. %1 imposta le note di %2 a %3. - + %1 places %2 %3%4 counter(s) on %5 (now %6). - + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + %1 failed to undo their last draw. - + %1 sets counter %2 to %3 (%4%5). %1 imposta il contatore %2 a %3 (%4%5). - + %1 sets %2 to not untap normally. %1 imposta che %2 non STAPpi normalmente. - + %1 sets %2 to untap normally. %1 imposta che %2 STAPpi normalmente. - + %1 removes the PT of %2. %1 elimina i valori F/C di %2. - + %1 changes the PT of %2 from nothing to %4. %1 cambia F/C di %2 da vuota a %4. - + %1 changes the PT of %2 from %3 to %4. %1 cambia F/C di %2 da %3 a %4. - + %1 has locked their sideboard. %1 ha bloccato la sua sideboard. - + %1 has unlocked their sideboard. %1 ha sbloccato la sua sideboard. - + %1 taps their permanents. %1 TAPpa i suoi permanenti. - + %1 untaps their permanents. %1 STAPpa i suoi permanenti. - + %1 taps %2. %1 TAPpa %2. - + %1 untaps %2. %1 STAPpa %2. - + %1 shuffles %2. %1 mescola %2. - + %1 shuffles the bottom %3 cards of %2. %1 mescola le %3 carte sul fondo da %2. - + %1 shuffles the top %3 cards of %2. %1 mescola le prime %3 carte da %2. - + %1 shuffles cards %3 - %4 of %2. %1 mescola le carte %3 - %4 da %2. - + %1 unattaches %2. %1 toglie %2. - + %1 undoes their last draw. %1 annulla la sua ultima pescata. - + %1 undoes their last draw (%2). %1 annulla la sua ultima pescata (%2). @@ -6409,115 +8061,125 @@ Il database delle carte verrà ricaricato. MessagesSettingsPage - + Word1 Word2 Word3 Parola1 Parola2 Parola3 - + Add New Message Aggiungi Nuovo Messaggio - + Edit Message Modifica Messaggio - + Remove Message Rimuovi Messaggio - + Add message Aggiungi messaggio - - + + Message: Messaggio: - + Edit message Modifica messaggio - + Chat settings Impostazioni chat - + Custom alert words Lista parole evidenziate - + Enable chat mentions Abilita menzioni in chat - + Enable mention completer Abilita completamento menzioni - + In-game message macros Messaggi rapidi in partita - + How to use in-game message macros Come usare i messaggi macro in gioco - + Ignore chat room messages sent by unregistered users Ignora i messaggi in chat inviati dagli utenti non registrati - + Ignore private messages sent by unregistered users Ignora i messaggi privati inviati dagli utenti non registrati - + Ignore private messages sent by non-buddy users - - + + Invert text color Inverti colore testo - + Enable desktop notifications for private messages Abilita notifiche desktop per i messaggi privati - + Enable desktop notification for mentions Abilita notifiche sul desktop per le menzioni - + Enable room message history on join Abilita messaggi recenti all'ingresso - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) (Colore in esadecimale) - + Separate words with a space, alphanumeric characters only Separare le parole con uno spazio; solo caratteri alfanumerici @@ -6525,42 +8187,42 @@ Il database delle carte verrà ricaricato. MoveMenu - + Move to &Metti in - + &Top of library in random order &Cima al grimorio in ordine casuale - + X cards from the top of library... Posizione X dalla cima del grimorio... - + &Bottom of library in random order &Fondo al grimorio in ordine casuale - + T&able Tavolo - + &Hand &Mano - + &Graveyard &Cimitero - + &Exile &Esilio @@ -6639,148 +8301,134 @@ Il database delle carte verrà ricaricato. PaletteEditorDialog - - + + Reset - - - Apply - - - - - - Save && Apply - - - - + ▼ Edit Palette - - + + ▶ Edit Palette - - - Palette Editor — %1 - - - - - <b>Palette Editor</b> &nbsp;·&nbsp; %1 - - - - - This theme ships no default palette files - - - Replace current colours with the theme author's defaults - + Palette Editor — %1 + Modifica Palette — %1 - - Switch between the light and dark palette files + + <b>Palette Editor</b> &nbsp;·&nbsp; %1 + <b>Modifica Palette</b> &nbsp;·&nbsp; %1 + + + + + Save - Editing: + Delete this scheme's custom palette and revert to the theme default (or the application palette) - Show or hide the per-role colour grid for manual tweaks + Switch between the light and dark palette files + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + ↺ Revert to theme default - + Discard unsaved edits and restore the last saved palette - - Preview this palette without saving to disk + + Cannot save: this theme has no writable directory - + Write palette-%1.toml and reload the theme - - Cannot save: this theme has no directory on disk - - - - + + Save failed - + + Could not write %1 to: %2 - - - No default found - - - - - No default palette file found for the "%1" scheme. - - PaletteGridWidget - + Active - + Disabled - + Inactive - + Normal interactive state - + Widget is disabled / not interactive - + Window is in background / unfocused + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + Phase @@ -6903,30 +8551,21 @@ Il database delle carte verrà ricaricato. Sottofase finale - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - it - - PlayerActions - - - - + + + + grave cimitero - - - - + + + + exile esilio @@ -7045,16 +8684,272 @@ Il database delle carte verrà ricaricato. PlayerMenu - + Player "%1" Giocatore "%1" - + &Counters &Contatori + + PlaymatCollectionDialog + + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + PrintingDisabledInfoWidget @@ -7078,12 +8973,12 @@ Questo significa che vedrai solo la stampa predefinita per ciascuna scarta anzic PrintingSelector - + Display Navigation Buttons Visualizza pulsanti di navigazione - + Printing Selector Selettore di stampa @@ -7091,25 +8986,65 @@ Questo significa che vedrai solo la stampa predefinita per ciascuna scarta anzic PrintingSelectorCardOverlayWidget - + Preference Preferenza - + Pin Printing Fissa stampa in cima alla lista - + Unpin Printing Rimuovi stampa dalla cima della lista - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards Mostra carte correlate + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -7140,28 +9075,28 @@ Questo significa che vedrai solo la stampa predefinita per ciascuna scarta anzic PrintingSelectorCardSortingWidget - + Alphabetical Alfabetico - + Preference Preferenza - + Release Date Data di rilascio - - + + Descending Discendente - + Ascending Ascendente @@ -7177,95 +9112,103 @@ Questo significa che vedrai solo la stampa predefinita per ciascuna scarta anzic PtMenu - + Power / toughness Forza / costituzione - + &Increase power &Aumenta forza - + &Decrease power &Diminuisci forza - + I&ncrease toughness A&umenta costituzione - + D&ecrease toughness D&iminuisci costituzione - + In&crease power and toughness Au&menta forza e costituzione - + Dec&rease power and toughness Dimi&nuisci forza e costituzione - + Increase power and decrease toughness Aumenta forza e diminuisci costituzione - + Decrease power and increase toughness Diminuisci forza e aumenta costituzione - + Set &power and toughness... Imposta &forza e costituzione... - + Reset p&ower and toughness Resetta f&orza e costituzione + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services Servizi - + Hide %1 Nascondi %1 - + Hide Others Nascondi altre - + Show All Mostra tutte - + Preferences... Preferenze... - + Quit %1 Esci da %1 - + About %1 Informazioni su %1 @@ -7273,17 +9216,17 @@ Questo significa che vedrai solo la stampa predefinita per ciascuna scarta anzic QObject - + Cockatrice card database (*.xml) Database delle carte di Cockatrice (*.xml) - + All files (*.*) Tutti i file (*.*) - + Cockatrice replays (*.cor) Replay di Cockatrice (*.cor) @@ -7303,17 +9246,22 @@ Questo significa che vedrai solo la stampa predefinita per ciascuna scarta anzic Token - + + Maybeboard + + + + Overwrite Existing File? Sovrascrivere il file esistente? - + A .cod version of this deck already exists. Overwrite it? Una versione .cod di questo mazzo esiste già. Sovrascriverla? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7330,7 +9278,7 @@ Dovrai usare il Gestore dei set, raggiungibile tramite Database carte -> Orga Sicuro di voler abilitare questa funzione? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7347,100 +9295,110 @@ Potrai anche usare il Gestore dei set per personalizzare l'ordine delle sta Sicuro di voler disabilitare questa funzione? - + Confirm Change Conferma modifica + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK Conferma - + Save Salva - + Save All Salva tutto - + Open Apri - + &Yes &Sì - + Yes to &All Sì a &tutto - + &No &No - + N&o to All N&o a tutto - + Abort Interrompi - + Retry Riprova - + Ignore Ignora - + Close Chiudi - + Cancel Annulla - + Discard Abbandona - + Help Aiuto - + Apply Applica - + Reset Resetta - + Restore Defaults Ripristina predefiniti @@ -7448,121 +9406,189 @@ Sicuro di voler disabilitare questa funzione? QuickSetupPanel - + %1% - + <b>Quick Setup</b> - + Generate all palette roles automatically from a single accent colour - + Accent: - + Primary hue. Used directly for highlights and links. At high intensity it also tints buttons and backgrounds. - + Intensity: - + Subtle - + Full colour - + 0–30 Subtle tint — only highlights and links change hue 30–70 Accented — buttons, tooltips, and borders join in 70–100 Full colour — backgrounds, everything - + 70% - - - Generate ↓ - - - - - Derive all palette roles from the accent colour above. -Fine-tune individual colours in the grid afterwards. - - RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name Nome - + ID ID - + Upload time Tempo di upload + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID ID - + Name Nome - + Players Giocatori - + Keep Tieni - + Time started Inizio - + Duration (sec) Durata (sec) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu @@ -7604,37 +9630,37 @@ Fine-tune individual colours in the grid afterwards. RoomSelector - + Rooms Stanze - + Joi&n E&ntra - + Room Stanza - + Description Descrizione - + Permissions Permessi - + Players Giocatori - + Games Partite @@ -7642,7 +9668,7 @@ Fine-tune individual colours in the grid afterwards. SayMenu - + S&ay Invi&a @@ -7650,32 +9676,32 @@ Fine-tune individual colours in the grid afterwards. SequenceEdit - + Choose an action from the table Scegli un'azione dalla tabella - + Hit the key/combination of keys you want to set for this action Premi il tasto/combinazione di tasti che vuoi impostare per questa azione - + Invalid key Tasto non valido - + Shortcut already in use by: Scorciatoia già utilizzata da: - + Clear Pulisci - + Restore default Ripristina predefinito @@ -7708,56 +9734,92 @@ Fine-tune individual colours in the grid afterwards. Data di rilascio + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts Ripristina scorciatoie predefinite - + Do you really want to restore all default shortcuts? Sei scuro di voler ripristinare tutte le scorciatoie predefinite? - + Clear all default shortcuts Rimuovi tutte le scorciatoie predefinite - + Do you really want to clear all shortcuts? Sei sicuro di voler rimuovere tutte le scorciatoie? - + Section: Sezione: - + Action: Azione: - + Shortcut: Scorciatoia: - + + Shortcut editor + + + + How to set custom shortcuts Come impostare scorciatoie personalizzate - + Clear all shortcuts Elimina tutte le scorciatoie - + Search by shortcut name Cerca per nome della scorciatoia @@ -7813,12 +9875,12 @@ Controlla le impostazioni! SideboardMenu - + &Sideboard &Sideboard - + &View sideboard &Guarda la sideboard @@ -7826,27 +9888,27 @@ Controlla le impostazioni! SoundSettingsPage - + Enable &sounds Abilita &suoni - + Current sounds theme: Tema sonoro attuale: - + Test system sound engine Prova il funzionamento dei suoni - + Sound settings Impostazioni suoni - + Master volume Volume @@ -7854,48 +9916,48 @@ Controlla le impostazioni! SpoilerBackgroundUpdater - + Spoilers season has ended La stagione degli spoiler è finita - + Deleting spoiler.xml. Please run Oracle Cancellando spoiler.xml. Per favore avviare Oracle - - + + Spoilers download failed Download degli spoiler fallito - + No internet connection Connessione internet assente - + Error Errore - + Spoilers already up to date Gli spoiler sono aggiornati - + No new spoilers added Nessun nuovo spoiler aggiunto - + Spoilers have been updated! Gli spoiler sono stati aggiornati! - + Last change: Ultima modifica: @@ -7903,157 +9965,170 @@ Controlla le impostazioni! StableReleaseChannel - + Default Predefinito - + No reply received from the release update server. Nessuna risposta ricevuta dal server degli aggiornamenti di versione. - + Invalid reply received from the release update server. Risposta non valida ricevuta dal server degli aggiornamenti di versione. - + No reply received from the tag update server. Nessuna risposta ricevuta dal server dei tag di aggiornamento. - + Invalid reply received from the tag update server. Risposta non valida ricevuta dal server dei tag di aggiornamento. + + StatsTally + + + Total Power + + + + + Total Toughness + + + StorageSettingsPage - - - + + + Success - + Cached card pictures have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + In-memory (currently loaded) card pictures have been reset. - + Card Picture Loader Caching Method: - + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. - + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. - + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. - + Delete Cached Images - + Delete Saved Images - + Clear In-Memory Images - + Card Picture Loader Cache Method - + Network Cache - + Filesystem - + In-Memory Picture Cache - + Network Cache Size: - + On-disk cache for downloaded pictures - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + In-memory cache for pictures not currently on screen - + Naming scheme: - + Day(s) @@ -8061,17 +10136,33 @@ Controlla le impostazioni! TabAccount - + + + My Reports + + + + Add to Buddy List Aggiungi a Lista amici - + Add to Ignore List Aggiungi a Lista ignorati - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account Account @@ -8192,118 +10283,118 @@ Controlla le impostazioni! TabArchidekt - - - + + + Desc. Decresc. - - + + AND AND - - + + Require ALL selected colors Richiede TUTTI i colori selezionati - - + + Deck name... Nome mazzo... - - + + Owner... Proprietario... - - + + Packages Package - - + + Advanced Filters Filtri avanzati - + Bracket: Fascia: - - + + Any Qualsiasi - - + + Contains card... Contiene carta... - - + + Commander... Comandante... - - + + Tag... Tag... - - + + Deck Size Dimensione mazzo - + Cards: Carte: - - + + Asc. Cresc. - + Sort by: Ordina per: - + Filter by: Filtra per: - + Display Settings Impostazioni di visualizzazione - - + + Search Cerca - - + + Formats Formati @@ -8316,42 +10407,42 @@ Controlla le impostazioni! TabCardArtRules - + Card: - + ProviderId: - + Mode: - + Reason: - + Type a card name... - + Add rule - + Remove rule - + Refresh @@ -8364,52 +10455,52 @@ Controlla le impostazioni! TabDeckEditor - + Card Info Info carta - + Deck Mazzo - + Filters Filtri - + &View &Visualizza - + Card Database Database carte - + Printing Stampa - + Visible Mostra - + Floating Separata - + Reset layout Reimposta disposizione - + Deck: %1 Mazzo: %1 @@ -8417,49 +10508,49 @@ Controlla le impostazioni! TabDeckEditorVisual - + Visual Deck: %1 Mazzo visuale: %1 - + &Visual Deck Editor Editor &visuale - - + + Card Info Info carta - - + + Deck Mazzo - + &View &Visualizza - + Printing Stampa - + Visible Mostra - + Floating Separata - + Reset layout Reimposta disposizione @@ -8467,22 +10558,22 @@ Controlla le impostazioni! TabDeckEditorVisualTabWidget - + Visual Deck View Mazzo visuale - + Visual Database Display Database visuale - + Deck Analytics Analisi mazzo - + Sample Hand Mano di esempio @@ -8490,138 +10581,236 @@ Controlla le impostazioni! TabDeckStorage - + Local file system File locali - + Server deck storage Mazzi su server - - + + Open in deck editor Apri nell'editor dei mazzi - + Rename deck or folder Rinomina mazzo o cartella - + Upload deck Upload mazzo - + Download deck Scarica mazzo - - - - + + + + New folder Nuova cartella - - + + Delete Elimina - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder Apri cartella mazzi - + Rename local folder Rinomina cartella locale - + Rename local file Rinomina file locale - + New name: Nuovo nome: - - - - + + + + + + + Error Errore - + Rename failed Rinomina non riuscita - - + + + Invalid deck file File mazzo non valido - + Enter deck name Inserisci il nome del mazzo - + This decklist does not have a name. Please enter a name: Questo mazzo non ha un nome. Per favore inserisci un nome: - + Unnamed deck Mazzo senza nome - + Failed to upload deck to server Impossibile caricare il mazzo sul server - + Delete local file Elimina file locali - + Are you sure you want to delete the selected files? Vuoi davvero eliminare i file selezionati? - + Delete remote decks Elimina i deck remoti - + Are you sure you want to delete the selected decks? Vuoi davvero eliminare i mazzi selezionati? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: Nome della nuova cartella: - + Deck Storage Archivio mazzi @@ -8629,20 +10818,271 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage Galleria mazzi - + Error Errore - + Could not open deck at %1 Impossibile aprire il mazzo a %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -8655,27 +11095,27 @@ Please enter a name: TabEdhRecMain - + &Cards &Carte - + Top Commanders Comandanti più popolari - + Tags Etichette - + Search for a card ... Cerca carta... - + Search Cerca @@ -8688,191 +11128,207 @@ Please enter a name: TabGame - - - + + + Replay Replay - - + + Game Partita - - + + Player List Giocatori - - + + + Invite + + + + + Card Info Info carta - - + + Messages Messaggi - - + + Replay Timeline Replay - + &Phases &Fasi - + &Game &Partita - + Next &phase Prossima &fase - + Next phase with &action Prossima sottofase + &azione - + Next &turn Prossimo &turno - + Reverse turn order Inverti l'ordine dei turni - + &Remove all local arrows &Rimuovi tutte le frecce - + Rotate View Cl&ockwise Ruota vista in senso &orario - + Rotate View Co&unterclockwise R&uota vista in senso antiorario - + Game &information &Informazioni partita - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede Rientra in gioco - - - + + + &Concede &Concedi - + &Leave game &Lascia partita - + C&lose replay C&hiudi replay - + &Focus Chat Vai alla &chat - + &Say: &Parla: - + Selected cards Carte selezionate - + &View &Visualizza - + Visible Mostra - + Floating Separata - + Reset layout Reimposta disposizione - + Concede Concedi - + Are you sure you want to concede this game? Vuoi veramente concedere la partita? - + Unconcede Annulla concedi - + You have already conceded. Do you want to return to this game? Hai già concesso. Vuoi ritornare in questa partita? - + Leave game Lascia la partita - + Are you sure you want to leave this game? Sei sicuro di voler lasciare la partita? - + A player has joined game #%1 Un giocatore si è unito alla partita #%1 - + %1 has joined the game %1 si è unito alla partita - + You have been kicked out of the game. Sei stato kickato fuori dalla partita. @@ -8888,158 +11344,158 @@ Please enter a name: TabLog - + Logs Registri - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Ora;NomeMittente;IPMittente;Messaggio;IDDestinatario;NomeDestinatario - + Room Logs Registri stanze - + Game Logs Registri partite - + Chat Logs Registri chat - - + + Error Errore - + You must select at least one filter. Seleziona almeno un filtro. - + You have to select a valid number of days to locate. Seleziona un numero di giorni valido. - + Username: Nome utente: - + IP Address: Indirizzo IP: - + Game Name: Nome partita: - + GameID: ID Partita: - + Message: Messaggio: - + Main Room Stanza principale - + Game Room Stanza partita - + Private Chat Chat privata - + Past X Days: Ultimi X giorni: - + Today Oggi - + Last Hour Ultima ora - + Maximum Results: Massimo numero di risultati: - + At least one filter is required. The more information you put in, the more specific your results will be. E' richiesto perlomeno un filtro. Più informazioni inserisci, più specifici saranno i risultati. - + Get User Logs Ottieni log utente - + Clear Filters Elimina filtri - + Filters Filtri - + Log Locations Posizioni registro - + Date Range Periodo - + Maximum Results Massimo numero di risultati - - + + Message History Storico messaggi - + Failed to collect message history information. Impossibile recuperare la storia dei ban. - + There are no messages for the selected filters. Nessun messaggio corrisponde ai filtri selezionati. @@ -9047,219 +11503,559 @@ Più informazioni inserisci, più specifici saranno i risultati. TabMessage - + Private &chat &Chat privata - + &Leave &Lascia - + %1 - Private chat %1 - Chat privata - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. Questo utente ti sta ignorando, non può vedere i tuoi messaggi nella chat principale e tu non puoi entrare nelle sue partite. - + Private message from Messaggio privato da - + %1 has left the server. %1 è uscito dal server. - + %1 has joined the server. %1 è entrato nel server. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system File locali - + Server replay storage Replay sul server - - + + Watch replay Guarda replay - + Rename Rinomina - - + + New folder Nuova cartella - - + + Delete Elimina - + Open replays folder Apri cartella replay - + Download replay Scarica replay - + Toggle expiration lock Metti/togli blocco scadenza - + Get replay share code Ottieni codice di condivisione replay - - + + Look up replay by share code Cerca replay dal codice di condivisione - + Rename local folder Rinomina cartella locale - + Rename local file Rinomina file locale - + New name: Nuovo nome: - + Error Errore - + Rename failed Rinomina non riuscita - + Name of new folder: Nome della nuova cartella: - + Delete local file Elimina il file locale - + Are you sure you want to delete the selected files? Vuoi davvero eliminare i file selezionati? - + Are you sure you want to delete the selected replays? Vuoi davvero eliminare i replay selezionati? - + Failed to get code Impossibile ottenere il codice - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. O questo server non supporta la condivisione dei replay, o non consente la condivisione dei replay per te. - - - + + + Failed Fallito - + Could not get replay code Impossibile ottenere il codice replay - + Replay Share Code Codice di condivisione replay - + Others can use this code to add the replay to their list of remote replays: %1 Altre persone possono usare questo codice per aggiungere il replay alla loro lista di replay remoti: %1 - + Copy to clipboard Copia negli appunti - + Replay share code Codice di condivisione replay - + Replay code found Codice replay trovato - + Replay was added, or you already had access to it. Il replay è stato aggiunto oppure ne avevi già accesso. - + Replay code not found Codice replay non trovato - + Failed to submit code Impossibile inviare il codice - + Unexpected error Errore inatteso - + Delete remote replay Elimina replay remoto @@ -9269,65 +12065,478 @@ Più informazioni inserisci, più specifici saranno i risultati. Replay partite + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - - Friends - - - - - Online - - - - - Ignored - - - - + &Say: &Parla: - + Chat Chat - + &Room &Stanza - + &Leave room &Lascia stanza - + &Clear chat Pulisci &chat - + Chat Settings... Impostazioni chat... - + mentioned you. ti ha menzionato. - + Click to view Clicca per visualizzare - + You are flooding the chat. Please wait a couple of seconds. Stai spammando la chat. Attendi un paio di secondi. @@ -9335,35 +12544,35 @@ Più informazioni inserisci, più specifici saranno i risultati. TabServer - + Server Server - - - - + + + + Error Errore - + Failed to join the server room: it doesn't exist on the server. Impossibile unirsi alla stanza: non esiste sul server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. Il server pensa che tu sia nella stanza ma il tuo client non è in grado di mostrarlo. Prova a riavviare il client. - + You do not have the required permission to join this server room. Non hai i requisiti necessari per unirti a questa stanza. - + Failed to join the server room due to an unknown error: %1. Impossibile unirsi alla stanza a causa di un errore sconosciuto: %1. @@ -9371,97 +12580,173 @@ Più informazioni inserisci, più specifici saranno i risultati. TabSupervisor - + Deck Editor &Editor dei mazzi - + Visual Deck Editor Editor visuale - + EDHRec EDHRec - + Archidekt Archidekt - + Home Home - + &Visual Deck Storage &Galleria mazzi - + Visual Database Display Galleria database - + Server Server - + Account Account - + Deck Storage &Archivio mazzi - + Game Replays &Replay partite - + Administration Amministrazione - + Logs Registri - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? Sei sicuro? - + There are still open games. Are you sure you want to quit? Ci sono ancora delle partite aperte. Sei sicuro di voler uscire? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view Clicca per visualizzare - + Your buddy %1 has signed on! Il tuo amico %1 si è collegato - + Unknown Event Evento sconosciuto. - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -9472,39 +12757,39 @@ Questo messaggio può significare che è disponibile una nuove versione di Cocka Per aggiornare il tuo client, vai su Aiuto -> Controlla aggiornamenti client - + Idle Timeout Timeout inattività - + You are about to be logged out due to inactivity. Stai per essere disconnesso per inattività. - + Promotion Promozione - + You have been promoted. Please log out and back in for changes to take effect. Sei stato promosso. Esci e rientra per dare effetto alle modifiche. - + Warned Avviso - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Hai ricevuto un avviso a causa di %1. Se pregato di evitare di continuare questa attività o potrebbero venire presi ulteriori provvedimenti nel tuoi confronti. Per qualsiasi domanda, manda un messaggio ad un moderatore. - + You have received the following message from the server. (custom messages like these could be untranslated) Hai ricevuto il seguente messaggio dal server. @@ -9524,6 +12809,34 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Galleria database + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -9538,6 +12851,65 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Impossibile analizzare il mazzo. + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -9609,57 +12981,52 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u UserCardArtSettingsDialog - + Card Art Settings - + Type a card name... - + Card name: - - Card ProviderId: + + Printing: - + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + Left margin (%): - + Right margin (%): - - Vertical offset: - - - - - Zoom: - - - - - Parameters - - - - - Preview - - - - + Remove Banner Card @@ -9667,216 +13034,266 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u UserContextMenu - + User &details &Dettagli utente - + Private &chat &Chat privata - + Show this user's &games Visualizza le &partite dell'utente - + + View this user's &public decks + + + + Add to &buddy list Aggiungi alla lista &amici - + Remove from &buddy list Rimuovi dalla lista &amici - + Add to &ignore list Aggiungi alla lista &ignorati - + Remove from &ignore list Rimuovi dalla lista &ignorati - + Kick from &game Caccia dalla &partita - + + Report user + + + + Warn user Avvisa utente - + View user's war&n history Mostra storia avvisi ute&nte - + Ban from &server Banna dal &server - + View user's &ban history Mostra storia &ban utente - + &Promote user to moderator &Promuovi utente a moderatore - + Dem&ote user from moderator Degrada il m&oderatore ad utente - + Promote user to &judge Promuovi utente a &giudice - + Demote user from judge Degrada utente da giudice - + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes Visualizza le note degli amministratori - - + Investigate user + + + + + + Error Errore - + This user does not exist. L'utente non esiste. - + You are being ignored by %1 and can't see their games. Stai venendo ignorato da %1 e non puoi vedere le sue partite. - + Could not get %1's games. Impossibile ottenere le partite di %1. - + %1's games Partite di %1 - - - + + + Ban History Storico ban - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Ora Ban;Moderatore;Durata Ban;Ragione Ban;Ragione Visibile - + User has never been banned. L'utente non è mai stato bannato. - + Failed to collect ban information. Impossibile recuperare le informazioni sui ban. - - - + + + Warning History Storico avvisi - + Warning Time;Moderator;User Name;Reason Ora Avviso;Moderatore;Nome Utente;Ragione - + User has never been warned. L'utente non ha mai ricevuto avvisi. - + Failed to collect warning information. Impossibile recuperare le informazioni sugli avvisi. - + Failed to get admin notes. Impossibile ottenere le note degli amministratori - - + + Success Successo - + Successfully promoted user. Utente promosso. - + Successfully demoted user. Utente degradato. - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + Kick Player - + Are you sure you want to kick this player from the game? - - - + + + Failed Fallito - + Failed to promote user. Promozione fallita. - + Failed to demote user. Degradazione fallita. - + Copy hash to clipboard Copia hash negli appunti - + Remove this user's messages Rimuovi i messaggi di questo utente @@ -9914,61 +13331,61 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Amministratore - + Moderator Moderatore - + Registered user Utente registrato - - + + Unregistered user Utente non registrato - + Judge Arbitro - + Unknown Sconosciuta - + The entered password does not match your account. La password inserita non corrisponde al tuo accound. - - - - + + + + Information Informazione - + User information updated. Informazioni utente aggiornate. - - - - - - + + + + + - - - + + + + Error Errore @@ -9992,283 +13409,292 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Edit Banner Card + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) 1 Anno.%n Anni.%n Anno(i). - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format %10%n Giorno %20%10%n Giorni %20%10%n Giorno(i) %20 - + Enter Password Inserisci Password - + Password verification is required in order to change your email address La verifica della password è necessaria per cambiare il tuo indirizzo email - - - + + + An error occurred while trying to update your user information. E' stato riscontrato un errore durante l'aggiornamento delle tue informazioni utente. - + The selected card is blacklisted on this server or another error occurred. - + Banner card removed. - + Banner card updated. - + This server does not permit you to update your user informations. Questo server non ti permette di aggiornare le tue informazioni utente. - + Password changed. Password modificata. - + This server does not permit you to change your password. Questo server non ti permette di modificare la password. - + The new password is too short. La nuova password è troppo corta. - + The old password is incorrect. La vecchia password è sbagliata. - + Avatar updated. Avatar modificato. - + This server does not permit you to update your avatar. Questo server non ti permette di aggiornare il tuo avatar. - + An error occured while trying to update your avatar. - - - This server does not permit you to update your user informations. - An error occured while trying to updater your avatar. - Questo server non ti permette di aggiornare le tue informazioni utente. - UserInfoPopup - - + + Games - + Chat - + Open private chat - + Profile - + View user profile - + Show this user's games - + + Invite + + + + + Invite to your game + + + + − Buddy - + Remove from buddy list - + + Buddy - + Add to buddy list - + − Ignore - + Remove from ignore list - + + Ignore - + Add to ignore list - + Ban - + Ban from server - + Warn - + Warn user - + Ban log - + View ban history - + Warn log - + View warning history - + Notes - + View admin notes - + − Mod - + Demote from moderator - + + Mod - + Promote to moderator - + − Judge - + Demote from judge - + + Judge - + Promote to judge - + Join game - + Spectate - - + + Loading games… - + Could not load games. - + No active games. @@ -10276,223 +13702,344 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u UserInterfaceSettingsPage - + General interface settings Impostazioni generali di interfaccia - + &Double-click cards to play them (instead of single-click) &Doppio click sulle carte per giocarle (anzichè un solo click) - + &Clicking plays all selected cards (instead of just the clicked card) &Cliccare gioca tutte le carte selezionate (anziché solo quella cliccata) - + &Play all nonlands onto the stack (not the battlefield) by default Gioca tutte le carte non terra nella &pila (invece che sul campo di battaglia) - + Do not delete &arrows inside of subphases Non eliminare le &freccie al cambio di sottofase - + Close card view window when last card is removed Chiudi la finestra di visualizzazione carte quando l'ultima carta viene rimossa - + Auto focus search bar when card view window is opened Passa alla barra di ricerca quando si apre la finestra di visualizzazione carta - + Annotate card text on tokens Annota il testo della carta sulle pedine - + Show selection count during drag selection - + Show total selection count - - Show subtype breakdown in selection tally + + + Disabled - + + + Enabled + + + + + + Automatic + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + Use tear-off menus, allowing right click menus to persist on screen Usa menù a strappo, permettendo ai menù del tasto destro del mouse di rimanere sullo schermo - + Keep game chat focused when clicking in game (Note: disables card view search bar) - + Notifications settings Impostazioni notifiche - + Enable notifications in taskbar Abilita notifiche nella barra delle applicazioni - + Notify in the taskbar for game events while you are spectating Notifica anche per le partite in cui si è solo uno spettatore - + Notify in the taskbar when users in your buddy list connect Notifca quando utenti nella tua lista amici si connettono - + Animation settings Impostazioni delle animazioni - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation Animazioni &TAPpa/STAPpa - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings Impostazioni editor/archivio mazzi - + Open deck in new tab by default Apri il mazzo in una nuova scheda automaticamente - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby Usa galleria mazzi nella lobby - + Use selection animation for Visual Deck Storage Mostra animazione al passaggio del mouse nella galleria mazzi - + When adding a tag in the visual deck storage to a .txt deck: Quando aggiungi un'etichetta ad un mazzo in formato .txt nella Galleria mazzi: - - do nothing - non fare nulla + + Do nothing + - - ask to convert to .cod - chiedi se convertire in file .cod + + Ask to convert to .cod + - - always convert to .cod - converti sempre in file .cod + + Always convert to .cod + - + Default deck editor type Editor dei mazzi predefinito - + Classic Deck Editor Editor classico - + Visual Deck Editor Editor visuale - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings Impostazioni di riproduzione - + Buffer time for backwards skip via shortcut: Tempo di attesa per saltare indietro dopo aver premuto la scorciatoia + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 Utenti connessi al server: %1 - + Users in this room: %1 Utenti in questa stanza: %1 - + Buddies online: %1 / %2 Amici online: %1 / %2 - + Ignored users online: %1 / %2 Utenti ignorati online: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters Aumenta tutti i segnalini della carta - + &Untap all permanents &STAPpa tutti i permanenti - + R&oll die... L&ancia un dado... - + Flip coin - + &Create token... &Crea una pedina... - + C&reate another token C&rea un'altra pedina - + Cr&eate predefined token Cr&ea pedina predefinita @@ -10529,22 +14076,22 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... Filtro di ricerca... - + Save Filter Salva filtro - + Save all currently applied filters to a file Salva su file i filtri correnti - + Enter filename... Inserisci il nome del file... @@ -10552,72 +14099,72 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDatabaseDisplayFilterToolbarWidget - + Sort by Ordina per - + Filter by Filtra per - + Save and load filters Salva e carica filtri - + Filter by exact card name Filtra per nome della carta esatto - + Filter by card main-type Filtra per tipo - + Filter by card sub-type Filtra per sottotipo - + Filter by set Filtra per set - + Filter by format legality Filtra per legalità di formato - + Save/Load Salva/Carica - + Name Nome - + Main Type Tipo - + Sub Type Sottotipo - + Sets Set - + Formats Formati @@ -10719,7 +14266,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets Filtra per i set più recenti @@ -10727,19 +14274,19 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDatabaseDisplaySetFilterWidget - + Search sets... Ricerca set... - - + + Mode: Exact Match Modalità: Corrispondenza esatta - - + + Mode: Includes Modalità: Include @@ -10785,28 +14332,28 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDatabaseDisplayWidget - + Search by card name (or search expressions) Cerca per nome della carta (o espressioni di ricerca) - - + + Visual Visuale - + Loading database ... Caricamento database... - + Clear all filters Elimina tutti i filtri - + Table Tabella @@ -10814,43 +14361,43 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDeckDisplayOptionsWidget - + Group by: Raggruppa per: - + Change how cards are divided into categories/groups. Modifica come le carte sono divise in categorie / gruppi. - + Sort by: Ordina per: - + Click and drag to change the sort order within the groups Clicca e trascina per modificare l'ordinamento interno ai gruppi - + Configure how cards are sorted within their groups Configura come le carte sono ordinate all'interno dei loro gruppi - - + + Toggle Layout: Overlap Disposizione: Sovrapposta - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) Modifica come le carte vengono mostrate nelle zone (es. sovrapposte o completamente visibili.) - + Toggle Layout: Flat Disposizione: Piatta @@ -10866,12 +14413,12 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDeckEditorSampleHandWidget - + Draw a new sample hand Pesca una nuova mano - + Sample hand size Dimensione della mano @@ -10879,17 +14426,17 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDeckEditorWidget - + Type a card name here for suggestions from the database... Scrivi un nome di carta qui per avere suggerimenti dal database... - + Quick search and add card Cerca e aggiungi carta al mazzo - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter Cerca per la corrispondenza più prossima nel database (con auto-completamento) e premendo Invio, aggiungi la stampa preferita della carta al mazzo. @@ -10897,7 +14444,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDeckStorageFolderDisplayWidget - + Deck Storage Archivio mazzi @@ -10905,52 +14452,57 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDeckStorageQuickSettingsWidget - + Show Folders Mostra cartelle - + Show Tag Filter Mostra filtro etichette - + Show Color Identity Mostra identità di colore - + Show Tags On Deck Previews Mostra etichette sulle anteprime dei mazzi - + + Show Upload Time + + + + Show Banner Card Selection Option Mostra opzione per selezione copertina - + Draw unused Color Identities Mostra identità di colore inutilizzate - + Unused Color Identities Opacity Opacità identità di colore inutilizzate - + Deck tooltip: Suggerimento mazzo: - + None Nessuno - + Filepath Percorso file @@ -10958,7 +14510,7 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDeckStorageSearchWidget - + Search by filename (or search expression) Cerca per nome del file (o espressioni di ricerca) @@ -10966,22 +14518,22 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) Ordina alfabeticamente (Nome del mazzo) - + Sort Alphabetically (Filename) Ordina alfabeticamente (Nome del file) - + Sort by Last Modified Ordina per ultima modifica - + Sort by Last Loaded Ordina per ultimo caricato @@ -10989,17 +14541,22 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u VisualDeckStorageWidget - + Loading database ... Caricamento database... - + Refresh loaded files Aggiorna i file caricati - + + Select decks to share + + + + Visual Deck Storage Settings Impostazioni galleria mazzi @@ -11007,177 +14564,202 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u WarningDialog - + Which warning would you like to send? Che avviso vorresti inviare? - + Redact all messages from this user in all rooms Ottieni tutti i messaggi di questo utente in tutte le stanze - + &OK &OK - + &Cancel &Annulla - + Warn user for misconduct Avvisa l'utente per cattivo comportamento - - + + Error Errore - + User name to send a warning to can not be blank, please specify a user to warn. Il nome utente a cui inviare l'avviso non può essere vuoto. - + Warning to use can not be blank, please select a valid warning to send. L'avviso da inviare non può essere vuoto. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top Muovi set selezionato in cima - + Move selected set up Muovi set selezionato su - + Move selected set down Muovi set selezionato giù - + Move selected set to the bottom Muovi set selezionato in fondo - - Search by set name, code, or type - Cerca per nome, codice o tipo del set - - - + Default order Ordine predefinito - + Restore original art priority order Ripristina ordine originale priorità immagini - + Enable all sets Abilita tutti i set - + Disable all sets Disabilita tutti i set - + Enable selected set(s) Abilita i set selezionati - + Disable selected set(s) Disabilita i set selezionati - + Deck Editor Editor dei mazzi - + Use CTRL+A to select all sets in the view. Usa CTRL+A per selezionare le espansioni nella visuale. - + Only cards in enabled sets will appear in the card list of the deck editor. Solo carte in espansioni abilitate appariranno nella lista carte dell'editor di mazzo. - + Image priority is decided in the following order: La priorità di immagine è decisa nell'ordine seguente: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki innanzitutto la cartella CUSTOM (%1), poi le Espansioni abilitate in questo dialogo (da cima a fondo) - + Include cards rebalanced for Alchemy [requires restart] Includi carte ribilanciate per Alchemy [richiede riavvio] - + Card Art Immagine carta - + + Search by set name, code, type, or release date + + + + How to use custom card art Come usare immagini personalizzate per le carte - + Hints Suggerimenti - + Note Note - + Sorting by column allows you to find a set while not changing set priority. L'ordinamento per colonne ti permette di trovare un set senza cambiare il loro ordine di priorità. - + To enable ordering again, click the column header until this message disappears. Per riabilitare l'ordinamento, clicca sull'intestazione della colonna finché questo messaggio non scompare. - + Use the current sorting as the set priority instead Usa l'ordinamento corrente per impostare la priorità dei set - + Sorts the set priority using the same column Imposta priorità dei set usando l'ordinamento per colonna - + Manage sets Organizza set @@ -11185,93 +14767,85 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u ZoneViewWidget - + Search by card name (or search expressions) Cerca per nome della carta (o espressioni di ricerca) - + Ungrouped Non raggruppare - + Group by Type Raggruppa per tipo - + Group by Mana Value Raggruppa per costo - + Group by Color Raggruppa per colore - + Unsorted Non ordinato - + Sort by Name Ordina per nome - + Sort by Type Ordina per tipo - + Sort by Mana Cost Ordina per costo - + Sort by Colors Ordina per colori - + Sort by P/T Ordina per F/C - + Sort by Set Ordina per Set - + shuffle when closing Mescola alla chiusura - + pile view Raggruppa per tipo - - i18n - - - English - Italiano (Italian) - - main - + Connect on startup Connetti all'avvio - + Debug to file Debug su file @@ -12055,6 +15629,16 @@ Se pregato di evitare di continuare questa attività o potrebbero venire presi u Graveyard Cimitero + + + Report Queue + + + + + Moderation + + diff --git a/cockatrice/translations/cockatrice_ja.ts b/cockatrice/translations/cockatrice_ja.ts index 322c70ac3..ddd1502ba 100644 --- a/cockatrice/translations/cockatrice_ja.ts +++ b/cockatrice/translations/cockatrice_ja.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... (&S)カウンターの数を決める... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter カウンターの設定 - + New value for counter '%1': '%1'カウンターの新しい値: @@ -36,65 +36,114 @@ AbstractTabDeckEditor - + Open in new tab 新しいタブで開く - + Are you sure? よろしいですか? - + The decklist has been modified. Do you want to save the changes? デッキリストが変更されています。 変更を保存しますか? - - - - - - + + + + + + Error エラー - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. - + There are no cards in your deck to be exported + + AccountSetupPage + + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + AddAnalyticsPanelDialog @@ -106,12 +155,12 @@ Please check that the directory is writable and try again. AdminNotesDialog - + Update Notes リリースノート - + Admin Notes for %1 @@ -137,218 +186,304 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error エラー - + Could not create themes directory at '%1'. '%1' にテーマディレクトリを作成できませんでした。 - + Theme settings テーマ設定 - + Current theme: 現在のテーマ - + Open themes folder テーマフォルダを開く - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings メニュー設定 - + Show keyboard shortcuts in right-click menus 右クリックメニューにキーボードショートカットを表示する - + Show game filter toolbar above list in room tab - + Card rendering カードの描画 - + Display card names on cards having a picture 画像のあるカードにカード名を表示 - + Auto-Rotate cards with sideways layout 横向きレイアウトのカードを自動回転 - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Light - + Dark - + + System - + + Show all playmats + + + + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + Active theme palette: - + + Active theme style: + + + + + Qt widget style saved to this theme ("System" lets the application decide) + + + + Edit theme palette - + Home tab settings - + Display card name of background in bottom right - + + Dim the home tab background + + + + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + Styling settings - + Style user list - + Card printings - + Bump sets that the deck contains cards from to the top in the printing selector デッキに含まれるカードを印刷セレクターの一番上に上げる - + Scale cards on mouse over マウスオーバーでカードを拡大 - + Use rounded card corners - + Card layout - + Minimum overlap percentage of cards on the stack and in vertical hand スタックと手札垂直配置時の重なり率の最小値 - + Maximum initial height for card view window: カードビューウィンドウの初期高さの最大値 - - + + rows 行 - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout 手札のレイアウト - + Display hand horizontally (wastes space) 手札を横に並べる - + Enable left justification 左揃えを有効 - + Table grid layout テーブルのレイアウト - + Invert vertical coordinate カード配置の垂直反転 - + Minimum player count for multi-column layout: プレイヤーを複数列レイアウトにする最少人数: - + Maximum font size for information displayed on cards: カードに表示する情報の最大のフォントサイズ: @@ -356,12 +491,12 @@ Please check that the directory is writable and try again. ArchidektApiResponseDeckDisplayWidget - + Back to results - + Open Deck in Deck Editor @@ -387,112 +522,112 @@ Please check that the directory is writable and try again. BanDialog - + ban &user name ユーザー名でBAN - + ban &IP address IPアドレスでBAN - + ban client I&D クライアントIDでBAN - + Ban type BANタイプ - + &permanent ban 永久BAN - + &temporary ban 一時BAN - + &Days: 日 - + &Hours: 時間 - + &Minutes: 分 - + Duration of the ban BAN期間 - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. BANの理由を入力してください。 これはモデレーターによって保存されBANされた人には見えません。 - + Please enter the reason for the ban that will be visible to the banned person. BANの理由を入力してください。これはBANされる人に通知されます。 - + Redact all messages from this user in all rooms すべてのルームでこのユーザーからのすべてのメッセージを編集します - + &OK OK - + &Cancel キャンセル - + Ban user from server サーバーからBANされたユーザーです - - - - + + + + Error エラー - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. BANの理由を3 つの組み合わせを選択する必要があります:不適切な名前、不適切なIPアドレス、クライアントID - + You must have a value in the name ban when selecting the name ban checkbox. 名前BANチェックボックスを選択するときは、名前BANの値を持っている必要があります。 - + You must have a value in the ip ban when selecting the ip ban checkbox. IP BANチェックボックスを選択するときは、IP BANの値を持っている必要があります。 - + You must have a value in the clientid ban when selecting the clientid ban checkbox. クライアントID BANチェックボックスを選択するときは、クライアントID BANに値を持っている必要があります。 @@ -500,22 +635,22 @@ This is only saved for moderators and cannot be seen by the banned person. BetaReleaseChannel - + Beta Beta(開発中バージョン) - + No reply received from the release update server. リリース更新サーバーから応答がありませんでした。 - + Invalid reply received from the release update server. リリース更新サーバーから無効な応答が受信されました。 - + No reply received from the file update server. ファイル更新サーバーからの応答がありませんでした。 @@ -523,7 +658,20 @@ This is only saved for moderators and cannot be seen by the banned person. CardArtPreviewWidget - + + Banner preview + + + + + + + + Zoom %1× + + + + No card selected @@ -531,22 +679,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardArtRulesModel - + Card - + ProviderId - + Mode - + Reason @@ -554,65 +702,209 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name カード名 - + Sets カードセット - + Mana cost マナ・コスト - + Card type カード・タイプ - + P/T P/T - + Color(s) 色 + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + CardDatabaseView - + Add to Deck - + Add to Sideboard - + Select Printing - + + Add to Zone + + + + + Create &new zone... + + + + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards @@ -722,22 +1014,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoFrameWidget - + Image カード画像 - + Description カード文章 - + Both 画像+文章 - + View transformation 別面を見る @@ -745,22 +1037,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards 関連カードを見る - + Add card to deck デッキにカードを追加 - + Mainboard メインボード - + Sideboard サイドボード @@ -768,27 +1060,27 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoTextWidget - + Name: カード名: - + Set: - + Collector Number: - + Related cards: 関連カード: - + Unknown card: 謎のカード: @@ -796,129 +1088,129 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Reduce life by power - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -939,7 +1231,7 @@ This is only saved for moderators and cannot be seen by the banned person. CardSizeWidget - + Card Size カードの大きさ @@ -1079,6 +1371,24 @@ This is only saved for moderators and cannot be seen by the banned person. + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser @@ -1108,280 +1418,382 @@ This is only saved for moderators and cannot be seen by the banned person. + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + ConnectionController - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - - - - - - - - - - - + + + + + + + + + + - - + + + Error - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + Server timeout - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Information - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -1389,113 +1801,113 @@ To update your client, go to Help -> Check for Updates. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - + Requesting forgotten password to %1 as %2... - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. @@ -1545,7 +1957,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1561,17 +1973,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add card to &maindeck - + Add card to &sideboard @@ -1579,97 +1991,158 @@ To update your client, go to Help -> Check for Updates. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + Format: - + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1677,17 +2150,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1695,114 +2168,119 @@ To update your client, go to Help -> Check for Updates. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + + Share deck... + + + + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1810,7 +2288,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1818,110 +2296,152 @@ To update your client, go to Help -> Check for Updates. DeckEditorSettingsPage - - + + Update Spoilers スポイラーを更新 - + Success 完了 - + Download URLs have been reset. ダウンロードURLをリセットしました。 - + Add URL URLを追加 - - + + URL: URL: - - + + Edit URL URLを編集 - + Add New URL 新しいURLを追加 - + Remove URL URLを削除 - + Updating... 更新中... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path パスを選ぶ - + URL Download Priority ダウンロードURL優先設定 - + Spoilers スポイラー - + Download Spoilers Automatically スポイラーを自動的にダウンロード - + Spoiler Location: スポイラーの保存場所: - + Last Change 最新に更新 - + Spoilers download automatically on launch スポイラーは起動時に自動的にダウンロードされます。 - + Press the button to manually update without relaunching ボタンを押すと再起動せずに手動で更新します。 - + Do not close settings until manual update is complete 手動更新が完了するまでこのウィンドウを閉じないでください。 - + Download card pictures on the fly カード画像を自動的にダウンロード(英語) - + How to add a custom URL URLの追加方法について(英語) - + Reset Download URLs ダウンロードURLをデフォルトに戻す @@ -1929,32 +2449,32 @@ To update your client, go to Help -> Check for Updates. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1962,27 +2482,27 @@ To update your client, go to Help -> Check for Updates. DeckListModel - + Count 枚数 - + Set セット - + Number 枚数 - + Provider ID プロバイダーID - + Card カード名 @@ -2003,17 +2523,22 @@ To update your client, go to Help -> Check for Updates. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match 検索モード: 完全一致 - + Mode: Includes 検索モード: 一部に含む - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -2021,7 +2546,7 @@ To update your client, go to Help -> Check for Updates. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... タグを編集... @@ -2029,62 +2554,62 @@ To update your client, go to Help -> Check for Updates. DeckPreviewTagDialog - + Deck Tags Manager デッキタグマネージャー - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: デッキタグの管理ができます。新たなタグを追加することもできます: - + Add a new tag (e.g., Aggro️) 新しいタグを追加する(例: イニシアチブ) - + Add Tag タグを追加 - + Filter tags... タグをフィルタ... - + OK OK - + Edit default tags - + Cancel キャンセル - + Invalid Input 無効な入力 - + Tag name cannot be empty! タグ名を入力してください! - + Duplicate Tag タグの重複 - + This tag already exists. このタグはすでに存在します。 @@ -2092,94 +2617,109 @@ To update your client, go to Help -> Check for Updates. DeckPreviewWidget - + Banner Card - + Open in deck editor - + + Share deck... + + + + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Rename deck + + + + + Rename file + + + + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -2187,57 +2727,102 @@ To update your client, go to Help -> Check for Updates. DeckStateManager - + Rename deck to "%1" from "%2" - + Updated comments (was %1 chars, now %2 chars) - + Set banner card to %1 (%2) - + + Set playmat to %1 + + + + Tags changed - + Set format to %1 - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + %1 1 × "%2" (%3) - + Added - + Removed @@ -2259,87 +2844,115 @@ To update your client, go to Help -> Check for Updates. DeckViewContainer - + Load deck... デッキを開く... - + Load remote deck... サーバーのデッキを開く... - + Load from clipboard... - + Load from website... - + Unload deck デッキを撤去 - + Ready to start 準備完了!( ・`д・´) - + Force start 強制開始 - + Sideboard unlocked サイドボード使用可能 - + Sideboard locked サイドボードロック中 - - + + Error エラー - + The selected file could not be loaded. 選択したファイルが開けませんでした。 - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion デッキフォーマット変換 - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? @@ -2348,7 +2961,7 @@ This will kick all non-ready players from the game. デッキをcod形式に変換しますか? - + Remember and automatically apply choice in the future 選択を記憶し、今後も自動的に適用する @@ -2361,143 +2974,143 @@ This will kick all non-ready players from the game. ダウンロード中... - + Known Hosts ホストを選択 - + Delete the currently selected saved server 選択中のサーバーを削除 - + Refresh the server list with known public servers デフォルトの公開サーバーリストに更新します - + New Host 新しいホスト - + Name: サーバー名: - + &Host: ホストアドレス: - + &Port: ポート: - + Player &name: プレイヤーネーム: - + P&assword: パスワード: - + &Save password パスワードを保存 - + A&uto connect 起動時に自動的に接続 - + Automatically connect to the most recent login when Cockatrice opens Cockatriceを開いた時に自動的に最後のログインに接続 - + If you have any trouble connecting or registering then contact the server staff for help! 接続や登録に問題がある場合は、サーバー管理者にお問い合わせください。 - - + + Webpage ウェブページ - + Reset Password パスワードをリセット - + Forgot password? パスワード忘れた - + &Connect 接続 - + Server サーバー - + Login ログイン - + Server Contact サーバーの連絡先 - + Connect to Server サーバーに接続 - + Server URL サーバーURL - + Communication Port 通信ポート - + Unique Server Name ユニークサーバー名 - + Connection Warning 接続の警告 - + You need to name your new connection profile. 新しい接続プロファイルに名前を付ける必要があります。 - + Connect Warning 接続の警告 - + The player name can't be empty. プレイヤー名を入力して下さい。 @@ -2505,122 +3118,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings 設定を保存する - + &Description: 説明: - + P&layers: プレイヤー人数: - + General 全般 - + Game type ゲームタイプ - + &Password: パスワード: - + Only &buddies can join フレンドのみ - + Only &registered users can join 登録プレイヤーのみ - + Joining restrictions 参加制限 - + &Spectators can watch 観戦を許可 - + Spectators &need a password to watch 観戦にパスワードが必要 - + Spectators can &chat 観戦者はチャットに参加できる - + Spectators can see &hands 観戦者に手札を見せる - + Create game as spectator 観戦者としてゲームを作成 - + Spectators 観戦者 - + Starting life total: 開始時のライフ: - + Open decklists in lobby - + Create game as judge - + Game setup options ゲーム準備オプション - + &Clear 設定をクリア - + Create game ゲームを作成 - + Game information ゲーム情報 - + Error エラー - + Server error. サーバーエラー。 @@ -2628,97 +3241,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: トークン名: - + Token トークン - + C&olor: 色: - + white 白 - + blue 青 - + black 黒 - + red 赤 - + green 緑 - + multicolor 多色 - + colorless 無色 - + &P/T: P/T: - + &Annotation: 注釈: - + &Destroy token when it leaves the table 戦場を離れる際に破棄 - + Create face-down (Only hides name) - + Token data トークン設定 - + Show &all tokens すべてのトークンを見る - + Show tokens from this &deck このデッキのトークンを見る - + Choose token from list 一覧からトークンを作成 - + Create token トークンを作成 @@ -2726,53 +3339,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + ✖ - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2781,7 +3394,7 @@ This will kick all non-ready players from the game. DlgEditAvatar - + No image chosen. 画像がありません。 @@ -2793,27 +3406,32 @@ To remove your current avatar, confirm without choosing a new image. 現在のアバターを削除するには、新しい画像を選ばずにOKを押してください。 - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... 参照... - + Change avatar アバター変更 - + Open Image アバター画像を開く - + Image Files (*.png *.jpg *.bmp) 画像ファイル (*.png *.jpg *.bmp) - + Invalid image chosen. この画像は使用できません。 @@ -2839,38 +3457,38 @@ To remove your current avatar, confirm without choosing a new image. DlgEditPassword - + Old password: 前のパスワード: - + New password: 新しいパスワード: - + Confirm new password: 新しいパスワード(確認): - + Change password パスワード変更 - - + + Error エラー - + Your password is too short. パスワードが短すぎます。 - + The new passwords don't match. 新しいパスワードが一致しませんでした。 @@ -2878,93 +3496,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: トークン名: - + C&olor: 色: - + white 白 - + blue 青 - + black 黒 - + red 赤 - + green 緑 - + multicolor 多色 - + colorless 無色 - + &P/T: P/T: - + &Annotation: 注釈: - + Token data トークンの設定 - - + + Add token トークンを追加 - + Remove token トークンを削除 - + Edit custom tokens カスタムトークンを編集 - + Please enter the name of the token: トークン名を入力: - + Error エラー - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. 選択された名前は、既存のカードやトークンと競合しています。 @@ -3043,8 +3661,9 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games that have started - 進行中のゲームを非表示 + Hide started games + Hide games that have started + @@ -3079,8 +3698,9 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - &Creator name: - 作成者でフィルター: + &Host name: + &Creator name: + @@ -3146,52 +3766,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning パスワードリセットの警告 - + A problem has occurred. Please try to request a new password again. 問題が発生しました。新しいパスワードのリクエストを再試行してください。 - + Enter the information of the server and the account you'd like to request a new password for. 新しいパスワードをリクエストしたいサーバーの情報とアカウントを入力してください。 - + &Host: ホストアドレス: - + &Port: ポート: - + Player &name: プレイヤー名: - + Email: メールアドレス: - + Reset Password Challenge パスワードリセットチャレンジ - + Reset Password Challenge Error パスワードリセットエラー - + The email address can't be empty. メールアドレスを入力してください。 @@ -3199,37 +3819,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. 新しいパスワードをリクエストするサーバーの情報を入力してください。 - + &Host: ホストアドレス: - + &Port: ポート: - + Player &name: プレイヤー名: - + Reset Password Request パスワードリセット要求 - + Reset Password Error パスワードリセットエラー - + The player name can't be empty. プレイヤー名を入力して下さい。 @@ -3237,94 +3857,127 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning パスワードリセットの警告 - + A problem has occurred. Please try to request a new password again. 問題が発生しました。新しいパスワードのリクエストを再試行してください。 - + Enter the received token and the new password in order to set your new password. 新しいパスワードを設定するには、受け取ったトークンと新しいパスワードを入力します。 - + &Host: ホストアドレス: - + &Port: ポート: - + Player &name: プレイヤー名: - + Token: トークン: - - + + New Password: 新しいパスワード: - + Reset Password パスワードリセット - + The player name can't be empty. プレイヤー名を入力して下さい。 - - - - + + + + Reset Password Error パスワードリセットエラー - + The token can't be empty. トークンを入力してください。 - + The new password can't be empty. 新しいパスワードを入力してください。 - + Error エラー - + Your password is too short. パスワードが短すぎます。 - + The passwords do not match. パスワードが一致しませんでした。 + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck デッキを開く @@ -3408,166 +4061,567 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLocalGameOptions - + Players: - + General - + Starting life total: - + Game setup options - + Remember settings - + Local game options + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): カード名または検索条件: - + Number of hits: 枚数 - + Auto play hits 自動的にプレイ - + Put top cards on stack until... 見つかるまで上からスタックに置く... - + No cards matching the search expression exists in the card database. Proceed anyways? 条件に一致するカードはデータベースに存在しません。続行しますか? - + Cockatrice 悪魔からの助言 - + Invalid filter 不正な条件 + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. あなたの情報と登録したいサーバーの情報を入力してください。 あなたのメールアドレスはアカウントの確認に使用されます。 - + &Host: ホストアドレス: - + &Port: ポート: - + Player &name: プレイヤー名: - + P&assword: パスワード: - + Password (again): パスワード (確認) : - + Email: メールアドレス: - + Email (again): メールアドレス (確認) : - + Country: 国籍: - + Undefined 指定なし - + Real name: 本名: - + Register to server サーバーに登録 - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning 登録に関する警告 - + Your password is too short. パスワードが短すぎます。 - + Your passwords do not match, please try again. パスワードが一致しませんでした。もう一度入力して下さい。 - + Your email addresses do not match, please try again. メールアドレスが一致しませんでした。もう一度入力して下さい。 - + The player name can't be empty. プレイヤー名を入力して下さい。 + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice @@ -3633,46 +4687,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database カードデータベース読み込みの不明なエラー。 - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - 無効なカードデータベースです。 - -Cockatriceは無効なデータベースでは正しく機能しない場合があります。 - -Oracle Importerでデータベースを更新する必要があります。 - -データベースの場所の設定を変更してみてください。 - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - 古いカードデータベースです。 - -カード情報や画像をロードする問題を引き起こす可能性があります。 - -Oracle Importerでデータベースを更新する必要があります。 - -データベースの場所の設定を変更してみてください。 - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3685,7 +4705,7 @@ Would you like to change your database location setting? データベースの場所の設定を変更しますか? - + File Error loading your card database. Would you like to change your database location setting? @@ -3694,7 +4714,7 @@ Would you like to change your database location setting? データベースの場所の設定を変更してみてください。 - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3703,7 +4723,7 @@ Would you like to change your database location setting? データベースの場所の設定を変更してみてください。 - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3716,77 +4736,208 @@ Would you like to change your database location setting? データベースの場所設定を変更しますか? - - - + + + Error エラー - + The path to your deck directory is invalid. Would you like to go back and set the correct path? あなたのデッキディレクトリへのパスは無効です。前に戻って正しいパスを設定してください。 - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? あなたのカード画像ディレクトリへのパスは無効です。前に戻って正しいパスを設定してください。 - + Settings 設定 - + + Search settings... + + + + + OK + + + + General 全般 - + Appearance 外観 - + User Interface UI - + Storage - + Card Sources カードダウンロード - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat チャット - + Sound サウンド - + Shortcuts ショートカット + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. @@ -3795,27 +4946,27 @@ You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3823,17 +4974,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next 次 - + Previous 前 - + Tip of the Day 今日のヒント @@ -3841,166 +4992,176 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel 現在のリリースチャンネル: - + Reinstall 再インストール - + Cancel Download ダウンロードをキャンセル - + Open Download Page ダウンロードページを開く - + Check for Client Updates クライアントの更新チェック - - - - + + + + Error エラー - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. CockatriceはSSLをサポートしていなかったため、更新を自動ダウンロード出来ません。 ダウンロードページから手動で更新してください。 - + Downloading update: %1 - + Checking for updates... 更新のチェック中... - + Finished checking for updates 更新のチェックが完了しました - + No Update Available 更新がありません - + Cockatrice is up to date! Cockatriceは最新版です。やったね! - + You are already running the latest version available in the chosen release channel. 選択したリリースチャンネルで最新のバージョンを使用しています。 - + Current version 現在のバージョン - + Selected release channel 選択したリリースチャンネル - - + + Update Available 更新できます!!! - - + + A new version of Cockatrice is available! 新しいバージョンのCockatriceが利用できます! - - + + New version 新しいバージョン - - + + Released 公開日時 - - + + Changelog 変更履歴 - + Do you want to update now? 今すぐ更新しますか? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. 残念ながら、自動アップデータは互換性のあるダウンロードを見つけることができませんでした。 You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error 更新エラー - + An error occurred while checking for updates: 更新のチェック中にエラーが発生しました: - + An error occurred while downloading an update: 更新のダウンロード中にエラーが発生しました: - + Installing... インストール中... - + Cockatrice is unable to open the installer. インストーラーが開けませんでした。 - + Try to update manually by closing Cockatrice and running the installer. Cockatriceを終了した後、インストーラーから手動で更新してください。 - + Download location ダウンロード場所 @@ -4008,17 +5169,17 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing 閉じるときにログをクリア - + Copy to clipboard - + Debug Log デバッグログ @@ -4256,7 +5417,7 @@ You may have to manually download the new version. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -4264,7 +5425,7 @@ You may have to manually download the new version. FilterBuilder - + Type your filter here ここにフィルタを入力 @@ -4272,45 +5433,88 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -4318,140 +5522,156 @@ You may have to manually download the new version. GameSelector - - - - - - - - - + + + + + + + + + Error エラー - + Please join the appropriate room first. 適切な部屋に参加してください。 - + Wrong password. パスワードが間違っています。 - + Spectators are not allowed in this game. このゲームは観戦出来ません。 - + The game is already full. このゲームはすでに満員です。 - + The game does not exist any more. このゲームはすでに存在しません。 - + This game is only open to registered users. このゲームは登録済みプレイヤーのみ参加できます。 - + This game is only open to its creator's buddies. このゲームは作成者のフレンドのみ参加できます。 - + You are being ignored by the creator of this game. あなたはゲームの作成者によって参加拒否されています。 - + Join Game 参加 - + Spectate Game 観戦 - + Game Information ゲーム情報 - + + Cop&y game link + + + + Join Game as Judge - + Spectate Game as Judge - + + Join game 参加 - - Password: - パスワード: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. 最初にルームに参加してください。 - + &Filter games ゲームフィルタ - + C&lear filter フィルタ解除 - + C&reate ゲームを作成 - + &Join 参加 - + Join as judge - + J&oin as spectator 観戦 - + Join as judge spectator - + Games shown: %1 / %2 表示されているゲーム: %1 / %2 - + Games ゲーム @@ -4492,105 +5712,105 @@ You may have to manually download the new version. GamesModel - + >1 day >1 日 - + %1%2 hr short age in hours %1%2 時 - + new できたて - + %1%2 min short age in minutes %1%2 分 - + password パスワード - + buddies only フレンドのみ - + reg. users only 登録ユーザーのみ - + open decklists - - + + can chat チャット可 - + see hands 手札閲覧可 - + can see hands 手札閲覧可 - + not allowed 観戦不可 - + Room ルーム - + Age 作成時 - + Description 説明 - - Creator - 作成者 + + Host + - + Type タイプ - + Restrictions 制限 - + Players 人数 - + Spectators 観戦者 @@ -4598,158 +5818,275 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths すべてのパスをリセット - + All paths have been reset すべてのパスをリセットしました - - - - - - - + + + + English + 日本語 (Japanese) + + + + + + + + + Choose path パスを選択 - + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> + + + + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + Language settings - + Language: 言語: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + Version settings - + Card database - + Startup settings - + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) パス(ポータブルモードでの編集は無効) - + Paths パス - + How to help with translations - + Decks directory: デッキフォルダ: - + Filters directory: - + Replays directory: リプレイフォルダ: - + Pictures directory: カード画像フォルダ: - + Card database: カードデータベース: - + Custom database directory: カスタムデータベースフォルダ: - + Token database: トークンデータベース: - + Update channel 更新チャンネル - + Check for client updates on startup 起動時にクライアントの更新をチェック - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client サーバーでサポートされている機能がクライアントに存在しない場合に通知する - + Automatically run Oracle when running a new version of Cockatrice 新バージョンのCockatriceを起動したら自動的にOracleも起動する - + Show tips on startup 起動時にヒントを表示 - + Last update check on %1 (%2 days ago) @@ -4757,47 +6094,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4891,59 +6228,353 @@ You may have to manually download the new version. + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu @@ -5161,273 +6792,294 @@ You may have to manually download the new version. MainWindow - - + + Player %1 プレイヤー %1 - + Load replay リプレイを開く - + About Cockatrice Cockatriceについて - + Version バージョン - + Cockatrice Webpage Cockatriceウェブページ - + Project Manager: プロジェクトマネージャ: - + Past Project Managers: 元プロジェクトマネージャ: - + Developers: デベロッパ: - + Our Developers リンク先参照 - + Help Develop! 開発者募集中! - + Translators: 翻訳者: - + Our Translators 翻訳者一覧 - + Help Translate! 翻訳者募集中! - + Support: サポート: - + Report an Issue 問題を報告 - + Troubleshooting トラブルシューティング - + F.A.Q. F.A.Q. - - + + Error エラー - + &Connect... サーバーに接続... - + &Disconnect サーバーから切断 - + Start &local game... ローカルゲームを開始... - + &Watch replay... リプレイを見る... - + &Full screen フルスクリーン - + &Register to server... サーバーに登録する - + &Restore password... パスワードリセット... - + &Settings... 設定... - + &Exit 終了 - + A&ctions アクション - + &Cockatrice Cockatrice - + C&ard Database カードデータベース - + &Manage sets... セットを管理... - + Edit custom &tokens... カスタムトークンを編集... - + Open custom image folder カスタム画像フォルダを開く - + Open custom sets folder カスタムセットフォルダを開く - + Add custom sets/cards カスタムセット/カードを追加 - + Reload card database カードデータベースを再読み込み - + Tabs タブ - + &Help ヘルプ - + &About Cockatrice Cockatriceについて - + &Tip of the Day 今日のヒント - + Check for Client Updates クライアントの更新チェック - + Check for Card Updates... カードデータベース更新 - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log デバッグログを見る - + Open Settings Folder 設定フォルダを開く - + + Re-run Onboarding Wizard... + + + + Show/Hide 表示/非表示 - + + Sign-in cancelled + + + + New Version 新しいバージョン - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Cockatrice %1へのアップデートおめでとうございます! Oracleが起動し、カードデータベースが更新されます。 - + Cockatrice installed Cockatriceがインストールされました - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Cockatrice %1のインストールおめでとうございます! Oracleを起動して、初期カードデータベースをインストールします。 - + + Are you sure? + 確認 + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database カードデータベース - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5436,29 +7088,29 @@ If unsure or first time user, choose "Yes" よくわからない場合、初回起動時などは「はい」を押して下さい。 - - + + Yes はい - - + + No いいえ - + Open settings 設定を開く - + New sets found 新しいセットが見つかりました - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5467,22 +7119,22 @@ Do you want to enable it/them? 有効にしますか? - + Yes, always enable - + View sets セットを見る - + Welcome ようこそ - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5491,89 +7143,89 @@ Read more about changing the set order or disabling specific sets and consequent 「セットの設定」ウィンドウ内の特定のセットを無効にしたり、順序を変更をする方法の詳細をお読みください。 - + Information 情報 - + A card database update is already running. データベース更新は既に起動中です。 - + Unable to run the card database updater: データベース更新が実行できませんでした: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. プロセスへの書き込み中にエラーが発生しました。プロセスが実行されていないか、入力チャネルが閉じられている等の可能性があります。 - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - - - - - + + + + + Load sets/cards セット/カードを読み込む - + Selected file cannot be found. 選択したファイルが見つかりませんでした。 - + You can only import XML databases at this time. ここではXMLデータベースのみをインポートできます。 - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. 新しいセット/カードが正常に追加されました。 データベースが再読込されます。 - + Sets/cards failed to import. セット/カードのインポートに失敗 @@ -5778,615 +7430,615 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play スタックから - + from their graveyard 墓地から - + from exile 追放領域から - + from their hand 手札から - + the top card of %1's library %1のライブラリーの一番上のカード - + the top card of their library ライブラリーの一番上のカード - + from the top of %1's library %1のライブラリーの一番上から - + from the top of their library ライブラリーの一番上から - + the bottom card of %1's library %1のライブラリーの一番下のカード - + the bottom card of their library ライブラリーの一番下のカード - + from the bottom of %1's library %1のライブラリーの一番下から - + from the bottom of their library ライブラリーの一番下のカード - + from %1's library %1のライブラリーから - + from their library ライブラリーから - + from sideboard サイドボードから - + from the stack スタックから - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 は%2一番上のカードを公開した状態でゲームをプレイしている。 - + %1 is not revealing the top card %2 any longer. %1 は%2一番上のカードの公開を終えた。 - + %1 can now look at top card %2 at any time. %1はいつでも%2一番上のカードを見ることができる。 - + %1 no longer can look at top card %2 at any time. %1はもう%2一番上のカードを見ることができない。 - + %1 attaches %2 to %3's %4. %1は%3の%4に%2をつけた。 - + %1 has conceded the game. %1 が投了した!!! - + %1 has unconceded the game. %1が投了を撤回した。 - + %1 has restored connection to the game. %1 がゲームに再接続した。 - + %1 has lost connection to the game. %1 はゲームから切断された。 - + %1 points from their %2 to themselves. %1 は %2 から自分自身へ対象を指定した。 - + %1 points from their %2 to %3. %1 は %2 から %3 へ対象を指定した。 - + %1 points from %2's %3 to themselves. %1 は %2 の %3 から自分自身へ対象を指定した。 - + %1 points from %2's %3 to %4. %1 は %2 の %3 から %4 へ対象を指定した。 - + %1 points from their %2 to their %3. %1 は %2 から %3 へ対象を指定した。 - + %1 points from their %2 to %3's %4. %1 は %2 から %3 の %4 へ対象を指定した。 - + %1 points from %2's %3 to their own %4. %1 は %2 の %3 から %4 へ対象を指定した。 - + %1 points from %2's %3 to %4's %5. %1 は %2 の %3 から %4 の %5 へ対象を指定した。 - + %1 creates a face down token. - + %1 creates token: %2%3. %1 はトークン: %2 を%3作成した。 - + %1 has loaded a deck (%2). %1 はデッキをロードした。ハッシュ:%2 - + %1 has loaded a deck with %2 sideboard cards (%3). %1 はデッキをロードした。サイドボード数:%2, ハッシュ:%3 - + %1 destroys %2. %1 は %2 を取り除いた。 - + a card カード - + %1 gives %2 control over %3. %1 は %2 に %3 のコントロールを渡した。 - + %1 puts %2 into play%3 face down. %1 は %2 を%3裏向きでプレイした。 - + %1 puts %2 into play%3. %1 は %2 を%3プレイした。 - + %1 puts %2%3 into their graveyard face down. - + %1 puts %2%3 into their graveyard. %1 は %2 を%3墓地に置いた。 - + %1 exiles %2%3 face down. - + %1 exiles %2%3. %1 は %2 を%3追放した。 - + %1 moves %2%3 to their hand. %1は %2 を%3手札に加えた。 - + %1 puts %2%3 into their library. %1 は %2 を%3ライブラリーに加えた。 - + %1 puts %2%3 onto the bottom of their library. %1 は %2 を%3ライブラリーの一番下に置いた。 - + %1 puts %2%3 on top of their library. %1 は %2 を%3ライブラリーの一番上に置いた。 - + %1 puts %2%3 into their library %4 cards from the top. %1 は %2 を%3ライブラリーの一番上から%4枚目に置いた。 - + %1 moves %2%3 to sideboard. %1 は %2 を%3サイドボードに置いた。 - + %1 plays %2%3 face down. - + %1 plays %2%3. %1 は %2 を%3プレイした。 - + %1 moves %2%3 to custom zone '%4' face down. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1 は……カードがないライブラリーからカードを引こうとした! - + %1 draws %2 card(s). %1は%2枚カードを引いた。 - + %1 is looking at %2. %1 は%2を見ている。 - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 は%2%4%3枚のカードを見ている。 - + bottom 下から - + top 上から - + %1 turns %2 face-down. %1 は %2 を裏向きにした。 - + %1 turns %2 face-up. %1 は %2 を表向きにした。 - + The game has been closed. ゲームがクローズされました。 - + The game has started. ◇◇ゲーム開始!◇◇ - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 がゲームに参加した。 - + %1 is now watching the game. %1 が観戦に参加した。 - + You have been kicked out of the game. ゲームからキックされました。 - + %1 has left the game (%2). %1 はゲームから離脱した(%2)。 - + %1 is not watching the game any more (%2). %1 が観戦から離脱した(%2)。 - + %1 is not ready to start the game any more. %1 は準備完了を解除した。 - + %1 shuffles their deck and draws a new hand of %2 card(s). %1はライブラリーを切り直し、新たにカードを%2枚引いた。 - + %1 shuffles their deck and draws a new hand. %1はライブラリーを切り直し、新たに手札を引いた。 - + You are watching a replay of game #%1. ゲーム#%1のリプレイを再生しています。 - + %1 is ready to start the game. %1 はゲーム開始の準備が完了した!! - + cards an unknown amount of cards カード - + %1 card(s) a card for singular, %1 cards for plural %1枚のカード - + %1 lends %2 to %3. %1 は %3 に%2を貸した。 - + %1 reveals %2 to %3. %1 は %3 に%2を見せた。 - + %1 reveals %2. %1 は%2を公開した。 - + %1 randomly reveals %2%3 to %4. %1 は%3無作為に %2 を選び、%4に見せた。 - + %1 randomly reveals %2%3. %1 は%3無作為に %2 を選び、公開した。 - + %1 peeks at face down card #%2. %1 は裏向きのカード#%2の表面を確認した。 - + %1 peeks at face down card #%2: %3. %1 は裏向きのカード#%2の表面を確認した ( %3 ) 。 - + %1 reveals %2%3 to %4. %1 は%3 %2 を %4 に見せた。 - + %1 reveals %2%3. %1 は%3 %2 を公開した。 - + %1 reversed turn order, now it's %2. %1はターン順を逆にした(%2)。 - + reversed 逆転 - + normal 順転 - + Heads 表 - + Tails 裏 - + %1 flipped a coin. It landed as %2. %1 はコインを投げた。結果は……【%2】だ! - + %1 rolls a %2 with a %3-sided die. %1 は%3面ダイスをふり、【%2】を出した。 - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 は%2枚のコインを投げた。表が【%3】枚、裏が【%4】枚出た。 - + %1 rolls a %2-sided dice %3 times: %4. %1 は%2面体サイコロを%3個振り、それぞれ【%4】を出した。 - + %1's turn. ■ %1 のターン■ - + %1 sets annotation of %2 to %3. %1 は %2 に注釈をつけた (%3)。 - + %1 places %2 %3%4 counter(s) on %5 (now %6). - + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + %1 failed to undo their last draw. - + %1 sets counter %2 to %3 (%4%5). %1 は%2カウンターを%3に設定した (%4%5)。 - + %1 sets %2 to not untap normally. %1 は %2 をアンタップ・ステップの間にアンタップしないようにした。 - + %1 sets %2 to untap normally. %1 は %2 を通常どおりアンタップするように設定した。 - + %1 removes the PT of %2. %1 は %2 のP/Tを取り除いた。 - + %1 changes the PT of %2 from nothing to %4. %1 は %2 のP/Tを%4にした。 - + %1 changes the PT of %2 from %3 to %4. %1 は %2 のP/Tを%3から%4にした。 - + %1 has locked their sideboard. %1 はサイドボードをロックした。 - + %1 has unlocked their sideboard. %1 はサイドボードを解禁した。 - + %1 taps their permanents. %1 は自分のコントロールするパーマネントをタップした。 - + %1 untaps their permanents. %1 は自分のコントロールするパーマネントをアンタップした。 - + %1 taps %2. %1 は %2 をタップした。 - + %1 untaps %2. %1 は %2 をアンタップした。 - + %1 shuffles %2. %1 は%2を切り直した。 - + %1 shuffles the bottom %3 cards of %2. %1 は%2の一番下の%3枚のカードをに無作為の順番で置いた。 - + %1 shuffles the top %3 cards of %2. %1 は%2の一番上の%3枚のカードをに無作為の順番で置いた。 - + %1 shuffles cards %3 - %4 of %2. %1 は%2の %3 - %4 枚を無作為の順番で置いた。 - + %1 unattaches %2. %1 は %2 をはずした。 - + %1 undoes their last draw. %1 は最後に引いたカードを戻した。 - + %1 undoes their last draw (%2). %1 は最後に引いたカード ( %2 ) を戻した 。 @@ -6394,115 +8046,125 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 ここに入力してください - + Add New Message 定型文を追加 - + Edit Message 定型文を編集 - + Remove Message 定型文を削除 - + Add message 定型文を追加 - - + + Message: 定型文: - + Edit message 定型文を編集 - + Chat settings チャット設定 - + Custom alert words 反応するキーワード - + Enable chat mentions メンション機能を有効 - + Enable mention completer メンション補完を有効 - + In-game message macros 定型文 - + How to use in-game message macros ゲーム内メッセージマクロの使い方 - + Ignore chat room messages sent by unregistered users 未登録ユーザーのルームメッセージを無視 - + Ignore private messages sent by unregistered users 未登録ユーザーの個人チャットを無視 - + Ignore private messages sent by non-buddy users - - + + Invert text color 反転テキストの色 - + Enable desktop notifications for private messages 個人チャットのデスクトップ通知を有効 - + Enable desktop notification for mentions メンションのデスクトップ通知を有効 - + Enable room message history on join 参加時にルームのメッセージ履歴を表示 - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) (色は16進数) - + Separate words with a space, alphanumeric characters only 英数字のみ使用可能。単語を半角スペースで区切って下さい。 @@ -6510,42 +8172,42 @@ Cockatrice will now reload the card database. MoveMenu - + Move to - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + T&able - + &Hand - + &Graveyard - + &Exile @@ -6624,148 +8286,134 @@ Cockatrice will now reload the card database. PaletteEditorDialog - - + + Reset - - - Apply - - - - - - Save && Apply - - - - + ▼ Edit Palette - - + + ▶ Edit Palette - + Palette Editor — %1 - + <b>Palette Editor</b> &nbsp;·&nbsp; %1 - - This theme ships no default palette files - - - - - Replace current colours with the theme author's defaults - - - - - Switch between the light and dark palette files + + + Save - Editing: + Delete this scheme's custom palette and revert to the theme default (or the application palette) - Show or hide the per-role colour grid for manual tweaks + Switch between the light and dark palette files + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + ↺ Revert to theme default - + Discard unsaved edits and restore the last saved palette - - Preview this palette without saving to disk + + Cannot save: this theme has no writable directory - + Write palette-%1.toml and reload the theme - - Cannot save: this theme has no directory on disk - - - - + + Save failed - + + Could not write %1 to: %2 - - - No default found - - - - - No default palette file found for the "%1" scheme. - - PaletteGridWidget - + Active - + Disabled - + Inactive - + Normal interactive state - + Widget is disabled / not interactive - + Window is in background / unfocused + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + Phase @@ -6888,30 +8536,21 @@ Cockatrice will now reload the card database. 最終フェイズ - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - ja - - PlayerActions - - - - + + + + grave - - - - + + + + exile @@ -7030,16 +8669,272 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters + + PlaymatCollectionDialog + + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + PrintingDisabledInfoWidget @@ -7060,12 +8955,12 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelector - + Display Navigation Buttons - + Printing Selector @@ -7073,25 +8968,65 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardOverlayWidget - + Preference 優先設定 - + Pin Printing ピン留めする - + Unpin Printing ピン留めを外す - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards 関連カードを見る + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -7122,28 +9057,28 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardSortingWidget - + Alphabetical 名前順 - + Preference - + Release Date 提供日時順 - - + + Descending 降順 - + Ascending 昇順 @@ -7159,95 +9094,103 @@ This setting means you'll only see the default printing for each card, instead o PtMenu - + Power / toughness - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services サービス - + Hide %1 %1を隠す - + Hide Others 他を隠す - + Show All すべて表示 - + Preferences... 設定... - + Quit %1 %1から出る - + About %1 %1について @@ -7255,17 +9198,17 @@ This setting means you'll only see the default printing for each card, instead o QObject - + Cockatrice card database (*.xml) Cockatriceカードデータベース(*.xml) - + All files (*.*) 全てのファイル (*.*) - + Cockatrice replays (*.cor) Cockatriceリプレイファイル (*.cor) @@ -7285,17 +9228,22 @@ This setting means you'll only see the default printing for each card, instead o トークン - + + Maybeboard + + + + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7306,7 +9254,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7317,100 +9265,110 @@ Are you sure you would like to disable this feature? - + Confirm Change + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK OK - + Save 保存 - + Save All 全て保存 - + Open 開く - + &Yes はい - + Yes to &All すべてはい - + &No いいえ - + N&o to All すべていいえ - + Abort 中止 - + Retry 再試行 - + Ignore 無視 - + Close 閉じる - + Cancel キャンセル - + Discard カードを捨てる - + Help ヘルプ - + Apply 適用 - + Reset リセット - + Restore Defaults デフォルトを復元 @@ -7418,121 +9376,189 @@ Are you sure you would like to disable this feature? QuickSetupPanel - + %1% - + <b>Quick Setup</b> - + Generate all palette roles automatically from a single accent colour - + Accent: - + Primary hue. Used directly for highlights and links. At high intensity it also tints buttons and backgrounds. - + Intensity: - + Subtle - + Full colour - + 0–30 Subtle tint — only highlights and links change hue 30–70 Accented — buttons, tooltips, and borders join in 70–100 Full colour — backgrounds, everything - + 70% - - - Generate ↓ - - - - - Derive all palette roles from the accent colour above. -Fine-tune individual colours in the grid afterwards. - - RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name デッキ名 - + ID ID - + Upload time アップロード日 + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID ID - + Name 説明 - + Players 参加プレイヤー - + Keep ロック - + Time started 開始日時 - + Duration (sec) 期間 (秒) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu @@ -7574,37 +9600,37 @@ Fine-tune individual colours in the grid afterwards. RoomSelector - + Rooms ルーム - + Joi&n 参加する - + Room ルーム名 - + Description 説明 - + Permissions アクセス権限 - + Players 人数 - + Games ゲーム数 @@ -7612,7 +9638,7 @@ Fine-tune individual colours in the grid afterwards. SayMenu - + S&ay @@ -7620,32 +9646,32 @@ Fine-tune individual colours in the grid afterwards. SequenceEdit - + Choose an action from the table 表の中からアクションを選択 - + Hit the key/combination of keys you want to set for this action このアクションに設定するキー(の組み合わせ)を押してください - + Invalid key 無効なキー - + Shortcut already in use by: ショートカットは既に使われています: - + Clear クリア - + Restore default デフォルトを復元 @@ -7678,56 +9704,92 @@ Fine-tune individual colours in the grid afterwards. 公開日時 + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts すべてのショートカットを元に戻す - + Do you really want to restore all default shortcuts? 本当にすべてのショートカットキーを元に戻しますか? - + Clear all default shortcuts すべてのショートカットをクリア - + Do you really want to clear all shortcuts? 本当にすべてのショートカットキーをクリアしますか? - + Section: セクション: - + Action: アクション: - + Shortcut: ショートカット: - + + Shortcut editor + + + + How to set custom shortcuts ショートカットキーの設定方法について(英語) - + Clear all shortcuts すべてのショートカットをクリア - + Search by shortcut name ショートカット名で検索 @@ -7783,12 +9845,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -7796,27 +9858,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds サウンドを有効 - + Current sounds theme: 現在のサウンドテーマ: - + Test system sound engine サウンドテスト - + Sound settings サウンド設定 - + Master volume 音量 @@ -7824,48 +9886,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended スポイラーシーズンが終了しました - + Deleting spoiler.xml. Please run Oracle spoiler.xmlを削除します。Oracleを起動してください。 - - + + Spoilers download failed スポイラーのダウンロードに失敗しました。 - + No internet connection インターネット接続がありません(´・ω・`) - + Error エラー - + Spoilers already up to date スポイラーリストは最新です。 - + No new spoilers added 新たなスポイラーはありません。 - + Spoilers have been updated! スポイラーが更新されました!! - + Last change: 最後の変更: @@ -7873,157 +9935,170 @@ Please check your shortcut settings! StableReleaseChannel - + Default デフォルト - + No reply received from the release update server. リリース更新サーバーから応答がありませんでした。 - + Invalid reply received from the release update server. リリース更新サーバーから無効な応答が受信されました。 - + No reply received from the tag update server. タグ更新サーバーからの応答がありませんでした。 - + Invalid reply received from the tag update server. タグ更新サーバーから無効な応答が受信されました。 + + StatsTally + + + Total Power + + + + + Total Toughness + + + StorageSettingsPage - - - + + + Success - + Cached card pictures have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + In-memory (currently loaded) card pictures have been reset. - + Card Picture Loader Caching Method: - + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. - + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. - + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. - + Delete Cached Images - + Delete Saved Images - + Clear In-Memory Images - + Card Picture Loader Cache Method - + Network Cache - + Filesystem - + In-Memory Picture Cache - + Network Cache Size: - + On-disk cache for downloaded pictures - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + In-memory cache for pictures not currently on screen - + Naming scheme: - + Day(s) @@ -8031,17 +10106,33 @@ Please check your shortcut settings! TabAccount - + + + My Reports + + + + Add to Buddy List フレンドリストに追加する名前を入力 - + Add to Ignore List 無視リストに追加する名前を入力 - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account アカウント @@ -8162,118 +10253,118 @@ Please check your shortcut settings! TabArchidekt - - - + + + Desc. - - + + AND - - + + Require ALL selected colors - - + + Deck name... - - + + Owner... - - + + Packages - - + + Advanced Filters - + Bracket: - - + + Any - - + + Contains card... - - + + Commander... - - + + Tag... - - + + Deck Size - + Cards: - - + + Asc. - + Sort by: - + Filter by: - + Display Settings - - + + Search - - + + Formats @@ -8286,42 +10377,42 @@ Please check your shortcut settings! TabCardArtRules - + Card: - + ProviderId: - + Mode: - + Reason: - + Type a card name... - + Add rule - + Remove rule - + Refresh @@ -8334,52 +10425,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info カード情報 - + Deck デッキ - + Filters フィルタ - + &View レイアウト - + Card Database - + Printing - + Visible 表示する - + Floating 別ウィンドウにする - + Reset layout レイアウトをリセット - + Deck: %1 デッキ: %1 @@ -8387,49 +10478,49 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - + &View - + Printing - + Visible - + Floating - + Reset layout @@ -8437,22 +10528,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -8460,139 +10551,237 @@ Please check your shortcut settings! TabDeckStorage - + Local file system ローカルのデッキ - + Server deck storage サーバーに保存されているデッキ - - + + Open in deck editor デッキエディターで開く - + Rename deck or folder - + Upload deck デッキをアップロード - + Download deck デッキをダウンロード - - - - + + + + New folder 新しいフォルダーを作成する - - + + Delete 削除 - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + + + + Error エラー - + Rename failed - - + + + Invalid deck file 無効なデッキファイル - + Enter deck name デッキの名前を入力 - + This decklist does not have a name. Please enter a name: このデッキリストには名前がありません。 名前を入力してください: - + Unnamed deck 無名のデッキ - + Failed to upload deck to server - + Delete local file ローカルファイルの削除 - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? 選択したデッキを削除してもよろしいですか? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: 新しいフォルダの名前: - + Deck Storage デッキストレージ @@ -8600,20 +10789,271 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage ビジュアルデッキストレージ - + Error - + Could not open deck at %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -8626,27 +11066,27 @@ Please enter a name: TabEdhRecMain - + &Cards - + Top Commanders - + Tags - + Search for a card ... - + Search @@ -8659,191 +11099,207 @@ Please enter a name: TabGame - - - + + + Replay リプレイ - - + + Game ゲーム - - + + Player List プレイヤーリスト - - + + + Invite + + + + + Card Info カード情報 - - + + Messages メッセージ - - + + Replay Timeline リプレイタイムライン - + &Phases フェイズ - + &Game ゲーム - + Next &phase 次のフェイズ - + Next phase with &action ターン起因処理をしながら次のフェイズ - + Next &turn 次のターン - + Reverse turn order ゲームのターン順を逆転する - + &Remove all local arrows 全ての対象指定を消す - + Rotate View Cl&ockwise 席を時計回りに回転 - + Rotate View Co&unterclockwise 席を反時計回りに回転 - + Game &information ゲーム情報 - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede 投了を撤回 - - - + + + &Concede 投了する - + &Leave game ゲームから離脱する - + C&lose replay リプレイを閉じる - + &Focus Chat - + &Say: 発言欄: - + Selected cards - + &View レイアウト - + Visible 表示する - + Floating 別ウィンドウにする - + Reset layout レイアウトをリセット - + Concede 投了する - + Are you sure you want to concede this game? 本当にこのゲームを投了しますか? - + Unconcede 投了を取り消す - + You have already conceded. Do you want to return to this game? あなたはすでに投了しています! 本当にゲームに復帰しますか? - + Leave game ゲームから離脱する - + Are you sure you want to leave this game? 本当にこのゲームから離脱しますか? - + A player has joined game #%1 プレイヤーがゲーム#%1に参加した。 - + %1 has joined the game %1 がゲームに参加した - + You have been kicked out of the game. ゲームからキックされました。 @@ -8859,158 +11315,158 @@ Please enter a name: TabLog - + Logs ログ - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName 時間;送信者名;送信者IP;メッセージ;対象ID;対象名 - + Room Logs ルームログ - + Game Logs ゲームログ - + Chat Logs チャットログ - - + + Error エラー - + You must select at least one filter. フィルターを選んで下さい。 - + You have to select a valid number of days to locate. 検索するには、有効な日数を選択する必要があります。 - + Username: ユーザー名: - + IP Address: IPアドレス: - + Game Name: ゲーム名: - + GameID: ゲームID: - + Message: メッセージ: - + Main Room メインルーム - + Game Room ゲームルーム - + Private Chat プライベートチャット - + Past X Days: X日経過: - + Today 今日 - + Last Hour 今 - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. 少なくとも一つのフィルタが必要です。 情報を入力すればするほど詳細な検索結果になります。 - + Get User Logs ユーザーログを取得 - + Clear Filters フィルタ解除 - + Filters フィルタ - + Log Locations ログの場所 - + Date Range 日付指定 - + Maximum Results - - + + Message History メッセージ履歴 - + Failed to collect message history information. メッセージ履歴情報の収集に失敗しました。 - + There are no messages for the selected filters. 選択したフィルタのためのメッセージはありません。 @@ -9018,218 +11474,558 @@ The more information you put in, the more specific your results will be. TabMessage - + Private &chat 個人チャット - + &Leave 退出する - + %1 - Private chat %1 - 個人チャット - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. このユーザーはあなたを無視しています。メインチャットのメッセージは表示されず、ゲームに参加することもできません。 - + Private message from 個人チャット: - + %1 has left the server. %1はサーバーから退出しました。 - + %1 has joined the server. %1がサーバーに参加しました。 + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system ローカルのリプレイ - + Server replay storage サーバーに保存されたリプレイ - - + + Watch replay リプレイを再生 - + Rename 名前変更 - - + + New folder 新しいフォルダ - - + + Delete 削除 - + Open replays folder リプレイフォルダを開く - + Download replay リプレイをダウンロード - + Toggle expiration lock 有効期限ロックの切り替え - + Get replay share code - - + + Look up replay by share code - + Rename local folder ローカルフォルダの名前を変更 - + Rename local file ローカルファイルの名前を変更 - + New name: 新しい名前: - + Error エラー - + Rename failed 変更失敗 - + Name of new folder: 新しいフォルダの名前: - + Delete local file ローカルファイルの削除 - + Are you sure you want to delete the selected files? 選択したファイルを削除してもよろしいですか? - + Are you sure you want to delete the selected replays? 選択したリプレイを削除してもよろしいですか? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay サーバーファイルの削除 @@ -9239,65 +12035,478 @@ The more information you put in, the more specific your results will be.リプレイ + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - - Friends - - - - - Online - - - - - Ignored - - - - + &Say: 発言欄: - + Chat チャット - + &Room ルーム - + &Leave room ルームから出る - + &Clear chat チャットをクリア - + Chat Settings... チャット設定... - + mentioned you. あなたにメンションした。 - + Click to view クリックで見る - + You are flooding the chat. Please wait a couple of seconds. あなたはチャットルームから弾かれました。少々お待ちください。 @@ -9305,35 +12514,35 @@ The more information you put in, the more specific your results will be. TabServer - + Server サーバー - - - - + + + + Error エラー - + Failed to join the server room: it doesn't exist on the server. サーバールームに参加できませんでした: サーバーに存在しません。 - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. サーバーからサーバールームに入室中の応答がありましたが、クライアントが表示できませんでした。クライアントを再起動してみてください。 - + You do not have the required permission to join this server room. あなたはこのサーバールームに参加するための権限がありません。 - + Failed to join the server room due to an unknown error: %1. 原因不明のエラーによってサーバールームに参加できませんでした: %1。 @@ -9341,97 +12550,173 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor デッキエディター - + Visual Deck Editor - + EDHRec - + Archidekt - + Home - + &Visual Deck Storage ビジュアルデッキストレージ - + Visual Database Display - + Server サーバー - + Account アカウント - + Deck Storage デッキストレージ - + Game Replays リプレイ - + Administration 管理 - + Logs ログ - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? よろしいですか? - + There are still open games. Are you sure you want to quit? ゲームがまだ開いています。本当に退出しますか? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view クリックで見る - + Your buddy %1 has signed on! %1がオンライン! - + Unknown Event 不明なイベント - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -9442,39 +12727,39 @@ To update your client, go to Help -> Check for Updates. クライアントを更新するには、ヘルプ→更新のチェックをクリックしてください。 - + Idle Timeout 放置タイムアウト - + You are about to be logged out due to inactivity. 非アクティブのためログアウトしています。 - + Promotion 昇格 - + You have been promoted. Please log out and back in for changes to take effect. あなたは昇格されました。変更を有効にするためにログインしなおして下さい。 - + Warned 警告 - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. 以下の理由によって警告を受けました: %1。 これらのような行動はご遠慮頂ますようお願い致します。質問がある場合は、モデレーターにプライベートメッセージを送信してください。 - + You have received the following message from the server. (custom messages like these could be untranslated) サーバーから次のメッセージを受け取りました。 @@ -9494,6 +12779,34 @@ Please refrain from engaging in this activity or further actions may be taken ag + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -9508,6 +12821,65 @@ Please refrain from engaging in this activity or further actions may be taken ag デッキを分析できませんでしたぁ…… + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -9579,57 +12951,52 @@ Please refrain from engaging in this activity or further actions may be taken ag UserCardArtSettingsDialog - + Card Art Settings - + Type a card name... - + Card name: - - Card ProviderId: + + Printing: - + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + Left margin (%): - + Right margin (%): - - Vertical offset: - - - - - Zoom: - - - - - Parameters - - - - - Preview - - - - + Remove Banner Card @@ -9637,216 +13004,266 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details ユーザー詳細 - + Private &chat プライベートチャット - + Show this user's &games このユーザーのゲームを表示 - + + View this user's &public decks + + + + Add to &buddy list フレンドリストに追加 - + Remove from &buddy list フレンドリストから削除 - + Add to &ignore list 無視リストに追加 - + Remove from &ignore list 無視リストから削除 - + Kick from &game ゲームからキックする - + + Report user + + + + Warn user ユーザーに警告する - + View user's war&n history ユーザーの警告履歴を見る - + Ban from &server サーバーからBANする - + View user's &ban history ユーザーのBAN履歴を見る - + &Promote user to moderator ユーザーを昇格する - + Dem&ote user from moderator ユーザーを降格する - + Promote user to &judge ユーザーをジャッジに昇格する - + Demote user from judge ユーザーをジャッジから降格する - + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes 管理者ノートを見る - - + Investigate user + + + + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games %1のゲーム - - - + + + Ban History BAN履歴 - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason BAN回数;モデレーター;BAN期間;BAN理由;可視理由 - + User has never been banned. ユーザーはBANされていません。 - + Failed to collect ban information. BAN情報の収集に失敗しました。 - - - + + + Warning History 警告履歴 - + Warning Time;Moderator;User Name;Reason 警告回数;モデレーター;ユーザー名;理由 - + User has never been warned. ユーザーは警告されていません。 - + Failed to collect warning information. 警告情報の収集に失敗しました。 - + Failed to get admin notes. 管理者ノートの取得に失敗しました。 - - + + Success 成功 - + Successfully promoted user. ユーザーを昇格しました。 - + Successfully demoted user. ユーザーを降格しました。 - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + Kick Player - + Are you sure you want to kick this player from the game? - - - + + + Failed 失敗 - + Failed to promote user. 昇格に失敗しました。 - + Failed to demote user. 降格に失敗しました。 - + Copy hash to clipboard クリップボードにハッシュをコピー - + Remove this user's messages このユーザーのメッセージを削除 @@ -9884,61 +13301,61 @@ Please refrain from engaging in this activity or further actions may be taken ag 管理者 - + Moderator モデレーター - + Registered user 登録ユーザー - - + + Unregistered user 未登録ユーザー - + Judge ジャッジ - + Unknown 不明 - + The entered password does not match your account. 入力されたパスワードはアカウントと一致しません。 - - - - + + + + Information 情報 - + User information updated. ユーザー情報が更新されました。 - - - - - - + + + + + - - - + + + + Error エラー @@ -9962,283 +13379,292 @@ Please refrain from engaging in this activity or further actions may be taken ag Edit Banner Card + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) %n年 - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format %10%n日%20 - + Enter Password パスワードを入力 - + Password verification is required in order to change your email address メールアドレスを変更するには、パスワード認証が必要です - - - + + + An error occurred while trying to update your user information. ユーザー情報の更新中にエラーが発生しました。 - + The selected card is blacklisted on this server or another error occurred. - + Banner card removed. - + Banner card updated. - + This server does not permit you to update your user informations. このサーバーはユーザーの情報を更新することができません。 - + Password changed. パスワードが変更されました。 - + This server does not permit you to change your password. このサーバーはパスワードを変更することができません。 - + The new password is too short. 新しいパスワードが短すぎます。 - + The old password is incorrect. 前のパスワードが一致しませんでした。 - + Avatar updated. アバターを更新しました。 - + This server does not permit you to update your avatar. このサーバーはアバターを更新することができません。 - + An error occured while trying to update your avatar. - - - This server does not permit you to update your user informations. - An error occured while trying to updater your avatar. - このサーバーはユーザーの情報を更新することができません。 - UserInfoPopup - - + + Games - + Chat - + Open private chat - + Profile - + View user profile - + Show this user's games - + + Invite + + + + + Invite to your game + + + + − Buddy - + Remove from buddy list - + + Buddy - + Add to buddy list - + − Ignore - + Remove from ignore list - + + Ignore - + Add to ignore list - + Ban - + Ban from server - + Warn - + Warn user - + Ban log - + View ban history - + Warn log - + View warning history - + Notes - + View admin notes - + − Mod - + Demote from moderator - + + Mod - + Promote to moderator - + − Judge - + Demote from judge - + + Judge - + Promote to judge - + Join game - + Spectate - - + + Loading games… - + Could not load games. - + No active games. @@ -10246,223 +13672,344 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings インターフェース全般設定 - + &Double-click cards to play them (instead of single-click) ダブルクリックでカードをプレイする (シングルクリックの代わり) - + &Clicking plays all selected cards (instead of just the clicked card) クリックしたカードだけでなく、選択したすべてのカードをプレイする。 - + &Play all nonlands onto the stack (not the battlefield) by default 土地でないカードをプレイする時、すぐに戦場に出さずにスタックに置く - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed 最後のカードが削除されたときにカード表示ウィンドウを閉じる - + Auto focus search bar when card view window is opened - + Annotate card text on tokens トークンのカードテキストを注釈にもつける - + Show selection count during drag selection - + Show total selection count - - Show subtype breakdown in selection tally + + + Disabled - + + + Enabled + + + + + + Automatic + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + Use tear-off menus, allowing right click menus to persist on screen ティアオフメニューを使用して、右クリックメニューを画面に表示したままにする - + Keep game chat focused when clicking in game (Note: disables card view search bar) - + Notifications settings 通知設定 - + Enable notifications in taskbar タスクバーの通知を有効 - + Notify in the taskbar for game events while you are spectating タスクバーに観戦中のゲームイベントを通知する - + Notify in the taskbar when users in your buddy list connect フレンドリストのユーザーが接続したときにタスクバーで通知する - + Animation settings アニメーション設定 - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation タップ/アンタップアニメーション - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings デッキエディタ/ストレージ設定 - + Open deck in new tab by default デフォルトでデッキを新しいタブで開く - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby ゲームロビーでビジュアルデッキストレージを使用 - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - - do nothing + + Do nothing - - ask to convert to .cod + + Ask to convert to .cod - - always convert to .cod + + Always convert to .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings リプレイ設定 - + Buffer time for backwards skip via shortcut: + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 ユーザーがサーバーに接続: %1 - + Users in this room: %1 ルームのユーザー数: %1 - + Buddies online: %1 / %2 フレンドオンライン: %1 / %2 - + Ignored users online: %1 / %2 無視ユーザーオンライン: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + Flip coin - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -10499,22 +14046,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... - + Save Filter - + Save all currently applied filters to a file - + Enter filename... @@ -10522,72 +14069,72 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterToolbarWidget - + Sort by - + Filter by - + Save and load filters - + Filter by exact card name - + Filter by card main-type - + Filter by card sub-type - + Filter by set - + Filter by format legality - + Save/Load - + Name - + Main Type - + Sub Type - + Sets - + Formats @@ -10689,7 +14236,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -10697,19 +14244,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -10755,28 +14302,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - - + + Visual - + Loading database ... - + Clear all filters - + Table @@ -10784,43 +14331,43 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat @@ -10836,12 +14383,12 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -10849,17 +14396,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter データベース内で最も近いものを検索し(自動提案付き)、Enter キーを押すと、優先印刷がデッキに追加されます。 @@ -10867,7 +14414,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageFolderDisplayWidget - + Deck Storage デッキストレージ @@ -10875,52 +14422,57 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + Show Color Identity - + Show Tags On Deck Previews - + + Show Upload Time + + + + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -10928,7 +14480,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -10936,22 +14488,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) デッキ名順 - + Sort Alphabetically (Filename) ファイル名順 - + Sort by Last Modified 更新日順 - + Sort by Last Loaded 最近使った順 @@ -10959,17 +14511,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... データベースをロード中... - + Refresh loaded files - + + Select decks to share + + + + Visual Deck Storage Settings @@ -10977,177 +14534,202 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? どの警告を送信しますか? - + Redact all messages from this user in all rooms すべてのルームでこのユーザーからのすべてのメッセージを編集します - + &OK OK - + &Cancel キャンセル - + Warn user for misconduct 不正行為をユーザーに警告 - - + + Error エラー - + User name to send a warning to can not be blank, please specify a user to warn. ユーザー名は空欄に出来ません。有効なユーザー名を選択して下さい。 - + Warning to use can not be blank, please select a valid warning to send. 警告は空欄に出来ません。有効な警告を選択して下さい。 + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top 選択したセットを一番上へ - + Move selected set up 選択中のセットを上へ - + Move selected set down 選択中のセットを下へ - + Move selected set to the bottom 選択したセットを一番下へ - - Search by set name, code, or type - セット名、略称、タイプで検索 - - - + Default order 初期順に戻す - + Restore original art priority order 元のアートの優先順位を復元する - + Enable all sets すべてのセットを有効 - + Disable all sets すべてのセットを無効 - + Enable selected set(s) 選択したセットを有効 - + Disable selected set(s) 選択したセットを無効 - + Deck Editor デッキエディター - + Use CTRL+A to select all sets in the view. Ctrl + A キーですべてのセットを選択します。 - + Only cards in enabled sets will appear in the card list of the deck editor. 有効にしたセットのカードのみがデッキエディタのカードリストに表示されます。 - + Image priority is decided in the following order: 画像の優先順位は、次の順序で決定されます: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki 最優先はCUSTOMフォルダ(%1)、以降はこのダイアログで有効化されたセットの並び順 - + Include cards rebalanced for Alchemy [requires restart] - + Card Art カードアート - + + Search by set name, code, type, or release date + + + + How to use custom card art カスタムカードアートの使い方 - + Hints ヒント - + Note 注意 - + Sorting by column allows you to find a set while not changing set priority. 列でソートすると、セットの優先度設定を変えずにセットを探すことができます。 - + To enable ordering again, click the column header until this message disappears. 優先度設定を行うには、このメッセージが消えるまで同じ列見出しをクリックしてください。 - + Use the current sorting as the set priority instead 現在のソートを優先度設定として使用する - + Sorts the set priority using the same column 同じ列を使用して優先度をソートする - + Manage sets セットを管理 @@ -11155,93 +14737,85 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped グループなし - + Group by Type タイプ別グループ - + Group by Mana Value マナ・コスト別グループ - + Group by Color 色別グループ - + Unsorted 順不同 - + Sort by Name 名前順 - + Sort by Type タイプ順 - + Sort by Mana Cost マナ・コスト順 - + Sort by Colors 色順 - + Sort by P/T P/T順 - + Sort by Set セット順 - + shuffle when closing 閉じる時に切り直す - + pile view タイプ別に重ねて表示 - - i18n - - - English - 日本語 (Japanese) - - main - + Connect on startup 起動時に接続 - + Debug to file @@ -12025,6 +15599,16 @@ Please refrain from engaging in this activity or further actions may be taken ag Graveyard 墓地 + + + Report Queue + + + + + Moderation + + diff --git a/cockatrice/translations/cockatrice_ko.ts b/cockatrice/translations/cockatrice_ko.ts index 4b06f2a5e..1a28a9f0b 100644 --- a/cockatrice/translations/cockatrice_ko.ts +++ b/cockatrice/translations/cockatrice_ko.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... 카운터 설정... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter 카운터 설정 - + New value for counter '%1': 카운터 '%1'의 값을 지정해 주세요: @@ -36,64 +36,113 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. - + There are no cards in your deck to be exported + + AccountSetupPage + + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + AddAnalyticsPanelDialog @@ -105,12 +154,12 @@ Please check that the directory is writable and try again. AdminNotesDialog - + Update Notes - + Admin Notes for %1 @@ -136,218 +185,304 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error 오류 - + Could not create themes directory at '%1'. - + Theme settings 테마 설정 - + Current theme: 현재 테마: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering 카드 렌더링 - + Display card names on cards having a picture 이미지가 존재하는 카드에도 카드 이름 표시 - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Light - + Dark - + + System - + + Show all playmats + + + + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + Active theme palette: - + + Active theme style: + + + + + Qt widget style saved to this theme ("System" lets the application decide) + + + + Edit theme palette - + Home tab settings - + Display card name of background in bottom right - + + Dim the home tab background + + + + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + Styling settings - + Style user list - + Card printings - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over 마우스 오버 시 카드 확대 - + Use rounded card corners - + Card layout - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout 손 레이아웃 - + Display hand horizontally (wastes space) 손의 카드를 가로로 정렬 - + Enable left justification 손의 카드를 좌측으로 정렬 - + Table grid layout 테이블 격자 레이아웃 - + Invert vertical coordinate 전장의 상하배치를 전환 (대지를 앞에 배치) - + Minimum player count for multi-column layout: 다열 레이아웃를 위한 최소 플레이어 인원 (4명 이상 권장) - + Maximum font size for information displayed on cards: 카드 정보에 표시할 폰트의 최대 크기: @@ -355,12 +490,12 @@ Please check that the directory is writable and try again. ArchidektApiResponseDeckDisplayWidget - + Back to results - + Open Deck in Deck Editor @@ -386,112 +521,112 @@ Please check that the directory is writable and try again. BanDialog - + ban &user name 사용자 계정 추방 - + ban &IP address IP 주소 추방 - + ban client I&D 클라이언트 ID 추방 - + Ban type 추방 유형 - + &permanent ban 영구 추방 - + &temporary ban 기간제 추방 - + &Days: 일: - + &Hours: 시간: - + &Minutes: 분: - + Duration of the ban 추방 기간 - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. 추방 사유를 적어주세요. 해당 사유는 관리자들만 볼 수 있고 추방 당한 사용자는 볼 수 없습니다. - + Please enter the reason for the ban that will be visible to the banned person. 추방된 사용자가 보게 될 추방 사유를 적어주세요. - + Redact all messages from this user in all rooms - + &OK 확인 - + &Cancel 취소 - + Ban user from server 사용자를 서버에서 추방 - - - - + + + + Error 오류 - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. 추방의 유형을 선택해 주세요. - + You must have a value in the name ban when selecting the name ban checkbox. 사용자명 추방을 하기 위해선 사용자명을 입력하셔야 합니다. - + You must have a value in the ip ban when selecting the ip ban checkbox. 아이피 추방을 하기 위해선 아이피 주소를 입력하셔야 합니다. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. 클라이언트 ID 추방을 하기 위해선 클라이언트 ID를 입력하셔야 합니다. @@ -499,22 +634,22 @@ This is only saved for moderators and cannot be seen by the banned person. BetaReleaseChannel - + Beta - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the file update server. @@ -522,7 +657,20 @@ This is only saved for moderators and cannot be seen by the banned person. CardArtPreviewWidget - + + Banner preview + + + + + + + + Zoom %1× + + + + No card selected @@ -530,22 +678,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardArtRulesModel - + Card - + ProviderId - + Mode - + Reason @@ -553,65 +701,209 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name 카드 이름 - + Sets 확장판 - + Mana cost 마나 비용 - + Card type 카드 유형 - + P/T 공/방 - + Color(s) 색(들) + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + CardDatabaseView - + Add to Deck - + Add to Sideboard - + Select Printing - + + Add to Zone + + + + + Create &new zone... + + + + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards @@ -721,22 +1013,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoFrameWidget - + Image - + Description - + Both - + View transformation @@ -744,22 +1036,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -767,27 +1059,27 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoTextWidget - + Name: - + Set: - + Collector Number: - + Related cards: - + Unknown card: @@ -795,129 +1087,129 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Reduce life by power - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -938,7 +1230,7 @@ This is only saved for moderators and cannot be seen by the banned person. CardSizeWidget - + Card Size @@ -1078,6 +1370,24 @@ This is only saved for moderators and cannot be seen by the banned person. + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser @@ -1107,280 +1417,382 @@ This is only saved for moderators and cannot be seen by the banned person. + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + ConnectionController - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - - - - - - - - - - - + + + + + + + + + + - - + + + Error - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + Server timeout - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Information - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -1388,113 +1800,113 @@ To update your client, go to Help -> Check for Updates. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - + Requesting forgotten password to %1 as %2... - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. @@ -1544,7 +1956,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1560,17 +1972,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add card to &maindeck - + Add card to &sideboard @@ -1578,97 +1990,158 @@ To update your client, go to Help -> Check for Updates. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + Format: - + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1676,17 +2149,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1694,114 +2167,119 @@ To update your client, go to Help -> Check for Updates. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + + Share deck... + + + + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1809,7 +2287,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1817,110 +2295,152 @@ To update your client, go to Help -> Check for Updates. DeckEditorSettingsPage - - + + Update Spoilers - + Success 성공 - + Download URLs have been reset. - + Add URL URL 추가 - - + + URL: URL: - - + + Edit URL URL 수정 - + Add New URL 새 URL 추가 - + Remove URL URL 제거 - + Updating... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Reset Download URLs @@ -1928,32 +2448,32 @@ To update your client, go to Help -> Check for Updates. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1961,27 +2481,27 @@ To update your client, go to Help -> Check for Updates. DeckListModel - + Count - + Set - + Number 카드 장수 - + Provider ID - + Card 카드 이름 @@ -2002,17 +2522,22 @@ To update your client, go to Help -> Check for Updates. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -2020,7 +2545,7 @@ To update your client, go to Help -> Check for Updates. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -2028,62 +2553,62 @@ To update your client, go to Help -> Check for Updates. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2091,94 +2616,109 @@ To update your client, go to Help -> Check for Updates. DeckPreviewWidget - + Banner Card - + Open in deck editor - + + Share deck... + + + + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Rename deck + + + + + Rename file + + + + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -2186,57 +2726,102 @@ To update your client, go to Help -> Check for Updates. DeckStateManager - + Rename deck to "%1" from "%2" - + Updated comments (was %1 chars, now %2 chars) - + Set banner card to %1 (%2) - + + Set playmat to %1 + + + + Tags changed - + Set format to %1 - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + %1 1 × "%2" (%3) - + Added - + Removed @@ -2258,94 +2843,122 @@ To update your client, go to Help -> Check for Updates. DeckViewContainer - + Load deck... 덱 불러오기 - + Load remote deck... 원격 덱 불러오기 - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start 시작 준비 - + Force start - + Sideboard unlocked 사이드보드 카드 교체 가능 - + Sideboard locked 사이드보드 카드 교체 불가능 - - + + Error 오류 - + The selected file could not be loaded. 지정하신 파일을 불러올 수 없었습니다. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? - + Remember and automatically apply choice in the future @@ -2358,143 +2971,143 @@ This will kick all non-ready players from the game. 다운로드중... - + Known Hosts 기록한 호스트 - + Delete the currently selected saved server - + Refresh the server list with known public servers - + New Host 새로운 서버 - + Name: 호스트명: - + &Host: 호스트 IP: - + &Port: 포트: - + Player &name: 사용자명: - + P&assword: 비밀번호: - + &Save password 비밀번호 저장 - + A&uto connect 자동연결 - + Automatically connect to the most recent login when Cockatrice opens 코카트리스를 실행할 때 최근에 로그인한 서버로 연결합니다. - + If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage - + Reset Password - + Forgot password? - + &Connect - + Server 서버 - + Login 로그인 - + Server Contact - + Connect to Server - + Server URL - + Communication Port - + Unique Server Name - + Connection Warning - + You need to name your new connection profile. - + Connect Warning - + The player name can't be empty. 사용자명은 비어있을 수 없습니다. @@ -2502,122 +3115,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings 설정 기억하기 - + &Description: 게임 이름: - + P&layers: 게임 참가 인원: - + General - + Game type 게임 종류 - + &Password: 비밀번호: - + Only &buddies can join 친구만 입장 가능 - + Only &registered users can join 서버에 가입한 사용자만 입장 가능 - + Joining restrictions 입장 제한 - + &Spectators can watch 관전자 허용 - + Spectators &need a password to watch 관전자 입장 시 비밀번호 필요 - + Spectators can &chat 관전자 대화 가능 - + Spectators can see &hands 관전자에게 플레이어의 손 공개 - + Create game as spectator - + Spectators 관전자 - + Starting life total: - + Open decklists in lobby - + Create game as judge - + Game setup options - + &Clear 설정 초기화 - + Create game 게임 만들기 - + Game information 게임 정보 - + Error 오류 - + Server error. 서버 오류. @@ -2625,97 +3238,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: 토큰 이름: - + Token 토큰 - + C&olor: 토큰 색: - + white 백색 - + blue 청색 - + black 흑색 - + red 적색 - + green 녹색 - + multicolor 다색 - + colorless 무색 - + &P/T: 공/방 - + &Annotation: 주석: - + &Destroy token when it leaves the table 토큰이 전장을 떠나면 게임에서 제거됨 - + Create face-down (Only hides name) - + Token data 토큰 정보 - + Show &all tokens 모든 토큰 보기 - + Show tokens from this &deck 이 덱에서 사용하는 토큰 보기 - + Choose token from list 목록에서 토큰 선택 - + Create token 토큰 생성 @@ -2723,53 +3336,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + ✖ - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2778,7 +3391,7 @@ This will kick all non-ready players from the game. DlgEditAvatar - + No image chosen. 이미지 없음 @@ -2790,27 +3403,32 @@ To remove your current avatar, confirm without choosing a new image. 현재 아바타를 삭제하시려면 이미지를 선택하지 않고 확인을 하시면 됩니다. - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... 찾아보기 - + Change avatar 아바타 변경 - + Open Image 이미지 불러오기 - + Image Files (*.png *.jpg *.bmp) 이미지 파일 (*.png *.jpg *.bmp) - + Invalid image chosen. 잘못된 이미지를 선택하셨습니다. @@ -2836,38 +3454,38 @@ To remove your current avatar, confirm without choosing a new image. DlgEditPassword - + Old password: 예전 비밀번호: - + New password: 새로운 비밀번호: - + Confirm new password: 새로운 비밀번호 확인: - + Change password 비밀번호 변경 - - + + Error 오류 - + Your password is too short. - + The new passwords don't match. 새로운 비밀번호가 일치하지 않습니다. @@ -2875,93 +3493,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: 토큰 이름: - + C&olor: 토큰 색: - + white 백색 - + blue 청색 - + black 흑색 - + red 적색 - + green 녹색 - + multicolor 다색 - + colorless 무색 - + &P/T: 공/방 - + &Annotation: 주석: - + Token data 토큰 정보 - - + + Add token 토큰 추가 - + Remove token 토큰 삭제 - + Edit custom tokens - + Please enter the name of the token: 토큰의 이름을 입력해 주세요. - + Error 오류 - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. @@ -3039,7 +3657,8 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games that have started + Hide started games + Hide games that have started @@ -3075,8 +3694,9 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - &Creator name: - 방장 이름: + &Host name: + &Creator name: + @@ -3142,52 +3762,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning - + A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. - + &Host: 호스트: - + &Port: 포트: - + Player &name: 사용자명: - + Email: 이메일: - + Reset Password Challenge - + Reset Password Challenge Error - + The email address can't be empty. @@ -3195,37 +3815,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. - + &Host: 호스트: - + &Port: 포트: - + Player &name: 사용자명: - + Reset Password Request - + Reset Password Error - + The player name can't be empty. @@ -3233,94 +3853,127 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning - + A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. - + &Host: 호스트: - + &Port: 포트: - + Player &name: 사용자명: - + Token: 토큰: - - + + New Password: 새로운 비밀번호: - + Reset Password - + The player name can't be empty. 사용자명은 비어있을 수 없습니다. - - - - + + + + Reset Password Error - + The token can't be empty. - + The new password can't be empty. - + Error 오류 - + Your password is too short. - + The passwords do not match. 비밀번호가 일치하지 않습니다. + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck @@ -3404,165 +4057,566 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLocalGameOptions - + Players: - + General - + Starting life total: - + Game setup options - + Remember settings - + Local game options + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: 호스트 IP: - + &Port: 포트: - + Player &name: 사용자명: - + P&assword: 비밀번호: - + Password (again): 비밀번호 확인: - + Email: 이메일: - + Email (again): 이메일 확인: - + Country: 국가: - + Undefined 미지정 - + Real name: 실명: - + Register to server 서버에 가입 - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning 가입 실패 - + Your password is too short. - + Your passwords do not match, please try again. 입력된 비밀번호가 서로 다릅니다. 다시 시도하여 주십시오. - + Your email addresses do not match, please try again. 입력된 이메일이 서로 다릅니다. 다시 시도하여 주십시오. - + The player name can't be empty. 사용자명은 비어있을 수 없습니다. + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice @@ -3628,46 +4682,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database 데이터베이스를 불러올 때 미상의 오류가 발생하였습니다. - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - 카드 데이터베이스가 손상되었습니다. - -잘못된 데이터베이스를 사용 시 코카트리스에 오류가 발생 할 수 있습니다. - -오라클을 다시 실행해서 카드 데이터베이스를 갱신할 수 있습니다. - -데이터베이스 경로를 다시 설정하시겠습니까? - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - 카드 데이터베이스가 너무 오래되었습니다. - -카드 정보나 이미지를 불러올때 오류가 발생할 수 있습니다. - -오라클을 다시 실행해서 카드 데이터베이스를 갱신하는 것을 권장합니다. - -데이터베이스 경로를 다시 설정하시겠습니까? - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3676,7 +4696,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3685,7 +4705,7 @@ Would you like to change your database location setting? 데이터베이스 경로를 다시 설정하시겠습니까? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3694,7 +4714,7 @@ Would you like to change your database location setting? 데이터베이스 경로를 다시 설정하시겠습니까? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3703,106 +4723,237 @@ Would you like to change your database location setting? - - - + + + Error 오류 - + The path to your deck directory is invalid. Would you like to go back and set the correct path? 덱 파일을 보관하는 디렉토리의 경로가 잘못되었습니다. 경로를 다시 설정하시겠습니까? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? 카드 이미지 파일을 보관하는 디렉토리의 경로가 잘못되었습니다. 경로를 다시 설정하시겠습니까? - + Settings 환경설정 - + + Search settings... + + + + + OK + + + + General 일반 - + Appearance 외형 - + User Interface 인터페이스 - + Storage - + Card Sources - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat 대화 - + Sound 소리 - + Shortcuts 단축키 + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3810,17 +4961,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next 다음 - + Previous 이전 - + Tip of the Day @@ -3828,165 +4979,175 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel - + Reinstall 재설치 - + Cancel Download 다운로드 취소 - + Open Download Page 다운로드 페이지 열기 - + Check for Client Updates - - - - + + + + Error 오류 - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. 코카트리스가 SSL 지원 없이 컴파일되어 자동 업데이트를 할 수 없습니다. 공식 홈페이지에서 수동으로 다운 받아주세요. - + Downloading update: %1 - + Checking for updates... 업데이트 확인중... - + Finished checking for updates - + No Update Available - + Cockatrice is up to date! Cockatrice는 최신 버젼입니다! - + You are already running the latest version available in the chosen release channel. - + Current version 현재 버젼 - + Selected release channel - - + + Update Available - - + + A new version of Cockatrice is available! - - + + New version 새로운 버젼 - - + + Released - - + + Changelog 변경점 - + Do you want to update now? 업데이트 하시겠습니까? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error 업데이트 오류 - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Installing... 설치중... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location 다운로드 위치 @@ -3994,17 +5155,17 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing - + Copy to clipboard - + Debug Log 디버그 로그 @@ -4242,7 +5403,7 @@ You may have to manually download the new version. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -4250,7 +5411,7 @@ You may have to manually download the new version. FilterBuilder - + Type your filter here @@ -4258,45 +5419,88 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -4304,140 +5508,156 @@ You may have to manually download the new version. GameSelector - - - - - - - - - + + + + + + + + + Error 오류 - + Please join the appropriate room first. 해당 게임이 열려있는 채널에 먼저 들어가시기 바랍니다. - + Wrong password. 잘못된 비밀번호를 입력하셨습니다. - + Spectators are not allowed in this game. 관전이 허용되지 않은 게임입니다. - + The game is already full. 게임 인원이 전부 찼습니다. - + The game does not exist any more. 게임이 더 이상 존재하지 않습니다. - + This game is only open to registered users. 서버에 가입한 사용자만 참가 할 수 있습니다. - + This game is only open to its creator's buddies. 방장의 친구 목록에 등록된 사용자만 참가 할 수 있습니다. - + You are being ignored by the creator of this game. 방장이 당신을 차단하였습니다. - + Join Game - + Spectate Game - + Game Information - + + Cop&y game link + + + + Join Game as Judge - + Spectate Game as Judge - + + Join game 게임 참가 - - Password: - 비밀번호: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. 해당 게임이 열려있는 채널에 먼저 들어가시기 바랍니다. - + &Filter games 게임 필터 - + C&lear filter 필터 해제 - + C&reate 게임 생성 - + &Join 게임 참가 - + Join as judge - + J&oin as spectator 관전자로 참가하기 - + Join as judge spectator - + Games shown: %1 / %2 - + Games 게임 목록 @@ -4478,105 +5698,105 @@ You may have to manually download the new version. GamesModel - + >1 day >1 일 - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password 비밀번호 필요 - + buddies only 친구만 - + reg. users only 가입한 사용자 전용 - + open decklists - - + + can chat 관전자 대화 가능 - + see hands 손 공개 - + can see hands 관전자에게 손 공개 - + not allowed 허용 안됨 - + Room 게임 - + Age - + Description 게임 이름 - - Creator - 방장 + + Host + - + Type - + Restrictions 입장 제한 - + Players 플레이어 - + Spectators 관전자 @@ -4584,158 +5804,275 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + English + 한국어 (Korean) + + + + + + + + + Choose path 경로 선택 - + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> + + + + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + Language settings - + Language: 언어: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + Version settings - + Card database - + Startup settings - + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) - + Paths 디렉토리 경로 - + How to help with translations - + Decks directory: 덱 파일 경로: - + Filters directory: - + Replays directory: 리플레이 파일 경로: - + Pictures directory: 카드 이미지 경로: - + Card database: 카드 데이터베이스 경로: - + Custom database directory: - + Token database: 토큰 데이터베이스 경로: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client 서버에서 서포트하는 기능이 클라이언트에 존재하지 않을 경우 알림 - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup - + Last update check on %1 (%2 days ago) @@ -4743,47 +6080,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4877,59 +6214,353 @@ You may have to manually download the new version. + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu @@ -5147,410 +6778,431 @@ You may have to manually download the new version. MainWindow - - + + Player %1 플레이어 %1 - + Load replay 리플레이 불러오기 - + About Cockatrice 코카트리스에 관하여 - + Version - + Cockatrice Webpage 코카트리스 홈페이지 - + Project Manager: 현 프로젝트 매니저: - + Past Project Managers: 전 프로젝트 매니저: - + Developers: 개발진: - + Our Developers 기여자 목록 - + Help Develop! 개발을 도와주세요! - + Translators: 번역진: - + Our Translators 번역진: - + Help Translate! 번역을 도와주세요! - + Support: 기술 지원: - + Report an Issue 문제점 보고 - + Troubleshooting 문제 해결 - + F.A.Q. 자주 묻는 질문 - - + + Error 오류 - + &Connect... 서버로 연결 - + &Disconnect 서버와의 연결 해제 - + Start &local game... 오프라인 게임 시작 - + &Watch replay... 리플레이 재생 - + &Full screen 전체 화면 - + &Register to server... 서버에 가입 - + &Restore password... - + &Settings... 환경설정 - + &Exit 끝내기 - + A&ctions 액션 - + &Cockatrice 코카트리스 - + C&ard Database - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder 커스텀 이미지 폴더 열기 - + Open custom sets folder 커스텀 셋 폴더 열기 - + Add custom sets/cards 커스텀 셋/카드 추가 - + Reload card database - + Tabs - + &Help 도움말 - + &About Cockatrice 코카트리스에 관하여 - + &Tip of the Day - + Check for Client Updates 클라이언트 업데이트 확인 - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + + Re-run Onboarding Wizard... + + + + Show/Hide - + + Sign-in cancelled + + + + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + + Are you sure? + 확실하십니까? + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database 카드 데이터베이스 - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes 예 - - + + No 아니오 - + Open settings 설정 열기 - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + Yes, always enable - + View sets 셋 보기 - + Welcome 환영합니다 - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - + Information 알림 - + A card database update is already running. 이미 카드 데이터베이스 업데이트가 진행 중입니다. - + Unable to run the card database updater: 카드 데이터베이스 업데이트를 진행할 수 없습니다: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. @@ -5755,615 +7407,615 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play 전장에 - + from their graveyard - + from exile 추방 영역에 - + from their hand 자신의 손에 - + the top card of %1's library %1의 서고 맨 위의 카드 - + the top card of their library - + from the top of %1's library %1의 서고 맨 위에 - + from the top of their library - + the bottom card of %1's library %1의 서고 맨 밑의 카드 - + the bottom card of their library - + from the bottom of %1's library %1의 서고 맨 밑에 - + from the bottom of their library - + from %1's library %1의 서고에 - + from their library - + from sideboard 사이드보드에 - + from the stack 스택에 - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1이(가) %2 맨 위 카드를 공개합니다. - + %1 is not revealing the top card %2 any longer. %1이(가) %2 맨 위 카드를 더 이상 공개하지 않습니다. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. - + %1 has conceded the game. - + %1 has unconceded the game. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. %1님이 자신의 %2에서 자신의 %3을(를) 가리켰습니다. - + %1 points from their %2 to %3's %4. %1님이 자신의 %2에서 %3님의 %4을(를) 가리켰습니다. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. %1님이 토큰 생성: %2%3. - + %1 has loaded a deck (%2). %1이(가) 덱을 불러왔습니다. (해시값: %2) - + %1 has loaded a deck with %2 sideboard cards (%3). %1이(가) %2장의 사이드보드가 있는 덱을 불러왔습니다. (해시값: %3) - + %1 destroys %2. - + a card 카드 한 장 - + %1 gives %2 control over %3. %1이(가) %3의 조종권을 %2에게 넘깁니다. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1이(가) %3서 %2을(를) 전장에 놓았습니다. - + %1 puts %2%3 into their graveyard face down. - + %1 puts %2%3 into their graveyard. - + %1 exiles %2%3 face down. - + %1 exiles %2%3. %1이(가) %3 있던 %2을(를) 추방합니다. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. %1이(가) %3 있던 %2을(를) 서고 맨 밑에 넣었습니다. - + %1 plays %2%3 face down. - + %1 plays %2%3. %1이(가) %3서 %2을(를) 플레이 하였습니다. - + %1 moves %2%3 to custom zone '%4' face down. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. 게임이 종료되었습니다. - + The game has started. 게임이 시작되었습니다. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1님이 게임에 입장했습니다. - + %1 is now watching the game. %1이(가) 관전을 시작하였습니다. - + You have been kicked out of the game. 게임에서 강제 퇴장 당하였습니다. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. %1님이 게임 시작 준비를 끝냈습니다. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. %1님이 %2을(를) 공개. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads 앞면 - + Tails 뒷면 - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. %1의 차례입니다. - + %1 sets annotation of %2 to %3. - + %1 places %2 %3%4 counter(s) on %5 (now %6). - + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + %1 failed to undo their last draw. - + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. %1님이 자신의 지속물을 탭. - + %1 untaps their permanents. %1님이 자신의 지속물을 언탭. - + %1 taps %2. %1님이 %2을(를) 탭. - + %1 untaps %2. %1님이 %2을(를) 언탭. - + %1 shuffles %2. %1님이 %2를 셔플했습니다. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. %1님이 %2을(를) 뗴어냈습니다. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -6371,115 +8023,125 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message 메세지 추가 - - + + Message: 메세지: - + Edit message - + Chat settings 대화 설정 - + Custom alert words 키워드 알림 - + Enable chat mentions 대화 중 본인의 사용자명 멘션 시 해당 문장을 강조 - + Enable mention completer 멘션 자동완성 기능 - + In-game message macros 게임 내 대화 매크로 - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users 서버에 가입하지 않은 사용자의 채널 대화 차단 - + Ignore private messages sent by unregistered users 서버에 가입하지 않은 사용자가 보낸 1:1 대화 차단 - + Ignore private messages sent by non-buddy users - - + + Invert text color 문장 색 반전 - + Enable desktop notifications for private messages 1:1 대화를 받을 시 데스크탑 알림 설정 - + Enable desktop notification for mentions 자신의 사용자명 멘션 시 데스크탑 알림 설정 - + Enable room message history on join 채널 입장 시 이전 대화 기록 표시 - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) (16진수 색상 코드) - + Separate words with a space, alphanumeric characters only 각 단어마다 스페이스바로 띄어써주세요. 문자 및 숫자만 가능합니다. @@ -6487,42 +8149,42 @@ Cockatrice will now reload the card database. MoveMenu - + Move to - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + T&able - + &Hand - + &Graveyard - + &Exile @@ -6601,148 +8263,134 @@ Cockatrice will now reload the card database. PaletteEditorDialog - - + + Reset - - - Apply - - - - - - Save && Apply - - - - + ▼ Edit Palette - - + + ▶ Edit Palette - + Palette Editor — %1 - + <b>Palette Editor</b> &nbsp;·&nbsp; %1 - - This theme ships no default palette files - - - - - Replace current colours with the theme author's defaults - - - - - Switch between the light and dark palette files + + + Save - Editing: + Delete this scheme's custom palette and revert to the theme default (or the application palette) - Show or hide the per-role colour grid for manual tweaks + Switch between the light and dark palette files + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + ↺ Revert to theme default - + Discard unsaved edits and restore the last saved palette - - Preview this palette without saving to disk + + Cannot save: this theme has no writable directory - + Write palette-%1.toml and reload the theme - - Cannot save: this theme has no directory on disk - - - - + + Save failed - + + Could not write %1 to: %2 - - - No default found - - - - - No default palette file found for the "%1" scheme. - - PaletteGridWidget - + Active - + Disabled - + Inactive - + Normal interactive state - + Widget is disabled / not interactive - + Window is in background / unfocused + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + Phase @@ -6865,30 +8513,21 @@ Cockatrice will now reload the card database. 종료단 - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - ko - - PlayerActions - - - - + + + + grave - - - - + + + + exile @@ -7007,16 +8646,272 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters + + PlaymatCollectionDialog + + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + PrintingDisabledInfoWidget @@ -7037,12 +8932,12 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelector - + Display Navigation Buttons - + Printing Selector @@ -7050,25 +8945,65 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -7099,28 +9034,28 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -7136,95 +9071,103 @@ This setting means you'll only see the default printing for each card, instead o PtMenu - + Power / toughness - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services 서비스 - + Hide %1 %1 가리기 - + Hide Others 기타 가리기 - + Show All 모두 보기 - + Preferences... 환경설정... - + Quit %1 %1 종료 - + About %1 %1에 관하여 @@ -7232,17 +9175,17 @@ This setting means you'll only see the default printing for each card, instead o QObject - + Cockatrice card database (*.xml) 코카트리스 카드 데이터베이스 파일 (*.xml) - + All files (*.*) 모든 파일 (*.*) - + Cockatrice replays (*.cor) 코카트리스 리플레이 파일 (*.cor) @@ -7262,17 +9205,22 @@ This setting means you'll only see the default printing for each card, instead o 토큰 - + + Maybeboard + + + + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7283,7 +9231,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7294,100 +9242,110 @@ Are you sure you would like to disable this feature? - + Confirm Change + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -7395,121 +9353,189 @@ Are you sure you would like to disable this feature? QuickSetupPanel - + %1% - + <b>Quick Setup</b> - + Generate all palette roles automatically from a single accent colour - + Accent: - + Primary hue. Used directly for highlights and links. At high intensity it also tints buttons and backgrounds. - + Intensity: - + Subtle - + Full colour - + 0–30 Subtle tint — only highlights and links change hue 30–70 Accented — buttons, tooltips, and borders join in 70–100 Full colour — backgrounds, everything - + 70% - - - Generate ↓ - - - - - Derive all palette roles from the accent colour above. -Fine-tune individual colours in the grid afterwards. - - RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name 덱 이름 - + ID ID - + Upload time 업로드 시간 + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID ID - + Name 게임 이름 - + Players 플레이어 - + Keep 보존 - + Time started 게임 시작 시간 - + Duration (sec) 게임 길이 + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu @@ -7551,37 +9577,37 @@ Fine-tune individual colours in the grid afterwards. RoomSelector - + Rooms 채널 목록 - + Joi&n 채널에 참가 - + Room 게임 - + Description 설명 - + Permissions 권한 - + Players 플레이어 - + Games 게임 @@ -7589,7 +9615,7 @@ Fine-tune individual colours in the grid afterwards. SayMenu - + S&ay @@ -7597,32 +9623,32 @@ Fine-tune individual colours in the grid afterwards. SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - + Invalid key - + Shortcut already in use by: - + Clear - + Restore default @@ -7655,56 +9681,92 @@ Fine-tune individual colours in the grid afterwards. 발매 일자 + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + + Shortcut editor + + + + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -7758,12 +9820,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -7771,27 +9833,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds 음향 효과 켜기 - + Current sounds theme: 현재 소리 테마: - + Test system sound engine 시스템 사운드 엔진 테스트 - + Sound settings 음향 설정 - + Master volume 주 음량 @@ -7799,48 +9861,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -7848,157 +9910,170 @@ Please check your shortcut settings! StableReleaseChannel - + Default - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. + + StatsTally + + + Total Power + + + + + Total Toughness + + + StorageSettingsPage - - - + + + Success - + Cached card pictures have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + In-memory (currently loaded) card pictures have been reset. - + Card Picture Loader Caching Method: - + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. - + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. - + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. - + Delete Cached Images - + Delete Saved Images - + Clear In-Memory Images - + Card Picture Loader Cache Method - + Network Cache - + Filesystem - + In-Memory Picture Cache - + Network Cache Size: - + On-disk cache for downloaded pictures - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + In-memory cache for pictures not currently on screen - + Naming scheme: - + Day(s) @@ -8006,17 +10081,33 @@ Please check your shortcut settings! TabAccount - + + + My Reports + + + + Add to Buddy List - + Add to Ignore List - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account @@ -8137,118 +10228,118 @@ Please check your shortcut settings! TabArchidekt - - - + + + Desc. - - + + AND - - + + Require ALL selected colors - - + + Deck name... - - + + Owner... - - + + Packages - - + + Advanced Filters - + Bracket: - - + + Any - - + + Contains card... - - + + Commander... - - + + Tag... - - + + Deck Size - + Cards: - - + + Asc. - + Sort by: - + Filter by: - + Display Settings - - + + Search - - + + Formats @@ -8261,42 +10352,42 @@ Please check your shortcut settings! TabCardArtRules - + Card: - + ProviderId: - + Mode: - + Reason: - + Type a card name... - + Add rule - + Remove rule - + Refresh @@ -8309,52 +10400,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info 카드 정보 - + Deck 덱 - + Filters 카드 필터 - + &View &보기 - + Card Database - + Printing - + Visible 표시 - + Floating 띄우기 - + Reset layout 화면 레이아웃 초기화 - + Deck: %1 덱 편집기: %1 @@ -8362,49 +10453,49 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - + &View - + Printing - + Visible - + Floating - + Reset layout @@ -8412,22 +10503,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -8435,139 +10526,237 @@ Please check your shortcut settings! TabDeckStorage - + Local file system 로컬 파일 시스템 - + Server deck storage 서버 덱 보관함 - - + + Open in deck editor 덱 편집기로 열기 - + Rename deck or folder - + Upload deck 덱 업로드 - + Download deck 덱 다운로드 - - - - + + + + New folder 새 폴더 - - + + Delete 삭제 - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + + + + Error - + Rename failed - - + + + Invalid deck file - + Enter deck name 덱 이름 작성 - + This decklist does not have a name. Please enter a name: 이 덱리스트는 이름이 없습니다. 이름을 입력해 주세요: - + Unnamed deck 이름 없는 덱 - + Failed to upload deck to server - + Delete local file 로컬 파일 삭제 - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: 새 폴더의 이름: - + Deck Storage @@ -8575,20 +10764,271 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -8601,27 +11041,27 @@ Please enter a name: TabEdhRecMain - + &Cards - + Top Commanders - + Tags - + Search for a card ... - + Search @@ -8634,191 +11074,207 @@ Please enter a name: TabGame - - - + + + Replay 리플레이 - - + + Game 게임 - - + + Player List 플레이어 목록 - - + + + Invite + + + + + Card Info 카드 정보 - - + + Messages 메세지 - - + + Replay Timeline 리플레이 타임라인 - + &Phases 단계 - + &Game 게임 - + Next &phase 다음 단계로 진행 - + Next phase with &action - + Next &turn 턴 넘기기 - + Reverse turn order - + &Remove all local arrows 내가 그린 화살표 제거 - + Rotate View Cl&ockwise 플레이어 위치 시계방향으로 조정 - + Rotate View Co&unterclockwise 플레이어 위치 반시계방향으로 조정 - + Game &information 게임 정보 - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede - - - + + + &Concede 항복 - + &Leave game 게임 나가기 - + C&lose replay 리플레이 닫기 - + &Focus Chat - + &Say: 말하기: - + Selected cards - + &View &보기 - + Visible 표시 - + Floating 띄우기 - + Reset layout 레이아웃 초기화 - + Concede 항복 - + Are you sure you want to concede this game? 정말 게임에서 항복하시겠습니까? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game 게임 나가기 - + Are you sure you want to leave this game? 정말 게임에서 나가시겠습니까? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. 게임에서 강제 퇴장 당하였습니다. @@ -8834,158 +11290,158 @@ Please enter a name: TabLog - + Logs 기록 - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs 게임 기록 - + Chat Logs 대화 기록 - - + + Error 오류 - + You must select at least one filter. 최소한 한 개 이상의 필터를 선택해주세요. - + You have to select a valid number of days to locate. - + Username: 사용자명: - + IP Address: IP 주소: - + Game Name: 게임 이름: - + GameID: 게임 ID: - + Message: 메세지: - + Main Room 대화 채널 - + Game Room 게임 방 - + Private Chat 1:1 대화 - + Past X Days: ~일 전: - + Today 오늘 - + Last Hour 1시간 이내 - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. 최소한 한개 이상의 필터를 지정해주세요. 자세히 지정하시면 좀 더 세분화된 결과를 보실 수 있습니다. - + Get User Logs - + Clear Filters - + Filters 필터 - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -8993,218 +11449,558 @@ The more information you put in, the more specific your results will be. TabMessage - + Private &chat 1:1 대화 - + &Leave 나가기 - + %1 - Private chat %1 - 1:1 대화 - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from - + %1 has left the server. %1이(가) 서버와의 연결을 끊었습니다. - + %1 has joined the server. %1이(가) 서버에 연결하였습니다. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system 로컬 파일 시스템 - + Server replay storage 서버 리플레이 보관함 - - + + Watch replay 리플레이 보기 - + Rename - - + + New folder - - + + Delete 삭제 - + Open replays folder - + Download replay 리플레이 다운로드 - + Toggle expiration lock 자동 삭제 여부 토글 - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file 로컬 파일 삭제 - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay 서버에 저장된 리플레이 삭제 @@ -9214,65 +12010,478 @@ The more information you put in, the more specific your results will be. + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - - Friends - - - - - Online - - - - - Ignored - - - - + &Say: 말하기: - + Chat 대화 - + &Room 채널 - + &Leave room 채널에서 나가기 - + &Clear chat 대화 내용 삭제 - + Chat Settings... 대화 설정... - + mentioned you. 이(가) 당신에게 멘션을 보냈습니다. - + Click to view 여기를 눌러 확인 - + You are flooding the chat. Please wait a couple of seconds. 너무 빨리 말하고 있습니다. 조금만 기다려 주세요. @@ -9280,35 +12489,35 @@ The more information you put in, the more specific your results will be. TabServer - + Server 서버 - - - - + + + + Error 오류 - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -9316,98 +12525,174 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - + Archidekt - + Home - + &Visual Deck Storage - + Visual Database Display - + Server - + Account - + Deck Storage - + Game Replays - + Administration - + Logs - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? 확실하십니까? - + There are still open games. Are you sure you want to quit? 참가 중인 게임이 있습니다. 정말로 코카트리스를 종료하시겠습니까? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -9415,32 +12700,32 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion 승급 - + You have been promoted. Please log out and back in for changes to take effect. - + Warned 경고 - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. 운영진으로부터 %1의 사유로 경고를 받았습니다. @@ -9448,7 +12733,7 @@ Please refrain from engaging in this activity or further actions may be taken ag 문의사항은 관리자에게 1:1대화로 물어보시기 바랍니다. - + You have received the following message from the server. (custom messages like these could be untranslated) @@ -9467,6 +12752,34 @@ Please refrain from engaging in this activity or further actions may be taken ag + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -9481,6 +12794,65 @@ Please refrain from engaging in this activity or further actions may be taken ag + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -9550,57 +12922,52 @@ Please refrain from engaging in this activity or further actions may be taken ag UserCardArtSettingsDialog - + Card Art Settings - + Type a card name... - + Card name: - - Card ProviderId: + + Printing: - + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + Left margin (%): - + Right margin (%): - - Vertical offset: - - - - - Zoom: - - - - - Parameters - - - - - Preview - - - - + Remove Banner Card @@ -9608,216 +12975,266 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details 사용자 정보 - + Private &chat 1:1 대화 - + Show this user's &games 이 사용자의 게임 보기 - - - Add to &buddy list - 친구 목록에 추가 - - - - Remove from &buddy list - 친구 목록에서 삭제 - - - - Add to &ignore list - 차단 목록에 추가 - - - - Remove from &ignore list - 차단 목록에서 제거 - - - - Kick from &game - 게임에서 강제 퇴장 - - - - Warn user - - - View user's war&n history + View this user's &public decks - Ban from &server - 서버에서 추방 + Add to &buddy list + 친구 목록에 추가 - View user's &ban history - + Remove from &buddy list + 친구 목록에서 삭제 - &Promote user to moderator - 사용자를 관리자로 승급 + Add to &ignore list + 차단 목록에 추가 - Dem&ote user from moderator - 사용자를 관리자로부터 강등 + Remove from &ignore list + 차단 목록에서 제거 - Promote user to &judge - + Kick from &game + 게임에서 강제 퇴장 - Demote user from judge + Report user + Warn user + + + + + View user's war&n history + + + + + Ban from &server + 서버에서 추방 + + + + View user's &ban history + + + + + &Promote user to moderator + 사용자를 관리자로 승급 + + + + Dem&ote user from moderator + 사용자를 관리자로부터 강등 + + + + Promote user to &judge + + + + + Demote user from judge + + + + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes - - + Investigate user + + + + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games %1의 게임 - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. 경고를 받은 적이 없는 사용자입니다. - + Failed to collect warning information. - + Failed to get admin notes. - - + + Success 성공 - + Successfully promoted user. 해당 사용자 승급에 성공하였습니다. - + Successfully demoted user. 해당 사용자 강등에 성공하였습니다. - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + Kick Player - + Are you sure you want to kick this player from the game? - - - + + + Failed 실패 - + Failed to promote user. 해당 사용자 승급에 실패하였습니다. - + Failed to demote user. 해당 사용자 강등에 실패하였습니다. - + Copy hash to clipboard - + Remove this user's messages @@ -9855,61 +13272,61 @@ Please refrain from engaging in this activity or further actions may be taken ag 운영자 - + Moderator 관리자 - + Registered user 서버에 가입한 사용자 - - + + Unregistered user 서버에 가입하지 않은 사용자 - + Judge - + Unknown 미상 - + The entered password does not match your account. - - - - + + + + Information 사용자 정보 - + User information updated. 사용자 정보가 갱신되었습니다. - - - - - - + + + + + - - - + + + + Error 오류 @@ -9933,283 +13350,292 @@ Please refrain from engaging in this activity or further actions may be taken ag Edit Banner Card + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + The selected card is blacklisted on this server or another error occurred. - + Banner card removed. - + Banner card updated. - + This server does not permit you to update your user informations. 본 서버는 당신의 사용자 정보를 수정 할 수 없게 설정되어 있습니다. - + Password changed. 비밀번호가 변경되었습니다. - + This server does not permit you to change your password. 본 서버는 당신의 비밀번호를 수정 할 수 없게 설정되어 있습니다. - + The new password is too short. 새로운 비밀번호가 너무 짧습니다. - + The old password is incorrect. 예전 비밀번호가 잘못되었습니다. - + Avatar updated. 아바타를 변경하였습니다. - + This server does not permit you to update your avatar. 본 서버는 당신의 아바타를 변경 할 수 없게 설정되어 있습니다. - + An error occured while trying to update your avatar. - - - This server does not permit you to update your user informations. - An error occured while trying to updater your avatar. - 본 서버는 당신의 사용자 정보를 수정 할 수 없게 설정되어 있습니다. - UserInfoPopup - - + + Games - + Chat - + Open private chat - + Profile - + View user profile - + Show this user's games - + + Invite + + + + + Invite to your game + + + + − Buddy - + Remove from buddy list - + + Buddy - + Add to buddy list - + − Ignore - + Remove from ignore list - + + Ignore - + Add to ignore list - + Ban - + Ban from server - + Warn - + Warn user - + Ban log - + View ban history - + Warn log - + View warning history - + Notes - + View admin notes - + − Mod - + Demote from moderator - + + Mod - + Promote to moderator - + − Judge - + Demote from judge - + + Judge - + Promote to judge - + Join game - + Spectate - - + + Loading games… - + Could not load games. - + No active games. @@ -10217,223 +13643,344 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings 일반 인터페이스 설정 - + &Double-click cards to play them (instead of single-click) 카드를 더블 클릭해서 발동 (해제시 한번만 클릭하면 발동 됨) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default 모든 대지가 아닌 카드를 발동 시에 스택으로 이동 (해제시 전장으로 바로 이동) - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens 토큰에 카드 텍스트 주석 자동 추가 - + Show selection count during drag selection - + Show total selection count - - Show subtype breakdown in selection tally + + + Disabled - + + + Enabled + + + + + + Automatic + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + Use tear-off menus, allowing right click menus to persist on screen - + Keep game chat focused when clicking in game (Note: disables card view search bar) - + Notifications settings - + Enable notifications in taskbar 상태 표시줄 알림 설정 - + Notify in the taskbar for game events while you are spectating 관전중인 게임의 상태 표시줄 알림 설정 - + Notify in the taskbar when users in your buddy list connect - + Animation settings 애니메이션 설정 - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation 탭/언탭 애니메이션 - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings - + Open deck in new tab by default - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - - do nothing + + Do nothing - - ask to convert to .cod + + Ask to convert to .cod - - always convert to .cod + + Always convert to .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings - + Buffer time for backwards skip via shortcut: + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + Flip coin - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -10470,22 +14017,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... - + Save Filter - + Save all currently applied filters to a file - + Enter filename... @@ -10493,72 +14040,72 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterToolbarWidget - + Sort by - + Filter by - + Save and load filters - + Filter by exact card name - + Filter by card main-type - + Filter by card sub-type - + Filter by set - + Filter by format legality - + Save/Load - + Name - + Main Type - + Sub Type - + Sets - + Formats @@ -10660,7 +14207,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -10668,19 +14215,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -10726,28 +14273,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - - + + Visual - + Loading database ... - + Clear all filters - + Table @@ -10755,43 +14302,43 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat @@ -10807,12 +14354,12 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -10820,17 +14367,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -10838,7 +14385,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -10846,52 +14393,57 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + Show Color Identity - + Show Tags On Deck Previews - + + Show Upload Time + + + + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -10899,7 +14451,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -10907,22 +14459,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -10930,17 +14482,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + + Select decks to share + + + + Visual Deck Storage Settings @@ -10948,177 +14505,202 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? 어떤 종류의 경고를 보내시겠습니까? - + Redact all messages from this user in all rooms - + &OK 확인 - + &Cancel 취소 - + Warn user for misconduct 사용자의 부적절한 행동에 경고 - - + + Error 오류 - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top 선택한 확장판을 맨 위로 올리기 - + Move selected set up 선택한 확장판을 한 단계 올리기 - + Move selected set down 선택한 확장판을 한 단계 내리기 - + Move selected set to the bottom 선택한 확장판을 맨 아래로 내리기 - - Search by set name, code, or type - - - - + Default order - + Restore original art priority order - + Enable all sets 모든 확장판 활성화 - + Disable all sets 모든 확장판 비활성화 - + Enable selected set(s) 선택한 세트 적용 - + Disable selected set(s) 선택한 세트 미적용 - + Deck Editor 덱 편집기 - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art 카드 이미지 - - How to use custom card art + + Search by set name, code, type, or release date + How to use custom card art + + + + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -11126,93 +14708,85 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing 닫은 후 덱 섞기 - + pile view 카드 유형별 행 정렬 - - i18n - - - English - 한국어 (Korean) - - main - + Connect on startup - + Debug to file @@ -11996,6 +15570,16 @@ Please refrain from engaging in this activity or further actions may be taken ag Graveyard 무덤 + + + Report Queue + + + + + Moderation + + diff --git a/cockatrice/translations/cockatrice_nl.ts b/cockatrice/translations/cockatrice_nl.ts index 882e50dae..8c89946e1 100644 --- a/cockatrice/translations/cockatrice_nl.ts +++ b/cockatrice/translations/cockatrice_nl.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Teller instellen... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Teller instellen - + New value for counter '%1': Nieuwe waarde voor teller '%1': @@ -36,66 +36,115 @@ AbstractTabDeckEditor - + Open in new tab Open in nieuw tabblad - + Are you sure? Weet je het zeker? - + The decklist has been modified. Do you want to save the changes? De decklijst is gewijzigd. Wil je de wijzigingen opslaan? - - - - - - + + + + + + Error Foutmelding - + Could not open deck at %1 Kon het deckbestand niet openen: %1 - + Could not save remote deck Kon remote deck niet opslaan - - + + The deck could not be saved. Please check that the directory is writable and try again. Het deck kon niet opgeslagen worden. Kijk na of je schrijfrechten tot de folder hebt, en probeer opnieuw. - + Save deck Deck opslaan - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. Het deck kon niet opslagen worden. - + There are no cards in your deck to be exported Er zijn geen kaarten in je deck om te exporteren. + + AccountSetupPage + + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + AddAnalyticsPanelDialog @@ -107,12 +156,12 @@ Kijk na of je schrijfrechten tot de folder hebt, en probeer opnieuw. AdminNotesDialog - + Update Notes Update Notities - + Admin Notes for %1 Admin Notities voor %1 @@ -138,218 +187,304 @@ Kijk na of je schrijfrechten tot de folder hebt, en probeer opnieuw. AppearanceSettingsPage - + seconds secondes - + Error Foutmelding - + Could not create themes directory at '%1'. Kon de themes map niet aanmaken op '%1'. - + Theme settings Thema opties - + Current theme: Huidige thema: - + Open themes folder Themasmap Openen - + Home tab background source: Starttabblad achtergrond bron: - + Home tab background shuffle frequency: Starttabblad achtergrond verversperiode. - + Disabled Uitgeschakeld - + Menu settings Menu instellingen - + Show keyboard shortcuts in right-click menus Laat snelkoppelingen in rechtermuisknop menus zien - + Show game filter toolbar above list in room tab Spelfilter werkbalk weergeven boven lijst in kamertab - + Card rendering Kaartweergave - + Display card names on cards having a picture Kaartnamen altijd weergeven op kaarten met een afbeelding - + Auto-Rotate cards with sideways layout Zijwaardse kaarten gedraaid weergeven - + Override all card art with personal set preference (Pre-ProviderID change behavior) Overschrijf alle kaart beelden met eigen set voorkeuren (Gedrag van voor implementatie van ProviderID) - + Light - + Dark - + + System - + + Show all playmats + + + + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + Active theme palette: - + + Active theme style: + + + + + Qt widget style saved to this theme ("System" lets the application decide) + + + + Edit theme palette - + Home tab settings - + Display card name of background in bottom right - + + Dim the home tab background + + + + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + Styling settings - + Style user list - + Card printings - + Bump sets that the deck contains cards from to the top in the printing selector Plaats sets van andere kaarten in het deck bovenin de drukselector - + Scale cards on mouse over Kaarten schalen met de muis - + Use rounded card corners Ronde kaarthoekjes - + Card layout - + Minimum overlap percentage of cards on the stack and in vertical hand Minimum overlap percentage van kaarten op de stack en in verticale handweergave - + Maximum initial height for card view window: Grootste begingrootte voor kaartbekijkvenster - - + + rows rijen - + Maximum expanded height for card view window: Grootste uitgebreide grootte voor kaartbekijkvenster - + Card counters Kaart-counters - + Counter %1 Counter %1 - + Hand layout Handweergave - + Display hand horizontally (wastes space) Hand horizontaal weergeven (gebruikt extra ruimte) - + Enable left justification Linker justificatie inschakelen - + Table grid layout Tafelindeling - + Invert vertical coordinate Verticale coördinaat omkeren - + Minimum player count for multi-column layout: Minimaal aantal spelers voor kolommenindeling: - + Maximum font size for information displayed on cards: Maximale lettergrootte voor informatie die op kaarten wordt weergegeven: @@ -357,12 +492,12 @@ Kijk na of je schrijfrechten tot de folder hebt, en probeer opnieuw. ArchidektApiResponseDeckDisplayWidget - + Back to results Terug naar resultaten - + Open Deck in Deck Editor Open Deck in Deckbewerker @@ -388,112 +523,112 @@ Kijk na of je schrijfrechten tot de folder hebt, en probeer opnieuw. BanDialog - + ban &user name ban &gebruikersnaam - + ban &IP address ban &IP-adres - + ban client I&D ban gebruiker I&D - + Ban type Ban type - + &permanent ban &definitieve ban - + &temporary ban &tijdelijke ban - + &Days: &Dagen: - + &Hours: &Uur: - + &Minutes: &Minuten: - + Duration of the ban Duur van de ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Voer een reden voor de ban in. Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de verbannen persoon. - + Please enter the reason for the ban that will be visible to the banned person. Voer een reden in die zichtbaar zal zijn voor de verbannen persoon. - + Redact all messages from this user in all rooms Verwijder alle berichten van deze gebruiker in alle kamers - + &OK &OK - + &Cancel &Annuleren - + Ban user from server Ban gebruiker van server - - - - + + + + Error Fout - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. U moet een ban gebaseerd op IP, naam, gebruikers ID of een combinatie van de drie. - + You must have a value in the name ban when selecting the name ban checkbox. U moet een naam meegeven als u een naam gebaseerde ban wilt uitvoeren. - + You must have a value in the ip ban when selecting the ip ban checkbox. U moet een IP meegeven als u een IP gebaseerde ban wilt uitvoeren. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. U moet een gebruikers ID meegeven als u een gebruikers ID gebaseerde ban wilt uitvoeren. @@ -501,22 +636,22 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v BetaReleaseChannel - + Beta Beta - + No reply received from the release update server. Geen antwoord ontvangen van de release update server. - + Invalid reply received from the release update server. Ongeldig antwoord ontvangen van de release update server. - + No reply received from the file update server. Geen antwoord ontvangen van de bestandsupdateserver. @@ -524,7 +659,20 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CardArtPreviewWidget - + + Banner preview + + + + + + + + Zoom %1× + + + + No card selected @@ -532,22 +680,22 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CardArtRulesModel - + Card - + ProviderId - + Mode - + Reason @@ -555,65 +703,209 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CardDatabaseModel - + Name Naam - + Sets Sets - + Mana cost Manakosten - + Card type Kaarttype - + P/T P/T - + Color(s) Kleur(en) + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + CardDatabaseView - + Add to Deck - + Add to Sideboard - + Select Printing - + + Add to Zone + + + + + Create &new zone... + + + + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards @@ -723,22 +1015,22 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CardInfoFrameWidget - + Image Plaatje - + Description Beschrijving - + Both Beide - + View transformation Bekijk gedaanteverandering @@ -746,22 +1038,22 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CardInfoPictureWidget - + View related cards Bekijk gerelateerde kaarten - + Add card to deck Voeg kaart aan deck toe - + Mainboard Mainboard - + Sideboard Sideboard @@ -769,27 +1061,27 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CardInfoTextWidget - + Name: Naam: - + Set: Set: - + Collector Number: Verzamelnummer: - + Related cards: Gerelateerde kaarten: - + Unknown card: Onbekende kaart: @@ -797,129 +1089,129 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CardMenu - + Re&veal to... &Onthul aan.... - + &All players &Alle spelers - + View related cards Bekijk gerelateerde kaarten - + Token: Token: - + All tokens Alle tokens - + &Select All &Selecteer Alle - + S&elect Row S&electeer Rij - + S&elect Column S&electeer Kolom - + &Play &Speel - + &Hide &Verberg - + Play &Face Down Speel kaart &verdekt - + &Tap / Untap Turn sideways or back again &Tap / Ontap - + Skip &untapping &Ontappen overslaan - + T&urn Over Turn face up/face down &Draai om - + &Peek at card face &Gluren naar de voorkant van kaart - + &Clone &Kloneer - + Attac&h to card... Aan de kaart &bevestigen.... - + Unattac&h &Losmaken - + &Draw arrow... &Teken pijl.... - + &Set annotation... Aantekeningen &zetten.... - + Reduce life by power - + Ca&rd counters Kaart-&counters - + &Add counter (%1) Voeg counter &toe (%1) - + &Remove counter (%1) &Verwijder counter (%1) - + &Set counters (%1)... Counters &instellen (%1)... @@ -940,7 +1232,7 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v CardSizeWidget - + Card Size Kaartgrootte @@ -1080,6 +1372,24 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v %1s aangepaste zone '%2' + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser @@ -1109,280 +1419,382 @@ Dit wordt opgeslagen voor het moderatorteam en zal niet zichtbaar zijn voor de v + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + ConnectionController - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - - - - - - - - - - - + + + + + + + + + + - - + + + Error - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + Server timeout - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Information - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -1390,113 +1802,113 @@ To update your client, go to Help -> Check for Updates. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - + Requesting forgotten password to %1 as %2... - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. @@ -1546,7 +1958,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorCardDatabaseDockWidget - + Card Database Kaartdatabase @@ -1562,17 +1974,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) Zoek op kaartnaam (of zoekexpressies) - + Add card to &maindeck Kaart aan deck &toevoegen - + Add card to &sideboard Kaart aan &sideboard toevoegen @@ -1580,97 +1992,158 @@ To update your client, go to Help -> Check for Updates. DeckEditorDeckDockWidget - + Loading Database... Database Laden... - + Banner Card Vaandelkaart - + Main Type Hoofdtype - + Mana Cost Mana Kosten - + Colors Kleuren - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing Selecteer Druk - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck Deck - + Deck &name: Deck&naam: - + Banner Card/Tags Visibility Settings Vaandelkaart/Tags zichtbaarheidsinstellingen - + Show banner card selection menu Selectiemenu van vaandelkaarten weergeven - + Show tags selection menu Selectiemenu van tags weergeven - + &Comments: &Opmerkingen: - + Group by: Groepeer op: - + Format: Format: - + Hash: Hash: - + &Increment number Ver&hoog getal - + &Decrement number Ver&laag getal - + &Remove row Ver&wijder rij - + Swap card to/from sideboard Wissel kaart van/naar sideboard @@ -1678,17 +2151,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorFilterDockWidget - + Filters Filters - + &Clear all filters &Wis alle filters - + Delete selected Selectie verwijderen @@ -1696,114 +2169,119 @@ To update your client, go to Help -> Check for Updates. DeckEditorMenu - + &Deck Editor &Deckbewerker - + &New deck &Nieuw deck - + &Load deck... &Laad deck... - + Load recent deck... Laad recent deck... - + Clear Wis - + &Save deck &Bewaar deck - + Save deck &as... Bewaar deck &als... - + + Share deck... + + + + Load deck from cl&ipboard... Laad deck van &klembord.... - + Edit deck in clipboard Deck in klembord aanpassen... - - + + Annotated Geannoteerd - - + + Not Annotated Niet Geannoteerd - + Save deck to clipboard Deck opslaan op klembord - + Annotated (No set info) Geannoteerd (Geen set info) - + Not Annotated (No set info) Niet Geannoteerd (Geen set info) - + &Print deck... &Print deck... - + Load deck from online service... Deck van online service laden... - + &Send deck to online service Deck naar online service &sturen - + Create decklist (decklist.org) Maak decklist aan (decklist.org) - + Create decklist (decklist.xyz) Maak decklist aan (decklist.xyz) - + Analyze deck (deckstats.net) Analyseer deck (deckstats.net) - + Analyze deck (tappedout.net) Analyseer deck (tappedout.net) - + &Close &Sluiten @@ -1811,7 +2289,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorPrintingSelectorDockWidget - + Printing Selector Drukselector @@ -1819,110 +2297,152 @@ To update your client, go to Help -> Check for Updates. DeckEditorSettingsPage - - + + Update Spoilers Update Spoilers - + Success Succes - + Download URLs have been reset. Download URL's zijn gereset. - + Add URL URL toevoegen - - + + URL: URL: - - + + Edit URL URL bewerken - + Add New URL Nieuwe URL toevoegen - + Remove URL URL verwijderen - + Updating... Bijwerken... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path Kies locatie - + URL Download Priority URL Download Prioriteit - + Spoilers Spoilers - + Download Spoilers Automatically Spoilers Automatisch Downloaden - + Spoiler Location: Spoiler Locatie: - + Last Change Laatste Wijziging - + Spoilers download automatically on launch Spoilers downloaden automatisch bij opstarten - + Press the button to manually update without relaunching Druk op de knop om handmatig bij te werken zonder opnieuw te starten. - + Do not close settings until manual update is complete Sluit de instellingen niet af voordat de handmatige update is voltooid - + Download card pictures on the fly Kaartafbeeldingen on the fly downloaden - + How to add a custom URL Hoe een aangepaste URL toe te voegen - + Reset Download URLs Reset Download URL's @@ -1930,32 +2450,32 @@ To update your client, go to Help -> Check for Updates. DeckListHistoryManagerWidget - + Undo Ontdoen - + Redo Herdoen - + Undo/Redo history Ontdoe/Herdoe geschiedenis - + Click on an entry to revert to that point in the history. Klik een regel om terug te gaan naar dat punt in de geschiedenis. - + [redo] [herdoen] - + [undo] [ontdoen] @@ -1963,27 +2483,27 @@ To update your client, go to Help -> Check for Updates. DeckListModel - + Count Hoeveelheid - + Set Set - + Number Nummer - + Provider ID Provider ID - + Card Kaart @@ -2004,17 +2524,22 @@ To update your client, go to Help -> Check for Updates. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match Modus: Exacte Overeenkomst - + Mode: Includes Modus: Bevat - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) Kleur-identiteit filtermodus (AND/OR/NOT samenvoegingen van filters) @@ -2022,7 +2547,7 @@ To update your client, go to Help -> Check for Updates. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... Bewerk tags ... @@ -2030,62 +2555,62 @@ To update your client, go to Help -> Check for Updates. DeckPreviewTagDialog - + Deck Tags Manager Deck Tags Beheren - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: Beheer je deck-tags. Vink tags aan of uit naar behoefte, of voeg nieuwe toe: - + Add a new tag (e.g., Aggro️) Nieuwe tag toevoegen (e.g., Aggro) - + Add Tag Tag Toevoegen - + Filter tags... Tags filteren... - + OK OK - + Edit default tags Standaardtags bewerken - + Cancel Annuleren - + Invalid Input Ongeldige Invoer - + Tag name cannot be empty! Naam van tag mog niet leeg zijn! - + Duplicate Tag Tag Dupliceren - + This tag already exists. Deze tag bestaat al. @@ -2093,94 +2618,109 @@ To update your client, go to Help -> Check for Updates. DeckPreviewWidget - + Banner Card Vaandelkaart - + Open in deck editor In deckbewerker openen - + + Share deck... + + + + Edit Tags Tags Aanpassen - + Rename Deck Deck Hernoemen - + Save Deck to Clipboard Bewaar Deck op Klembord - + Annotated Geannoteerd - + Annotated (No set info) Geannoteerd (Geen set info) - + Not Annotated Niet Geannoteerd - + Not Annotated (No set info) Niet Geannoteerd (Geen set info) - + Rename File Bestand Hernoemen - + Delete File Bestand Verwijderen - + Set Banner Card Vaandelkaart Instellen - - + + New name: Nieuwe naam: - - + + Rename deck + + + + + Rename file + + + + + Error Foutmelding - + Rename failed Hernoemen mislukt - + Delete file Bestand Verwijderen - + Are you sure you want to delete the selected file? Weet je zeker dat je het opgeslagen bestand wilt verwijderen? - + Delete failed Verwijderen mislukt @@ -2188,57 +2728,102 @@ To update your client, go to Help -> Check for Updates. DeckStateManager - + Rename deck to "%1" from "%2" Hernoem deck naar "%1" van "%2" - + Updated comments (was %1 chars, now %2 chars) Opmerkingen bijgewerkt (van %1 karakters naar %2 karakters) - + Set banner card to %1 (%2) Vaandelkaart veranderd naar %1 (%2) - + + Set playmat to %1 + + + + Tags changed Tags veranderd - + Set format to %1 Format veranderd naar %1 - + Added (%1): %2 (%3) %4 Toegevoegd (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) Verplaatst naar %1 1 × "%2" (%3) - + Removed "%1" (all copies) Verwijderd "%1" (alle exemplaren) - + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + %1 1 × "%2" (%3) %1 1 × "%2" (%3) - + Added Toegevoegd - + Removed Verwijderd @@ -2260,88 +2845,116 @@ To update your client, go to Help -> Check for Updates. DeckViewContainer - + Load deck... Deck laden... - + Load remote deck... Laad extern deck... - + Load from clipboard... Van klembord laden.... - + Load from website... Van website laden... - + Unload deck Deck uitladen - + Ready to start Gereed voor begin - + Force start Geforceerd starten - + Sideboard unlocked Sideboard ontgrendeld - + Sideboard locked Sideboard vergrendeld - - + + Error Fout - + The selected file could not be loaded. Het geselecteerde bestand kon niet geladen worden. - + Deck is greater than maximum file size. Deck is groter dan maximum bestandsgrootte. - + Are you sure you want to force start? This will kick all non-ready players from the game. Weet je zeker dat je geforceerd beginnen wil? Dit zal alle spelers die niet gereed zijn van het spel verwijderen. - + Cockatrice Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion Deckformaat Omzetten - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? @@ -2350,7 +2963,7 @@ Tags kunnen alleen worden toegevoegd aan decks in .cod-formaat. Wil je het deck omzetten naar het .cod-formaat? - + Remember and automatically apply choice in the future Onthouden en automatisch keuze toepassen in de toekomst @@ -2363,143 +2976,143 @@ Wil je het deck omzetten naar het .cod-formaat? Downloaden... - + Known Hosts Bekende Hosts - + Delete the currently selected saved server Verwijder de geselecteerde opgeslagen server - + Refresh the server list with known public servers Hernieuw de serverlijst met bekende publieke servers - + New Host Nieuwe Eigenaar - + Name: Naam: - + &Host: &Gastheer - + &Port: &Poort - + Player &name: Spelers&naam: - + P&assword: W&achtwoord: - + &Save password Wachtwoord &Opslaan - + A&uto connect A&utomatisch verbinden - + Automatically connect to the most recent login when Cockatrice opens Maak automatisch verbinding met de meest recente login wanneer Cockatrice wordt geopend. - + If you have any trouble connecting or registering then contact the server staff for help! Als u problemen ondervindt bij het aansluiten of registreren, neem dan contact op met het serverteam voor hulp! - - + + Webpage Website - + Reset Password Wachtwoord veranderen - + Forgot password? Wachtwoord vergeten? - + &Connect &Verbinden - + Server Server - + Login Login - + Server Contact Servercontact - + Connect to Server Verbinding maken met server - + Server URL Server URL - + Communication Port Communicatiepoort - + Unique Server Name Unieke Servernaam - + Connection Warning Verbindingswaarschuwing - + You need to name your new connection profile. U moet het nieuwe verbindingsprofiel een naam geven. - + Connect Warning Verbindingswaarschuwing - + The player name can't be empty. De naam van de speler mag niet leeg zijn. @@ -2507,122 +3120,122 @@ Wil je het deck omzetten naar het .cod-formaat? DlgCreateGame - + Re&member settings Instellingen &Onthouden - + &Description: &Omschrijving: - + P&layers: Spe&lers: - + General Algemeen - + Game type Spel type - + &Password: &Wachtwoord: - + Only &buddies can join Alleen &maatjes kunnen aansluiten - + Only &registered users can join Alleen ge&registreerde gebruikers kunnen aansluiten - + Joining restrictions Restricties voor aansluiten - + &Spectators can watch Toe&schouwers kunnen meekijken - + Spectators &need a password to watch Toeschouwers hebben een wachtwoord &nodig om mee te kijken - + Spectators can &chat Toeschouwers kunnen &chatten - + Spectators can see &hands Toeschouwers kunnen &handen zien - + Create game as spectator Maak spel aan als toeschouwer - + Spectators Toeschouwers - + Starting life total: Beginnend levenstotaal: - + Open decklists in lobby Open decklijsten in lobby - + Create game as judge Maak spel aan als judge - + Game setup options Spel opzet opties - + &Clear &Opschonen - + Create game Spel aanmaken - + Game information Spelgegevens - + Error Fout - + Server error. Server Fout @@ -2630,97 +3243,97 @@ Wil je het deck omzetten naar het .cod-formaat? DlgCreateToken - + &Name: &Naam: - + Token Token - + C&olor: Kle&ur: - + white wit - + blue blauw - + black zwart - + red rood - + green groen - + multicolor veelkleurig - + colorless kleurloos - + &P/T: &P/T: - + &Annotation: &Annotatie: - + &Destroy token when it leaves the table &Vernietig token als het de tafel verlaat - + Create face-down (Only hides name) Verdekt aanmaken (Verbergt alleen naam) - + Token data Token data - + Show &all tokens Laat &alle tokens zien - + Show tokens from this &deck Laat tokens van dit &dek zien - + Choose token from list Kies token uit lijst - + Create token Maak token @@ -2728,53 +3341,53 @@ Wil je het deck omzetten naar het .cod-formaat? DlgDefaultTagsEditor - + Edit Tags Tags Aanpassen - + Add Toevoegen - + Confirm Bevestig - + Cancel Annuleren - + Enter a tag and press Enter Vul tag in en druk Enter - - + + ✖ ✖ - + Invalid Input Ongeldige Invoer - + Tag name cannot be empty! Naam van tag mog niet leeg zijn! - + Duplicate Tag Tag Dupliceren - + This tag already exists. Deze tag bestaat al. @@ -2783,7 +3396,7 @@ Wil je het deck omzetten naar het .cod-formaat? DlgEditAvatar - + No image chosen. Geen afbeelding gekozen. @@ -2795,27 +3408,32 @@ To remove your current avatar, confirm without choosing a new image. Om je avatar te verwijderen, accepteer zonder een nieuwe afbeelding te kiezen. - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... Zoeken... - + Change avatar Verander Icoon - + Open Image Afbeelding openen - + Image Files (*.png *.jpg *.bmp) Afbeeldingen (*.png *.jpg *.bmp) - + Invalid image chosen. Geen geldige afbeelding gekozen @@ -2841,38 +3459,38 @@ Om je avatar te verwijderen, accepteer zonder een nieuwe afbeelding te kiezen. DlgEditPassword - + Old password: Oud wachtwoord: - + New password: Nieuw wachtwoord: - + Confirm new password: Herhaal nieuw wachtwoord: - + Change password Verander wachtwoord - - + + Error Fout - + Your password is too short. Je wachtwoord is te kort. - + The new passwords don't match. Nieuwe wachtwoorden komen niet overeen. @@ -2880,93 +3498,93 @@ Om je avatar te verwijderen, accepteer zonder een nieuwe afbeelding te kiezen. DlgEditTokens - + &Name: &Naam: - + C&olor: Kle&ur: - + white wit - + blue blauw - + black zwart - + red rood - + green groen - + multicolor veelkleurig - + colorless kleurloos - + &P/T: &P/T: - + &Annotation: &Annotatie: - + Token data Token data - - + + Add token Voeg token toe - + Remove token Verwijder token - + Edit custom tokens Aangepaste tokens bewerken - + Please enter the name of the token: Gelieve de naam van de token in te voeren: - + Error Fout - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. De gekozen naam is al in gebruik door een bestaande kaart of token. @@ -3045,8 +3663,9 @@ Zorg ervoor dat u de 'Token' set in het " Beheer sets" dialo - Hide games that have started - Verberg spellen die al begonnen zijn + Hide started games + Hide games that have started + @@ -3081,8 +3700,9 @@ Zorg ervoor dat u de 'Token' set in het " Beheer sets" dialo - &Creator name: - Naam van &maker: + &Host name: + &Creator name: + @@ -3148,52 +3768,52 @@ Zorg ervoor dat u de 'Token' set in het " Beheer sets" dialo DlgForgotPasswordChallenge - + Reset Password Challenge Warning Wachtwoord Vergeten Challenge Waarschuwing - + A problem has occurred. Please try to request a new password again. Een probleem heeft zich opgetreden. Gelieve opnieuw een wachtwoord aan te vragen. - + Enter the information of the server and the account you'd like to request a new password for. Vul de gegevens in van de server en het account waar je een nieuw wachtwoord aan voor wil vragen. - + &Host: &Host: - + &Port: &Poort: - + Player &name: &Spelernaam: - + Email: E-mail: - + Reset Password Challenge Wachtwoord Vergeten Challenge - + Reset Password Challenge Error Foutmelding Wachtwoord Vergeten Challenge - + The email address can't be empty. Het e-mailadres mag niet leeg zijn. @@ -3201,37 +3821,37 @@ Zorg ervoor dat u de 'Token' set in het " Beheer sets" dialo DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. Vul de gegevens in van de server waar je een nieuw wachtwoord aan voor wil vragen. - + &Host: &Host: - + &Port: &Poort: - + Player &name: &Spelernaam: - + Reset Password Request Wachtwoord Vergeten Aanvraag - + Reset Password Error Foutmelding Wachtwoord Resetten - + The player name can't be empty. De spelernaam kan niet leeg zijn. @@ -3239,94 +3859,127 @@ Zorg ervoor dat u de 'Token' set in het " Beheer sets" dialo DlgForgotPasswordReset - + Reset Password Warning Waarschuwing Wachtwoord Resetten - + A problem has occurred. Please try to request a new password again. Een probleem heeft zich opgetreden. Gelieve opnieuw een wachtwoord aan te vragen. - + Enter the received token and the new password in order to set your new password. Vul het ontvangen token en het nieuwe wachtwoord in om je wachtwoord in te stellen. - + &Host: &Host: - + &Port: &Poort: - + Player &name: &Spelernaam: - + Token: Token: - - + + New Password: Nieuw Wachtwoord: - + Reset Password Reset Wachtwoord - + The player name can't be empty. De spelernaam kan niet leeg zijn. - - - - + + + + Reset Password Error Foutmelding Resetten Wachtwoord - + The token can't be empty. De token mag niet leeg zijn. - + The new password can't be empty. Het nieuwe wachtwoord mag niet leeg zijn. - + Error Fout - + Your password is too short. Uw wachtwoord is te kort. - + The passwords do not match. De wachtwoorden komen niet overeen. + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck Deck Laden @@ -3418,166 +4071,567 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLocalGameOptions - + Players: Spelers: - + General Algemeen - + Starting life total: Beginnend levenstotaal: - + Game setup options Spel opzet opties - + Remember settings Instellingen onthouden - + Local game options Opties plaatselijk spel + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): Kaart naam (of zoekexpressies) - + Number of hits: Hoeveelheid resultaten: - + Auto play hits Speel resultaten automatisch - + Put top cards on stack until... Plaats bovenste kaart op stack totdat... - + No cards matching the search expression exists in the card database. Proceed anyways? Er zijn geen kaarten die bij deze zoekopdracht passen in de kaartendatabase. Toch doorgaan? - + Cockatrice Cockatrice - + Invalid filter Ongeldige filter + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. Vul je gegevens en de gegevens van de server waar je wil registreren. Je email zal gebruikt worden om je account te controleren. - + &Host: &Eigenaar: - + &Port: &Poort: - + Player &name: &Naam van speler: - + P&assword: W&achtwoord: - + Password (again): Wachtwoord (opnieuw): - + Email: Email: - + Email (again): Email (opnieuw): - + Country: Land: - + Undefined Onbekend - + Real name: Echte naam: - + Register to server Registreer op server - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning Registratie Waarschuwing - + Your password is too short. Je wachtwoord is te kort. - + Your passwords do not match, please try again. Uw wachtwoorden komen niet overeen, probeer opnieuw. - + Your email addresses do not match, please try again. Uw email adressen komen niet overeen, probeer opnieuw. - + The player name can't be empty. Spelersnaam kan niet leeg zijn. + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice @@ -3643,46 +4697,12 @@ Je email zal gebruikt worden om je account te controleren. DlgSettings - + Unknown Error loading card database Onbekende Fout in het lezen van de kaart database - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - Uw kaar database is niet geldig. - -Cockatrice kan niet correct functioneren zonder een geldige database - -U zou oracle moeten herstarten om uw database te updaten. - -Zou u de locatie van uw database willen veranderen? - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - Je kaart database versie is te oud. - -Dit kan problemen veroorzaken tijdens het laden van kaart gegevens of afbeeldingen. - -Dit zou mogelijk opgelost worden door oracle opnieuw te starten en je kaart database te updaten. - -Wil je de locatie van je database veranderen? - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3695,7 +4715,7 @@ Plaats een klacht op https://github.com/Cockatrice/Cockatrice/issues met uw card Wilt u uw database lokatie aanpassen? - + File Error loading your card database. Would you like to change your database location setting? @@ -3704,7 +4724,7 @@ Would you like to change your database location setting? Wilt u uw database lokatie aanpassen? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3713,7 +4733,7 @@ Would you like to change your database location setting? Wilt u uw database lokatie aanpassen? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3726,77 +4746,208 @@ Plaats een klacht op https://github.com/Cockatrice/Cockatrice/issues asltublieft Wilt u uw database lokatie aanpassen? - - - + + + Error Fout - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Het pad naar uw deck map is niet geldig. Wilt u terug gaan en de correcte pad invullen? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Het pad naar uw kaart afbeelding map is niet geldig. Wil je terug gaan en het juiste pad invullen? - + Settings Instellingen - + + Search settings... + + + + + OK + + + + General Algemeen - + Appearance Weergave - + User Interface Gebruikersomgeving - + Storage - + Card Sources Kaart Bronnen - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat Chat - + Sound Geluid - + Shortcuts Sneltoetsen + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check Kaartbewerkingscontrole - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. @@ -3805,27 +4956,27 @@ Kies hoe je de kaartendatabasebewerker wil uitvoeren. Je kan altijd dit gedrag aanpassen in de "Algemeen" instellingtab. - + Run in foreground In voorgrond uitvoeren - + Run in background In achtergrond uitvoeren - + Run in background and always from now on Altijd in achtergrond uitvoeren vanaf nu - + Don't prompt again and don't run Niet opniew vragen en niet uitvoeren - + Don't run this time Niet nu uitvoeren @@ -3833,17 +4984,17 @@ Je kan altijd dit gedrag aanpassen in de "Algemeen" instellingtab. DlgTipOfTheDay - + Next Volgende - + Previous Vorige - + Tip of the Day Tip van de Dag @@ -3851,166 +5002,176 @@ Je kan altijd dit gedrag aanpassen in de "Algemeen" instellingtab. DlgUpdate - + Current release channel Huidig releasekanaal - + Reinstall Herinstalleer - + Cancel Download Annuleer Downloaden - + Open Download Page Open de downloadpagina - + Check for Client Updates Controleren op Client-updates - - - - + + + + Error Fout - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice is niet gebouwd met SSL-ondersteuning, daarom kunt u niet automatisch updates downloaden! Ga naar de downloadpagina om handmatig bij te werken. - + Downloading update: %1 Bewerkingen downloaden: %1 - + Checking for updates... Controleren op updates... - + Finished checking for updates Controle op updates voltooid - + No Update Available Geen Update Beschikbaar - + Cockatrice is up to date! Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. U draait al de laatste versie die beschikbaar is in het gekozen releasekanaal. - + Current version Huidige versie - + Selected release channel Geselecteerd releasekanaal - - + + Update Available Update Beschikbaar - - + + A new version of Cockatrice is available! Een nieuwe versie van Cockatrice is beschikbaar! - - + + New version Nieuwe versie - - + + Released Released - - + + Changelog Changelog - + Do you want to update now? Wilt u nu updaten? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. Helaas, de automatische bewerker is niet geslaagd in het vinden van een toepasselijke download. Je moet misschien handmatig de nieuwe versie downloaden. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. Controleer alsjeblieft de <a href="%1">releases pagina</a>op onze Github en download de versie voor jouw systeem. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error Update Fout - + An error occurred while checking for updates: Er is een fout opgetreden bij het controleren op updates: - + An error occurred while downloading an update: Er is een fout opgetreden tijdens het downloaden van een update: - + Installing... Installeren... - + Cockatrice is unable to open the installer. Cockatrice kan het installatieprogramma niet openen. - + Try to update manually by closing Cockatrice and running the installer. Probeer handmatig bij te werken door Cockatrice te sluiten en het installatieprogramma uit te voeren. - + Download location Download locatie @@ -4018,17 +5179,17 @@ Je moet misschien handmatig de nieuwe versie downloaden. DlgViewLog - + Clear log when closing Wis log bij sluiten - + Copy to clipboard Kopieer naar klembord - + Debug Log Debug Log @@ -4266,7 +5427,7 @@ Je moet misschien handmatig de nieuwe versie downloaden. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: Zoutigheid: @@ -4274,7 +5435,7 @@ Je moet misschien handmatig de nieuwe versie downloaden. FilterBuilder - + Type your filter here Typ hier uw filter in @@ -4282,45 +5443,88 @@ Je moet misschien handmatig de nieuwe versie downloaden. FilterDisplayWidget - + Confirm Delete Bevestig Verwijderen - + Are you sure you want to delete the filter '%1'? Weet je zeker dat je de filter '%1' wil verwijderen? - + Delete Failed Verwijderen Mislukt - + Failed to delete filter '%1'. Verwijderen van filter '%1' mislukt. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator er uit geschopt door spelmaker of moderator - + player left the game speler heeft het spel verlaten - + player disconnected from server speler verbrak verbinding van server - + reason unknown reden onbekend @@ -4328,140 +5532,156 @@ Je moet misschien handmatig de nieuwe versie downloaden. GameSelector - - - - - - - - - + + + + + + + + + Error Fout - + Please join the appropriate room first. Gelieve eerst naar de juiste kamer te gaan. - + Wrong password. Verkeerd wachtwoord. - + Spectators are not allowed in this game. Toeschouwers zijn niet toegelaten in dit spel. - + The game is already full. Dit spel is reeds vol. - + The game does not exist any more. Het spel bestaat niet meer. - + This game is only open to registered users. Dit spel is alleen toegankelijk voor geregistreerde gebruikers. - + This game is only open to its creator's buddies. Dit spel is alleen toegankelijk voor de maatjes van de maker van dit spel. - + You are being ignored by the creator of this game. Je wordt genegeerd door de maker van dit spel. - + Join Game Aansluiten bij spel - + Spectate Game Spel Toeschouwen - + Game Information Spelgegevens - + + Cop&y game link + + + + Join Game as Judge Bij Spel Aansluiten als Judge - + Spectate Game as Judge Spel Toeschouwen als Judge - + + Join game Aansluiten bij spel - - Password: - Wachtwoord: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. Gelieve eerst bij de betreffende kamer aan te sluiten. - + &Filter games &Filter spellen - + C&lear filter W&is filter - + C&reate C&reëer - + &Join &Aansluiten - + Join as judge Aansluiten als judge - + J&oin as spectator &Aansluiten als toeschouwer - + Join as judge spectator Aansluiten als judge toeschouwer - + Games shown: %1 / %2 Spellen weergegeven: %1 / %2 - + Games Spellen @@ -4502,105 +5722,105 @@ Je moet misschien handmatig de nieuwe versie downloaden. GamesModel - + >1 day >1 dag - + %1%2 hr short age in hours %1%2 uur%1%2 uur - + new nieuw - + %1%2 min short age in minutes %1%2 min%1%2 min - + password wachtwoord - + buddies only alleen maatjes - + reg. users only enkel geregistreerde gebruikers - + open decklists decklijsten gedeeld - - + + can chat kan praten - + see hands zie handen - + can see hands kan handen zien - + not allowed niet toegelaten - + Room Kamer - + Age Ouderdom - + Description Omschrijving - - Creator - Maker + + Host + - + Type Type - + Restrictions Restricties - + Players Spelers - + Spectators Toeschouwers @@ -4608,158 +5828,275 @@ Je moet misschien handmatig de nieuwe versie downloaden. GeneralSettingsPage - + Reset all paths Reset alle bestandspaden - + All paths have been reset Alle bestandspaden zijn gereset - - - - - - - + + + + English + Nederlands (Dutch) + + + + + + + + + Choose path Kies pad - + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> + + + + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + Language settings - + Language: Taal: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + Version settings - + Card database - + Startup settings - + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) Locaties (bewerken uitgeschakeld in "portable mode") - + Paths Paden - + How to help with translations Hoe met vertalen te helpen - + Decks directory: Map voor decks: - + Filters directory: Filters map: - + Replays directory: Map voor replays: - + Pictures directory: Map voor afbeeldingen: - + Card database: Kaartdatabase: - + Custom database directory: Custom database map: - + Token database: Token-database - + Update channel Updatekanaal - + Check for client updates on startup Controleren op client-updates bij opstarten - + Check for card database updates on startup Controleren op kaartendatabasebewerkingen bij opstarten - + Don't check Niet controleren - + Prompt for update Vragen om bewerking - + Always update in the background Altijd in achtergrond bewerken - + Check for card database updates every Controleren op kaartendatabasebewerkingen iedere - + days dagen - + Notify if a feature supported by the server is missing in my client Waarschuw als een functie die is ondersteund door de server mist in mijn client - + Automatically run Oracle when running a new version of Cockatrice Oracle automatisch uitvoeren bij het opstarten van een nieuwe versie van Cockatrice - + Show tips on startup Toon tips bij het opstarten - + Last update check on %1 (%2 days ago) Laatste controle op %1 (%2 dagen geleden) @@ -4767,47 +6104,47 @@ Je moet misschien handmatig de nieuwe versie downloaden. GraveyardMenu - + &Graveyard &Graveyard - + &View graveyard &Bekijk graveyard - + &Move graveyard to... &Verplaats graveyard naar... - + &Top of library &Bovenkant van library - + &Bottom of library &Onderkant van library - + &All players &Alle spelers - + &Hand &Hand - + &Exile &Exile - + Reveal random card to... Onthul willekeurige kaart aan... @@ -4901,59 +6238,353 @@ Je moet misschien handmatig de nieuwe versie downloaden. Onthul &willekeurige kaart aan.... + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck Maak Nieuw Deck - + Browse Decks Decks Doorbladeren - + Browse Card Database Kaartendatabase Doorbladeren - + Browse EDHRec EDHRec Doorbladeren - + Browse Archidekt Archidekt Doorbladeren - + View Replays Replays Inzien - + Quit Sluiten - + Connecting... Verbinden... - + Connect Verbinden - + Play Spelen + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu @@ -5171,302 +6802,323 @@ Je moet misschien handmatig de nieuwe versie downloaden. MainWindow - - + + Player %1 Speler %1 - + Load replay Laad replay - + About Cockatrice Over Cockatrice - + Version Versie - + Cockatrice Webpage Cockatrice Webpagina - + Project Manager: Project Manager: - + Past Project Managers: Vroegere projectmanagers: - + Developers: Ontwikkelaars: - + Our Developers Onze ontwikkelaars - + Help Develop! Help ontwikkelen! - + Translators: Vertalers: - + Our Translators Onze Vertalers - + Help Translate! Help Vertalen! - + Support: Steun: - + Report an Issue Een probleem melden - + Troubleshooting Probleemoplossing - + F.A.Q. F.A.Q. - - + + Error Fout - + &Connect... &Verbinden.... - + &Disconnect V&erbinding verbreken - + Start &local game... Start &plaatselijk spel.... - + &Watch replay... &Bekijk replay... - + &Full screen &Volledig scherm - + &Register to server... &Registreer bij server... - + &Restore password... &Reset wachtwoord... - + &Settings... &Instellingen.... - + &Exit &Afsluiten - + A&ctions Act&ies - + &Cockatrice &Cockatrice - + C&ard Database &Kaartdatabase: - + &Manage sets... &Beheer sets... - + Edit custom &tokens... Aangepaste &tokens bewerken - + Open custom image folder Open map voor aangepaste afbeeldingen - + Open custom sets folder Open aangepaste sets map - + Add custom sets/cards Aangepaste sets/kaarten toevoegen - + Reload card database Kaartendatabase opniew laden - + Tabs Tabs - + &Help &Hulp - + &About Cockatrice &Over Cockatrice - + &Tip of the Day &Tip van de Dag - + Check for Client Updates Controleren op Client-updates - + Check for Card Updates... Kijk of er kaartupdates zijn... - + Check for Card Updates (Automatic) Controleren op kaartbewerkingen (Automatisch) - + Show Status Bar Statusbalk Weergeven - + View &Debug Log Bekijk &debug log - + Open Settings Folder Instellingsmap Openen - + + Re-run Onboarding Wizard... + + + + Show/Hide Tonen/Verbergen - + + Sign-in cancelled + + + + New Version Niewe Versie - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Gefeliciteerd met de update naar Cockatrice %1! Oracle zal nu starten om uw kaartendatabase bij te werken. - + Cockatrice installed Cockatrice geinstalleerd - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Gefeliciteerd met het installeren van Cockatrice %1! Oracle zal nu starten om uw kaartendatabase bij te werken. - + + Are you sure? + + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database Kaartdatabase - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" Cockatrice kan de kaartendatabase niet laden. - - + + Yes Ja - - + + No Nee - + Open settings Open instellingen - + New sets found Nieuwe sets gevonden - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5477,22 +7129,22 @@ Set codes: %1 Wilt u deze inschakelen? - + Yes, always enable - + View sets Bekijk sets - + Welcome Welkom - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5501,90 +7153,90 @@ Alle sets in de kaartendatabase zijn ingeschakeld. Lees meer over het wijzigen van de setvolgorde of het uitschakelen van specifieke sets en de daaruit voortvloeiende effecten in het "Beheer sets" dialoogvenster. - + Information Informatie - + A card database update is already running. Een update van de kaartendatabase is al aan de gang. - + Unable to run the card database updater: Kan het updateprogramma van de kaartendatabase niet uitvoeren: - + Card database update running. Kaartendatabasebewerking in uitvoering. - + Failed to start. The file might be missing, or permissions might be incorrect. Opstarten mislukt. Het bestand mist misschien, of toestemmingen zijn misschien incorrect. - + The process crashed some time after starting successfully. Het proces crashte nadat deze geslaagd is opgestart. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. Tijd verlopen. Het proces nam te lang met antwoorden. De laatste waitfor...() functie verliep. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. Een fout heeft zich voorgedaan tijdens een poging om te schrijven naar het proces. Bijvoorbeeld, het proces is niet in uitvoering misschien, of het heeft misschien het invoerkanaal gesloten. - + An error occurred when attempting to read from the process. For example, the process may not be running. Een fout heeft zich voorgedaan tijdens een poging om te lezen van het proces. Bijvoorbeeld, het proces is niet in uitvoering misschien. - + Unknown error occurred. Een onbekende fout heeft zich voorgedaan. - + The card database updater exited with an error: %1 De kaartendatabasebewerker sloot af met foutmelding. %1 - - - - - + + + + + Load sets/cards Laad sets/kaarten - + Selected file cannot be found. Geselecteerd bestand kan niet worden gevonden. - + You can only import XML databases at this time. U kunt op dit moment alleen XML-databases importeren. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. De nieuwe sets/kaarten zijn succesvol toegevoegd. Cockatrice zal nu de kaartendatabase herladen. - + Sets/cards failed to import. Sets/kaarten zijn niet geïmporteerd. @@ -5789,615 +7441,615 @@ Cockatrice zal nu de kaartendatabase herladen. MessageLogWidget - + from play vanuit spel - + from their graveyard vanuit hun graveyard. - + from exile vanuit exile - + from their hand vanuit hun hand. - + the top card of %1's library de bovenste kaart %1's library - + the top card of their library de bovenste kaart van hun library - + from the top of %1's library van de top van %1's library - + from the top of their library van de bovenkant van hun library - + the bottom card of %1's library de onderste kaart van %1's library - + the bottom card of their library de onderste kaart van hun library - + from the bottom of %1's library van de onderkant van %1's library - + from the bottom of their library van de onderkant van hun library - + from %1's library van %1's library - + from their library van hun library - + from sideboard van sideboard - + from the stack van de stack - + from custom zone '%1' vanaf aangepaste zone '%1' - + %1 is now keeping the top card %2 revealed. %1 houdt nu de bovenste kaart %2 onthuld. - + %1 is not revealing the top card %2 any longer. %1 geeft de bovenste kaart %2 niet meer weer. - + %1 can now look at top card %2 at any time. %1 kan nu altijd de bovenste kaart %2 bekijken. - + %1 no longer can look at top card %2 at any time. %1 kan nu niet meer de bovenste kaart %2 bekijken. - + %1 attaches %2 to %3's %4. %1 bevestigt %2 aan %3s %4. - + %1 has conceded the game. %1 heeft het spel opgegeven. - + %1 has unconceded the game. %1 is terug in het spel. - + %1 has restored connection to the game. %1 heeft de verbinding met het spel hersteld. - + %1 has lost connection to the game. %1 heeft de verbinding met het spel verloren. - + %1 points from their %2 to themselves. %1 wijst van hun %2 naar zichzelf. - + %1 points from their %2 to %3. %1 wijst van hun %2 naar %3. - + %1 points from %2's %3 to themselves. %1 wijst van %2s %3 naar zichzelf. - + %1 points from %2's %3 to %4. %1 wijst van %2s %3 naar %4. - + %1 points from their %2 to their %3. %1 wijst van hun %2 naar hun %3. - + %1 points from their %2 to %3's %4. %1 wijst van hun %2 naar %3s %4. - + %1 points from %2's %3 to their own %4. %1 wijst van %2s %3 naar hun eigen %4. - + %1 points from %2's %3 to %4's %5. %1 wijst van %2s %3 naar %4s %5. - + %1 creates a face down token. %1 creëert een token verdekt. - + %1 creates token: %2%3. %1 creëert een token: %2%3. - + %1 has loaded a deck (%2). %1 heeft een deck geladen (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 heeft een deck met %2 sideboard kaarten geladen (%3). - + %1 destroys %2. %1 vernietigt %2. - + a card een kaart - + %1 gives %2 control over %3. %1 geeft %2 controle over %3. - + %1 puts %2 into play%3 face down. %1 zet %2 gesloten in het spel%3. - + %1 puts %2 into play%3. %1 zet %2 in het spel%3. - + %1 puts %2%3 into their graveyard face down. %1 legt %2%3 in hun graveyard verdekt. - + %1 puts %2%3 into their graveyard. %1 legt %2%3 in hun graveyard. - + %1 exiles %2%3 face down. %1 legt %2%3 in exile verdekt. - + %1 exiles %2%3. %1 legt %2%3 in exile. - + %1 moves %2%3 to their hand. %1 verplaatst %2%3 naar hun hand. - + %1 puts %2%3 into their library. %1 legt %2%3 in hun library. - + %1 puts %2%3 onto the bottom of their library. %1 legt %2%3 aan de onderkant van hun library. - + %1 puts %2%3 on top of their library. %1 legt %2%3 op de bovenkant van hun library. - + %1 puts %2%3 into their library %4 cards from the top. %1 plaatst %2%3 in hun library %4 kaarten vanaf de bovenkant. - + %1 moves %2%3 to sideboard. %1 verplaatst %2%3 naar het sideboard. - + %1 plays %2%3 face down. %1 speelt %2%3 verdekt. - + %1 plays %2%3. %1 speelt %2%3. - + %1 moves %2%3 to custom zone '%4' face down. %1 verplaatst %2%3 naar aangepaste zone '%4' verdekt. - + %1 moves %2%3 to custom zone '%4'. %1 verplaatst %2%3 naar aangepaste zone '%4'. - + %1 tries to draw from an empty library %1 probeert een kaart te nemen van een lege library - + %1 draws %2 card(s). %1 trekt een kaart%1 trekt %2 kaarten - + %1 is looking at %2. %1 is aan het kijken naar %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 kijkt naar de %4 %3 kaart %2.%1 kijkt naar de %4 %3 kaarten %2. - + bottom onderkant - + top bovenkant - + %1 turns %2 face-down. %1 draait %2 om met de voorkant naar beneden. - + %1 turns %2 face-up. %1 draait %2 om met de voorkant naar boven. - + The game has been closed. Het spel is afgesloten. - + The game has started. Het spel is begonnen. - + You are flooding the game. Please wait a couple of seconds. Je bent het spel aan het overstelpen. Wacht een paar seconden. - + %1 has joined the game. %1 heeft zich bij het spel aangesloten. - + %1 is now watching the game. %1 kijkt nu naar het spel. - + You have been kicked out of the game. Je bent uit het spel geschopt. - + %1 has left the game (%2). %1 heeft het spel verlaten (%2). - + %1 is not watching the game any more (%2). %1 kijkt niet meer naar het spel (%2). - + %1 is not ready to start the game any more. %1 is niet meer klaar om het spel te starten. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 schudt zijn of haar deck en raapt een nieuwe hand van %2 kaart.%1 schudt zijn of haar deck en raapt een nieuwe hand van %2 kaarten. - + %1 shuffles their deck and draws a new hand. %1 schudt zijn of haar deck en trekt een nieuwe hand. - + You are watching a replay of game #%1. Je kijkt naar een replay van spel #%1. - + %1 is ready to start the game. %1 is klaar om het spel te starten. - + cards an unknown amount of cards kaarten - + %1 card(s) a card for singular, %1 cards for plural één kaart%1 kaarten - + %1 lends %2 to %3. %1 leent %2 aan %3. - + %1 reveals %2 to %3. %1 onthult %2 naar %3. - + %1 reveals %2. %1 onthult %2. - + %1 randomly reveals %2%3 to %4. %1 onthult %2%3 aan %4 gebaseerd op willekeur. - + %1 randomly reveals %2%3. %1 onthult %2%3 gebaseerd op willekeur. - + %1 peeks at face down card #%2. %1 bekijkt verdekte kaart #%2. - + %1 peeks at face down card #%2: %3. %1 bekijkt verdekte kaart #%2: %3. - + %1 reveals %2%3 to %4. %1 onthult %2%3 aan %4. - + %1 reveals %2%3. %1 onthult %2%3. - + %1 reversed turn order, now it's %2. %1 keert de beurtvolgorde om, deze is nu %2. - + reversed omgekeerd - + normal normaal - + Heads Kop - + Tails Munt - + %1 flipped a coin. It landed as %2. %1 gooit een muntje om. Het landde %2. - + %1 rolls a %2 with a %3-sided die. %1 rolt een %2 met een %3-zijdige dobbelsteen. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 werpt %2 munten. Er zijn %3 koppen en %4 munten. - + %1 rolls a %2-sided dice %3 times: %4. %1 werpt een %2-zijdige dobbelsteen %3 keer: %4. - + %1's turn. %1s beurt. - + %1 sets annotation of %2 to %3. %1 verandert de annotatie van %2 naar %3. - + %1 places %2 %3%4 counter(s) on %5 (now %6). - + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + %1 failed to undo their last draw. - + %1 sets counter %2 to %3 (%4%5). %1 verandert teller %2 naar %3 (%4%5). - + %1 sets %2 to not untap normally. %1 zet %2 op niet tegelijk ontappen. - + %1 sets %2 to untap normally. %1 zet %2 op tegelijk ontappen. - + %1 removes the PT of %2. %1 verwijdert de stats van %2. - + %1 changes the PT of %2 from nothing to %4. %1 verandert de stats van %2 van niks naar %4. - + %1 changes the PT of %2 from %3 to %4. %1 verandert de stats van %2 van %3 naar %4. - + %1 has locked their sideboard. %1 heeft hun sideboard vergrendeld. - + %1 has unlocked their sideboard. %1 heeft hun sideboard ontgrendeld. - + %1 taps their permanents. %1 tapt alles. - + %1 untaps their permanents. %1 ontapt alles. - + %1 taps %2. %1 tapt %2. - + %1 untaps %2. %1 ontapt %2. - + %1 shuffles %2. %1 schudt %2. - + %1 shuffles the bottom %3 cards of %2. %1 schudt de onderste %3 kaarten van %2. - + %1 shuffles the top %3 cards of %2. %1 schudt de bovenste %3 kaarten van %2. - + %1 shuffles cards %3 - %4 of %2. %1 schudt kaarten %3 - %4 van %2. - + %1 unattaches %2. %1 maakt %2 los. - + %1 undoes their last draw. %1 legt hun laatst getrekte kaart terug. - + %1 undoes their last draw (%2). %1 legt hun laats getrekte kaart terug (%2). @@ -6405,115 +8057,125 @@ Cockatrice zal nu de kaartendatabase herladen. MessagesSettingsPage - + Word1 Word2 Word3 Woord1 Woord2 Woord3 - + Add New Message Bericht toevoegen - + Edit Message Bericht bewerken - + Remove Message Bericht Verwijderen - + Add message Bericht toevoegen - - + + Message: Bericht: - + Edit message Bericht bewerken - + Chat settings Chat instellingen - + Custom alert words Aanpasbare alerteringswoorden - + Enable chat mentions Schakel chat vermeldingen in - + Enable mention completer Vermeldings aanvuller inschakelen - + In-game message macros In-game bericht macro's - + How to use in-game message macros Uitleg gebruik van in-game bericht macro's - + Ignore chat room messages sent by unregistered users Chatroomberichten van niet-geregistreerde gebruikers negeren - + Ignore private messages sent by unregistered users Privéberichten van niet-geregistreerde gebruikers negeren - + Ignore private messages sent by non-buddy users - - + + Invert text color Text kleur inverteren - + Enable desktop notifications for private messages Desktopmeldingen voor privéberichten inschakelen - + Enable desktop notification for mentions Desktopmelding voor vermeldingen inschakelen - + Enable room message history on join Kamerboodschapsgeschiedenis op aansluiting inschakelen - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) (Kleur is hexadecimaal) - + Separate words with a space, alphanumeric characters only Woorden scheiden met een spatie, alleen alfanumerieke tekens @@ -6521,42 +8183,42 @@ Cockatrice zal nu de kaartendatabase herladen. MoveMenu - + Move to Verplaats naar - + &Top of library in random order %bovenkant library in willekeurige volgorde - + X cards from the top of library... X kaarten van bovenkant library... - + &Bottom of library in random order &Onderkant library in willekeurige volgorde - + T&able &Tafel - + &Hand &Hand - + &Graveyard &Graveyard - + &Exile &Exile @@ -6635,148 +8297,134 @@ Cockatrice zal nu de kaartendatabase herladen. PaletteEditorDialog - - + + Reset - - - Apply - - - - - - Save && Apply - - - - + ▼ Edit Palette - - + + ▶ Edit Palette - + Palette Editor — %1 - + <b>Palette Editor</b> &nbsp;·&nbsp; %1 - - This theme ships no default palette files - - - - - Replace current colours with the theme author's defaults - - - - - Switch between the light and dark palette files + + + Save - Editing: + Delete this scheme's custom palette and revert to the theme default (or the application palette) - Show or hide the per-role colour grid for manual tweaks + Switch between the light and dark palette files + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + ↺ Revert to theme default - + Discard unsaved edits and restore the last saved palette - - Preview this palette without saving to disk + + Cannot save: this theme has no writable directory - + Write palette-%1.toml and reload the theme - - Cannot save: this theme has no directory on disk - - - - + + Save failed - + + Could not write %1 to: %2 - - - No default found - - - - - No default palette file found for the "%1" scheme. - - PaletteGridWidget - + Active - + Disabled - + Inactive - + Normal interactive state - + Widget is disabled / not interactive - + Window is in background / unfocused + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + Phase @@ -6899,30 +8547,21 @@ Cockatrice zal nu de kaartendatabase herladen. Einde van de beurt fase - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - en - - PlayerActions - - - - + + + + grave graveyard - - - - + + + + exile exile @@ -7041,16 +8680,272 @@ Cockatrice zal nu de kaartendatabase herladen. PlayerMenu - + Player "%1" Speler "%1" - + &Counters &Counters + + PlaymatCollectionDialog + + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + PrintingDisabledInfoWidget @@ -7075,12 +8970,12 @@ Deze instelling betekend dat je alleen de geprioritiseerde druk van iedere kaart PrintingSelector - + Display Navigation Buttons Navigatieknoppen weergeven - + Printing Selector Drukkiezer @@ -7088,25 +8983,65 @@ Deze instelling betekend dat je alleen de geprioritiseerde druk van iedere kaart PrintingSelectorCardOverlayWidget - + Preference Voorkeur - + Pin Printing Druk Vastpinnen - + Unpin Printing Druk Lospinnen - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards Gerelateerde kaarten weergeven + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -7137,28 +9072,28 @@ Deze instelling betekend dat je alleen de geprioritiseerde druk van iedere kaart PrintingSelectorCardSortingWidget - + Alphabetical Alphabetisch - + Preference Voorkeur - + Release Date Datum van Uitgave - - + + Descending Omlaag - + Ascending Omhoog @@ -7174,95 +9109,103 @@ Deze instelling betekend dat je alleen de geprioritiseerde druk van iedere kaart PtMenu - + Power / toughness Kracht / hardheid - + &Increase power Ver&hoog kracht - + &Decrease power Ver&laag kracht - + I&ncrease toughness Verh&oog hardheid - + D&ecrease toughness Verl&aag hardheid - + In&crease power and toughness Verhoo&g kracht en hardheid - + Dec&rease power and toughness Verlaa&g kracht en hardheid - + Increase power and decrease toughness Verhoog kracht en verlaag hardheid - + Decrease power and increase toughness Verlaag kracht en verhoog hardheid - + Set &power and toughness... Kracht en hardheid &instellen... - + Reset p&ower and toughness &Herstel kracht en hardheid + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services Diensten - + Hide %1 Verberg %1 - + Hide Others Verberg Anderen - + Show All Toon Alle - + Preferences... Voorkeuren... - + Quit %1 Stop %1 - + About %1 Over %1 @@ -7270,17 +9213,17 @@ Deze instelling betekend dat je alleen de geprioritiseerde druk van iedere kaart QObject - + Cockatrice card database (*.xml) Cockatrice kaartendatabase (*.xml) - + All files (*.*) Alle bestanden (*.*) - + Cockatrice replays (*.cor) Cockatrice replays (*.cor) @@ -7300,17 +9243,22 @@ Deze instelling betekend dat je alleen de geprioritiseerde druk van iedere kaart Tokens - + + Maybeboard + + + + Overwrite Existing File? Bestaand Bestand Overschrijven? - + A .cod version of this deck already exists. Overwrite it? Een .cod versie van dit deck bestaat al. Deze overschrijven? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7327,7 +9275,7 @@ Je zal de Setbeheerder moeten gebruiken, te vinden bij Kaartendatabase -> Beh Weet je zeker dat je deze functie wil inschakelen? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7344,100 +9292,110 @@ Je ook de Setbeheerder gebruiken om de sorteervolgorde voor de drukken in de Dru Weet je zeker dat je deze functie wil uitschakelen? - + Confirm Change Bevestig Wijziging + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK OK - + Save Opslaan - + Save All Alles Opslaan - + Open Open - + &Yes &Ja - + Yes to &All Ja op A&lle - + &No &Nee - + N&o to All Nee op &Alle - + Abort Afbreken - + Retry Opnieuw proberen - + Ignore Negeren - + Close Sluiten - + Cancel Annuleren - + Discard Afleggen - + Help Hulp - + Apply Toepassen - + Reset Resetten - + Restore Defaults Standaardinstellingen herstellen @@ -7445,121 +9403,189 @@ Weet je zeker dat je deze functie wil uitschakelen? QuickSetupPanel - + %1% - + <b>Quick Setup</b> - + Generate all palette roles automatically from a single accent colour - + Accent: - + Primary hue. Used directly for highlights and links. At high intensity it also tints buttons and backgrounds. - + Intensity: - + Subtle - + Full colour - + 0–30 Subtle tint — only highlights and links change hue 30–70 Accented — buttons, tooltips, and borders join in 70–100 Full colour — backgrounds, everything - + 70% - - - Generate ↓ - - - - - Derive all palette roles from the accent colour above. -Fine-tune individual colours in the grid afterwards. - - RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name Naam - + ID ID - + Upload time Upload tijd + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID ID - + Name Naam - + Players Spelers - + Keep Bewaar - + Time started Tijd begonnen - + Duration (sec) Duur (sec) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu @@ -7601,37 +9627,37 @@ Fine-tune individual colours in the grid afterwards. RoomSelector - + Rooms Kamers - + Joi&n &Deelnemen - + Room Kamer - + Description Omschrijving - + Permissions Toestemmingen - + Players Spelers - + Games Spellen @@ -7639,7 +9665,7 @@ Fine-tune individual colours in the grid afterwards. SayMenu - + S&ay &Zeg @@ -7647,32 +9673,32 @@ Fine-tune individual colours in the grid afterwards. SequenceEdit - + Choose an action from the table Kies een actie uit de tabel - + Hit the key/combination of keys you want to set for this action Druk op de toets/combinatie van toetsen die je voor deze functie wilt gebruiken. - + Invalid key Ongeldige toets - + Shortcut already in use by: Sneltoets reeds in gebruik door: - + Clear Wis - + Restore default Herstel standaard @@ -7705,56 +9731,92 @@ Fine-tune individual colours in the grid afterwards. releasedatum + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts Alle standaard snelkoppelingen herstellen - + Do you really want to restore all default shortcuts? Wilt u echt alle standaard snelkoppelingen herstellen? - + Clear all default shortcuts Alle standaard snelkoppelingen wissen - + Do you really want to clear all shortcuts? Wil je echt alle snelkoppelingen vrijmaken? - + Section: Sectie: - + Action: Functie: - + Shortcut: Sneltoets: - + + Shortcut editor + + + + How to set custom shortcuts Hoe aangepaste snelkoppelingen in te stellen - + Clear all shortcuts Alle sneltoetsen wissen - + Search by shortcut name Zoek op sneltoetsnaam @@ -7810,12 +9872,12 @@ Controleer uw snelkoppelingsinstellingen! SideboardMenu - + &Sideboard &Sideboard - + &View sideboard &Bekijk sideboard @@ -7823,27 +9885,27 @@ Controleer uw snelkoppelingsinstellingen! SoundSettingsPage - + Enable &sounds &Geluiden inschakelen - + Current sounds theme: Huidige geluiden thema: - + Test system sound engine Test geluid engine van systeem - + Sound settings Geluidsinstellingen - + Master volume Hoofdvolume @@ -7851,48 +9913,48 @@ Controleer uw snelkoppelingsinstellingen! SpoilerBackgroundUpdater - + Spoilers season has ended Spoilers seizoen is afgelopen - + Deleting spoiler.xml. Please run Oracle Verwijdert spoiler.xml. Gelieve Oracle te draaien - - + + Spoilers download failed Spoilers downloaden mislukt - + No internet connection Geen internetverbinding - + Error Fout - + Spoilers already up to date Spoilers reeds up-to-date - + No new spoilers added Geen nieuwe spoilers toegevoegd - + Spoilers have been updated! Spoilers zijn bijgewerkt! - + Last change: Laatste Wijziging: @@ -7900,157 +9962,170 @@ Controleer uw snelkoppelingsinstellingen! StableReleaseChannel - + Default Standaard - + No reply received from the release update server. Geen antwoord ontvangen van de release update server. - + Invalid reply received from the release update server. Ongeldig antwoord ontvangen van de release update server. - + No reply received from the tag update server. Geen antwoord ontvangen van de tag update server. - + Invalid reply received from the tag update server. Ongeldig antwoord ontvangen van de tag update server. + + StatsTally + + + Total Power + + + + + Total Toughness + + + StorageSettingsPage - - - + + + Success - + Cached card pictures have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + In-memory (currently loaded) card pictures have been reset. - + Card Picture Loader Caching Method: - + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. - + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. - + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. - + Delete Cached Images - + Delete Saved Images - + Clear In-Memory Images - + Card Picture Loader Cache Method - + Network Cache - + Filesystem - + In-Memory Picture Cache - + Network Cache Size: - + On-disk cache for downloaded pictures - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + In-memory cache for pictures not currently on screen - + Naming scheme: - + Day(s) @@ -8058,17 +10133,33 @@ Controleer uw snelkoppelingsinstellingen! TabAccount - + + + My Reports + + + + Add to Buddy List Aan Maatjeslijst toevoegen - + Add to Ignore List Voeg toe aan Negeerlijst - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account Account @@ -8189,118 +10280,118 @@ Controleer uw snelkoppelingsinstellingen! TabArchidekt - - - + + + Desc. Afl. - - + + AND EN - - + + Require ALL selected colors Vereis ALLE gekozen kleuren - - + + Deck name... Decknaam... - - + + Owner... Eigenaar... - - + + Packages Pakketten - - + + Advanced Filters Geavanceerde Filters - + Bracket: Bracket: - - + + Any Geen Voorkeur - - + + Contains card... Bevat kaart... - - + + Commander... Commander... - - + + Tag... Tag... - - + + Deck Size Deckgrootte - + Cards: Kaarten: - - + + Asc. Opl. - + Sort by: Sorteer op: - + Filter by: Filter op: - + Display Settings Weergavensinstellingen - - + + Search Zoeken - - + + Formats Formats @@ -8313,42 +10404,42 @@ Controleer uw snelkoppelingsinstellingen! TabCardArtRules - + Card: - + ProviderId: - + Mode: - + Reason: - + Type a card name... - + Add rule - + Remove rule - + Refresh @@ -8361,52 +10452,52 @@ Controleer uw snelkoppelingsinstellingen! TabDeckEditor - + Card Info Kaart info - + Deck Deck - + Filters Filters - + &View &Bekijk - + Card Database Kaartendatabase - + Printing Druk - + Visible Zichtbaar - + Floating Zwevend - + Reset layout Indeling resetten - + Deck: %1 Dek: %1 @@ -8414,49 +10505,49 @@ Controleer uw snelkoppelingsinstellingen! TabDeckEditorVisual - + Visual Deck: %1 Visueel Deck: %1 - + &Visual Deck Editor &Visuele Deckbewerker - - + + Card Info Kaarteninfo - - + + Deck Deck - + &View &Bekijk - + Printing Druk - + Visible Zichtbaar - + Floating Zwevend - + Reset layout Herstel indeling @@ -8464,22 +10555,22 @@ Controleer uw snelkoppelingsinstellingen! TabDeckEditorVisualTabWidget - + Visual Deck View Visuele Deckweergave - + Visual Database Display Visuele Databaseweergave - + Deck Analytics Deckanalyses - + Sample Hand Proefhand @@ -8487,138 +10578,236 @@ Controleer uw snelkoppelingsinstellingen! TabDeckStorage - + Local file system Plaatselijk bestandssysteem - + Server deck storage Dekopslag op de server - - + + Open in deck editor Open deck in bewerker - + Rename deck or folder Hernoem deck of map - + Upload deck Upload dek - + Download deck Download dek - - - - + + + + New folder Nieuwe map - - + + Delete Verwijder - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder Deckmap openen - + Rename local folder Hernoem plaatselijke map - + Rename local file Hernoem plaatselijk bestand - + New name: Nieuwe naam: - - - - + + + + + + + Error Foutmelding - + Rename failed Hernoemen mislukt - - + + + Invalid deck file Ongeldig deck bestand - + Enter deck name Geeft dek naam in - + This decklist does not have a name. Please enter a name: De deklijst heeft geen naam. Geef een naam in aub. - + Unnamed deck Onbenoemd dek - + Failed to upload deck to server Uploaded van deck naar server mislukt - + Delete local file Verwijder plaatselijk bestand - + Are you sure you want to delete the selected files? Weet je zeker dat je de opgeslagen bestanden wil verwijderen? - + Delete remote decks Verwijder deck van server - + Are you sure you want to delete the selected decks? Weet je zeker dat je de geselekteerde decks wil verwijderen? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: Naam van de map: - + Deck Storage Deckopslag @@ -8626,20 +10815,271 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage Visuele Deckopslag - + Error Foutmelding - + Could not open deck at %1 Kon deck op %1 niet openen + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -8652,27 +11092,27 @@ Please enter a name: TabEdhRecMain - + &Cards &Kaarten - + Top Commanders Top Commanders - + Tags Tags - + Search for a card ... Zoeken op kaart... - + Search Zoeken @@ -8685,191 +11125,207 @@ Please enter a name: TabGame - - - + + + Replay Replay - - + + Game Spel - - + + Player List Spelerslijst - - + + + Invite + + + + + Card Info Kaartinfo - - + + Messages Berichten - - + + Replay Timeline Replay Tijdslijn - + &Phases Fases - + &Game Spel - + Next &phase Volgende fase - + Next phase with &action Volgende fase met &handeling - + Next &turn Volgende beurt - + Reverse turn order Keer beurtvolgorde om - + &Remove all local arrows Verwijder alle plaatselijke pijlen - + Rotate View Cl&ockwise Draai de weergave met de klok &mee - + Rotate View Co&unterclockwise Draai de weergave &tegen de klok in - + Game &information Spelgegevens - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede Opgeven &terugdraaien - - - + + + &Concede &Geef op - + &Leave game Verlaat spel - + C&lose replay Sluit opgeslagen spel - + &Focus Chat &Focus Chat - + &Say: Zeg: - + Selected cards Geselecteerde kaarten - + &View &Bekijk - + Visible Zichtbaar - + Floating Zwevend - + Reset layout Reset indeling - + Concede Opgeven - + Are you sure you want to concede this game? Ben je zeker dat je wilt opgeven voor dit spel? - + Unconcede Opgeven ongedaan maken - + You have already conceded. Do you want to return to this game? Je hebt al opgegeven. Wil je nar dit spel terugkeren? - + Leave game Verlaat spel - + Are you sure you want to leave this game? Ben je zeker dat je dit spel wilt verlaten? - + A player has joined game #%1 Een speler sluitte aan bij spel #%1 - + %1 has joined the game %1 sluitte bij het spel aan - + You have been kicked out of the game. Je bent uit het spel gegooid. @@ -8885,158 +11341,158 @@ Please enter a name: TabLog - + Logs Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Tijd;Afzendernaam;AfzenderIP; Bericht;DoelID;DoelNaam - + Room Logs Kamerlogboeken - + Game Logs Spellogboeken - + Chat Logs Chat Logs - - + + Error Fout - + You must select at least one filter. U moet ten minste één filter selecteren. - + You have to select a valid number of days to locate. U moet een geldig aantal dagen selecteren om te zoeken. - + Username: Gebruikersnaam: - + IP Address: IP-adres: - + Game Name: Spelnaam: - + GameID: GameID: - + Message: Boodschap: - + Main Room Hoofdkamer - + Game Room Spelkamer - + Private Chat Privéchat - + Past X Days: Afgelopen X dagen: - + Today Vandaag - + Last Hour Afgelopen uur - + Maximum Results: Maximale resultaten: - + At least one filter is required. The more information you put in, the more specific your results will be. Er is minstens één filter nodig. Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn. - + Get User Logs Gebruikerslogboeken ontvangen - + Clear Filters Wis filters - + Filters Filters - + Log Locations Loglokaties - + Date Range Datumbereik - + Maximum Results Maximale resultaten - - + + Message History Bericht Geschiedenis - + Failed to collect message history information. Het is mislukt om informatie over de berichtgeschiedenis te verzamelen. - + There are no messages for the selected filters. Er zijn geen berichten voor de geselecteerde filters. @@ -9044,219 +11500,559 @@ Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn. TabMessage - + Private &chat Privaat gesprek - + &Leave Verlaat - + %1 - Private chat %1 - Privaat gesprek - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. Deze gebruiker negeert u, ze kunnen uw berichten niet zien in de hoofdchat en u kunt niet deelnemen aan hun spelletjes. - + Private message from Privé bericht van - + %1 has left the server. %1 heeft zich afgemeld van de server. - + %1 has joined the server. %1 heeft zich aangemeld op de server + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system Plaatselijk bestandssysteem - + Server replay storage Opslag gespeelde spellen op server - - + + Watch replay Bekijk opgeslagen spel - + Rename Hernoemen - - + + New folder Nieuwe map - - + + Delete Verwijder - + Open replays folder Open replays map - + Download replay Download opgeslagen spel. - + Toggle expiration lock Schakel expiratieslot om - + Get replay share code Ontvang replay-deelcode - - + + Look up replay by share code Replay opzoeken met replay-deelcode - + Rename local folder Hernoem plaatselijke map - + Rename local file Hernoem plaatselijk bestand - + New name: Nieuwe naam: - + Error Foutmelding - + Rename failed Hernoemen mislukt - + Name of new folder: Naam van nieuwe map: - + Delete local file Verwijder plaatselijk bestand - + Are you sure you want to delete the selected files? Weet je zeker dat je de geselecteerde bestanden wil verwijderen? - + Are you sure you want to delete the selected replays? Weet je zeker dat je de geselecteerde replays wil verwijderen? - + Failed to get code Ontvangen code mislukt - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. Deze server ondersteund het delen van replays niet of deze staat het niet toe aan jou. - - - + + + Failed Mislukt - + Could not get replay code Ontvangen replay-code niet gelukt - + Replay Share Code Replay-deelcode - + Others can use this code to add the replay to their list of remote replays: %1 Anderen kunnen deze code gebruiken om de replay aan hun lijst van serverreplays toe te voegen: %1 - + Copy to clipboard Kopieer naar klembord - + Replay share code Replay-deelcode - + Replay code found Replay-code gevonden - + Replay was added, or you already had access to it. Replay toegevoegd, of je had misschien al toegang tot deze. - + Replay code not found Replay-code niet gevonden - + Failed to submit code Versturen code mislukt - + Unexpected error Onverwachte foutmelding - + Delete remote replay Verwijder op server opgeslagen spel @@ -9266,65 +12062,478 @@ Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn.Spel-replays + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - - Friends - - - - - Online - - - - - Ignored - - - - + &Say: Zeg: - + Chat Gesprek - + &Room Kamer - + &Leave room Verlaat kamer - + &Clear chat Maak gespreksgeschiedenis leeg - + Chat Settings... Gespreksinstellingen... - + mentioned you. heeft je genoemd. - + Click to view Klik om weer te geven - + You are flooding the chat. Please wait a couple of seconds. Je bent de chat aan het overstelpen. Wacht enkele seconden a.u.b. @@ -9332,35 +12541,35 @@ Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn. TabServer - + Server Server - - - - + + + + Error Fout - + Failed to join the server room: it doesn't exist on the server. Aansluiten bij kamer mislukt: deze bestaat niet op de server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. De server denkt dat je in de kamer bent, maar de client is niet in staat om deze te tonen. Probeer de client opnieuw te starten. - + You do not have the required permission to join this server room. U heeft niet de vereiste toestemming om bij deze kamer aan te sluiten. - + Failed to join the server room due to an unknown error: %1. Mislukt om bij de kamer aan te sluiten door een onbekende fout: %1. @@ -9368,97 +12577,173 @@ Hoe meer informatie je inbrengt, hoe specifieker je resultaten zullen zijn. TabSupervisor - + Deck Editor Deckbewerker - + Visual Deck Editor Visuele Deckbewerker - + EDHRec EDHRec - + Archidekt Archidekt - + Home Thuis - + &Visual Deck Storage &Visuele Deckopslag - + Visual Database Display Visuele Databaseweergave - + Server Server - + Account Account - + Deck Storage Deckopslag - + Game Replays Spel-replays - + Administration Beheer - + Logs Logs - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? Weet je het zeker? - + There are still open games. Are you sure you want to quit? Er staan nog spellen open. Ben je zeker dat je wilt afsluiten? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view Klik om weer te geven - + Your buddy %1 has signed on! Je maatje %1 heeft in gelogd! - + Unknown Event Onbekend Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -9469,39 +12754,39 @@ Dit bericht kan betekenen dat er een nieuwe versie van Cockatrice beschikbaar is Om uw client bij te werken, ga naar Hulp -> Controleer op updates. - + Idle Timeout Inactiviteit Time-out - + You are about to be logged out due to inactivity. U staat op het punt uitgelogd te worden wegens inactiviteit. - + Promotion Promotie - + You have been promoted. Please log out and back in for changes to take effect. Je bent bevorderd. Gelieve uit te loggen en weer in te loggen om de wijzigingen in werking te laten treden. - + Warned Gewaarschuwd - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. U heeft een waarschuwing ontvangen wegens %1. Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties tegen u worden ondernomen. Als u vragen heeft, kunt u een privé-bericht sturen naar een moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) U heeft het volgende bericht van de server ontvangen. @@ -9521,6 +12806,34 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties Visuele Databaseweergave + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -9535,6 +12848,65 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties Niet in staat om het dek te analyseren. + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -9606,57 +12978,52 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties UserCardArtSettingsDialog - + Card Art Settings - + Type a card name... - + Card name: - - Card ProviderId: + + Printing: - + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + Left margin (%): - + Right margin (%): - - Vertical offset: - - - - - Zoom: - - - - - Parameters - - - - - Preview - - - - + Remove Banner Card @@ -9664,216 +13031,266 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties UserContextMenu - + User &details Gebruikersdetails - + Private &chat Privaat gesprek - + Show this user's &games Toon spellen van deze gebruiker - + + View this user's &public decks + + + + Add to &buddy list Voeg toe aan maatjeslijst - + Remove from &buddy list Verwijder uit maatjeslijst - + Add to &ignore list Voeg toe aan negatielijst - + Remove from &ignore list Verwijder uit negatielijst - + Kick from &game Gooi uit spel - + + Report user + + + + Warn user Waarschuw gebruiker - + View user's war&n history Bekijk waarschuwings&geschiedenis van gebruiker - + Ban from &server Verban van de &server - + View user's &ban history Bekijk &bangeschiedenis van gebruiker - + &Promote user to moderator &Bevorder gebruiker tot moderator - + Dem&ote user from moderator Demoteer gebruiker van moderator - + Promote user to &judge Bevorder gebruiker tot &judge - + Demote user from judge Demoteer gebruiker van judge - + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes Bekijk adminnotities - - + Investigate user + + + + + + Error Foutmelding - + This user does not exist. Deze gebruiker bestaat niet. - + You are being ignored by %1 and can't see their games. Je wordt genegeerd door %1 en je kan diens spellen niet zien. - + Could not get %1's games. Kon %1's spellen niet ophalen. - + %1's games %1's spellen - - - + + + Ban History Ban Geschiedenis - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Ban Tijd;Moderator;Ban Lengte; Ban Reden; Zichtbare Reden - + User has never been banned. De gebruiker is nog nooit gebant. - + Failed to collect ban information. Mislukt in het verzamelen van gegevens over ban. - - - + + + Warning History Waarschuwingsgeschiedenis - + Warning Time;Moderator;User Name;Reason Waarschuwingstijd;Moderator;Gebruikersnaam;Reden - + User has never been warned. De gebruiker is nog nooit gewaarschuwd. - + Failed to collect warning information. Mislukt in het verzamelen van gegevens over waarschuwing. - + Failed to get admin notes. Ontvangen adminnotities mislukt. - - + + Success Succesvol - + Successfully promoted user. Succesvolle promotie van gebruiker. - + Successfully demoted user. Succesvolle demotie van gebruiker. - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + Kick Player - + Are you sure you want to kick this player from the game? - - - + + + Failed Mislukt - + Failed to promote user. Mislukt in het bevorderen van gebruiker. - + Failed to demote user. De gebruiker is niet gedemoteerd. - + Copy hash to clipboard Kopieer hash naar klembord - + Remove this user's messages Verwijder de berichten van deze gebruiker @@ -9911,61 +13328,61 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties Administrator - + Moderator Moderator - + Registered user Geregistreerde gebruiker - - + + Unregistered user Ongeregistreerde gebruiker - + Judge Judge - + Unknown Onbekend - + The entered password does not match your account. Het opgegeven wachtwoord komt niet overeen met jouw account. - - - - + + + + Information Gegevens - + User information updated. Gebruikersgegevens bijgewerkt. - - - - - - + + + + + - - - + + + + Error Fout @@ -9989,283 +13406,292 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties Edit Banner Card + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) %n jaar, %n jaar, - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format %10%n dag %20%10%n Dagen %20 - + Enter Password Wachtwoord Invoeren - + Password verification is required in order to change your email address Wachtwoord controle is vereist om je e-mailadres te veranderen - - - + + + An error occurred while trying to update your user information. Er is een fout opgetreden bij het bijwerken van uw gebruikersgegevens. - + The selected card is blacklisted on this server or another error occurred. - + Banner card removed. - + Banner card updated. - + This server does not permit you to update your user informations. Deze server staat jou niet toe om je gebruikersgegevens bij te werken. - + Password changed. Wachtwoord gewijzigd. - + This server does not permit you to change your password. Deze server staat u niet toe uw wachtwoord te wijzigen. - + The new password is too short. Het nieuwe wachtwoord is te kort. - + The old password is incorrect. Het oude wachtwoord is onjuist. - + Avatar updated. Avatar bijgewerkt. - + This server does not permit you to update your avatar. Deze server laat je niet toe om je avatar bij te werken. - + An error occured while trying to update your avatar. - - - This server does not permit you to update your user informations. - An error occured while trying to updater your avatar. - Deze server staat jou niet toe om je gebruikersgegevens bij te werken. - UserInfoPopup - - + + Games - + Chat - + Open private chat - + Profile - + View user profile - + Show this user's games - + + Invite + + + + + Invite to your game + + + + − Buddy - + Remove from buddy list - + + Buddy - + Add to buddy list - + − Ignore - + Remove from ignore list - + + Ignore - + Add to ignore list - + Ban - + Ban from server - + Warn - + Warn user - + Ban log - + View ban history - + Warn log - + View warning history - + Notes - + View admin notes - + − Mod - + Demote from moderator - + + Mod - + Promote to moderator - + − Judge - + Demote from judge - + + Judge - + Promote to judge - + Join game - + Spectate - - + + Loading games… - + Could not load games. - + No active games. @@ -10273,223 +13699,344 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties UserInterfaceSettingsPage - + General interface settings Algemene weergave-instellingen - + &Double-click cards to play them (instead of single-click) Dubbelklik op kaarten om ze te spelen (in plaats van enkele klik) - + &Clicking plays all selected cards (instead of just the clicked card) &Klikken speelt alle geselecteerde kaarten (in plaats van allen de geklikte kaart) - + &Play all nonlands onto the stack (not the battlefield) by default Speel standaard alle niet-landen op de stapel (niet op het slagveld) - + Do not delete &arrows inside of subphases Verwijder &pijlen niet binnen subphases - + Close card view window when last card is removed Sluit kaartenweergavevenster wanner laatste kaart verwijderd is - + Auto focus search bar when card view window is opened Focus zoekbalk onmiddelijk wanneer kaartenweergavevenster is geopent - + Annotate card text on tokens Maak aantekeningen bij de kaarttekst op tokens - + Show selection count during drag selection - + Show total selection count - - Show subtype breakdown in selection tally + + + Disabled - + + + Enabled + + + + + + Automatic + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + Use tear-off menus, allowing right click menus to persist on screen Gebruik tear-off menus, laat rechtermuisknopmenu's op het scherm staan - + Keep game chat focused when clicking in game (Note: disables card view search bar) - + Notifications settings Notificatie instellingen - + Enable notifications in taskbar Activeer meldingen in de taakbalk - + Notify in the taskbar for game events while you are spectating Meld spelgebeurtenissen in de taakbalk terwijl je toekijkt - + Notify in the taskbar when users in your buddy list connect Notificatie in de taakbalk wanneer gebruikers in je buddy lijst verbinding maken - + Animation settings Animatie-instellingen - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation &Tap/Untap animatie - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings Deckbewerker/opslag instellingen - + Open deck in new tab by default Deck standaard in niewe tab openen - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby Visuele deckopslag gebruiken in spelontvangstscherm - + Use selection animation for Visual Deck Storage Gebruik selectieanimatie voor Visuele Deckopslag - + When adding a tag in the visual deck storage to a .txt deck: Wanner een tag aan een .txt deck wordt toegevoegd in de visuele deckopslag: - - do nothing - niks doen + + Do nothing + - - ask to convert to .cod - vragen om naar .cod om te zetten + + Ask to convert to .cod + - - always convert to .cod - altijd omzetten naar .cod + + Always convert to .cod + - + Default deck editor type Standaard deckbewerker soort - + Classic Deck Editor Klassieke Deckbewerker - + Visual Deck Editor Visuele Deckbewerker - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings Replayinstellingen - + Buffer time for backwards skip via shortcut: + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + Flip coin - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -10526,22 +14073,22 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... - + Save Filter - + Save all currently applied filters to a file - + Enter filename... @@ -10549,72 +14096,72 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDatabaseDisplayFilterToolbarWidget - + Sort by - + Filter by - + Save and load filters - + Filter by exact card name - + Filter by card main-type - + Filter by card sub-type - + Filter by set - + Filter by format legality - + Save/Load - + Name - + Main Type - + Sub Type - + Sets - + Formats @@ -10716,7 +14263,7 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -10724,19 +14271,19 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -10782,28 +14329,28 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - - + + Visual - + Loading database ... - + Clear all filters - + Table @@ -10811,43 +14358,43 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups Klik en sleep om de sorteervolgorde binnen de groepen aan te passen - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat @@ -10863,12 +14410,12 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -10876,17 +14423,17 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -10894,7 +14441,7 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -10902,52 +14449,57 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + Show Color Identity - + Show Tags On Deck Previews - + + Show Upload Time + + + + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -10955,7 +14507,7 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -10963,22 +14515,22 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -10986,17 +14538,22 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + + Select decks to share + + + + Visual Deck Storage Settings @@ -11004,177 +14561,202 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties WarningDialog - + Which warning would you like to send? Welke waarschuwing wilt u sturen? - + Redact all messages from this user in all rooms Verwijder alle berichten van deze gebruiker in alle kamers - + &OK &OK - + &Cancel &Cancel - + Warn user for misconduct Waarschuw gebruiker voor wangedrag - - + + Error Fout - + User name to send a warning to can not be blank, please specify a user to warn. Gebruikersnaam om een waarschuwing naar te sturen kan niet leeg zijn, geef een gebruiker op om te waarschuwen. - + Warning to use can not be blank, please select a valid warning to send. Te gebruiken waarschuwing kan niet leeg zijn, selecteer een geldige waarschuwing om te verzenden. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top Verplaats de geselecteerde set naar boven - + Move selected set up Verplaats de geselecteerde set naar boven - + Move selected set down Verplaats de geselecteerde set naar beneden - + Move selected set to the bottom Verplaats de geselecteerde set naar beneden - - Search by set name, code, or type - Zoeken op setnaam, -code of -type - - - + Default order Standaard volgorde - + Restore original art priority order Herstel oorspronkelijke volgorde prioriteit afbeeldingen - + Enable all sets Alle sets inschakelen - + Disable all sets Alle sets uitschakelen - + Enable selected set(s) Geselecteerde set(s) inschakelen - + Disable selected set(s) Geselecteerde set(s) uitschakelen - + Deck Editor Deckbewerker - + Use CTRL+A to select all sets in the view. Gebruik CTRL+A om alle sets in de lijst te selecteren. - + Only cards in enabled sets will appear in the card list of the deck editor. Alleen kaarten in de ingeschakelde sets zullen verschijnen in de kaartlijst van de deckbewerker. - + Image priority is decided in the following order: De prioriteit voor afbeeldingen wordt in de volgende volgorde vastgesteld: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki eerst de CUSTOM Map (%1), daarna de Ingeschakelde Sets in deze dialoog (Boven naar Beneden) - + Include cards rebalanced for Alchemy [requires restart] - + Card Art Afbeelding - + + Search by set name, code, type, or release date + + + + How to use custom card art Hoe aangepaste kaart afbeeldingen te gebruiken - + Hints Tips - + Note Opmerking - + Sorting by column allows you to find a set while not changing set priority. Sorteren op kolom stelt u in staat om een set te vinden zonder de ingestelde prioriteit te wijzigen. - + To enable ordering again, click the column header until this message disappears. Om rangschikken weer mogelijk te maken, klik op de kop van de kolom totdat dit bericht verdwijnt. - + Use the current sorting as the set priority instead Gebruik de huidige sortering als de ingestelde prioriteit - + Sorts the set priority using the same column Sorteert de prioriteit van sets met behulp van dezelfde kolom - + Manage sets Beheer sets @@ -11182,93 +14764,85 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing Schudden bij afsluiten - + pile view Stapelweergave - - i18n - - - English - Nederlands (Dutch) - - main - + Connect on startup Verbinding maken bij het opstarten - + Debug to file Debug naar bestand @@ -12052,6 +15626,16 @@ Gelieve af te zien van deelname aan deze activiteit of er kunnen verdere acties Graveyard Graveyard + + + Report Queue + + + + + Moderation + + diff --git a/cockatrice/translations/cockatrice_pl.ts b/cockatrice/translations/cockatrice_pl.ts index 29b7b3896..06d6d6e3d 100644 --- a/cockatrice/translations/cockatrice_pl.ts +++ b/cockatrice/translations/cockatrice_pl.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Ustaw znacznik… @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Ustaw znacznik - + New value for counter '%1': Nowa wartość dla znacznika '%1' @@ -36,64 +36,113 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. - + There are no cards in your deck to be exported + + AccountSetupPage + + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + AddAnalyticsPanelDialog @@ -105,12 +154,12 @@ Please check that the directory is writable and try again. AdminNotesDialog - + Update Notes Aktualności - + Admin Notes for %1 Notatki Administracyjne dla %1 @@ -136,218 +185,304 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error Błąd - + Could not create themes directory at '%1'. Nie można stworzyć folderu motywów w '%1'. - + Theme settings Ustawienia motywu - + Current theme: Obecny motyw: - + Open themes folder Otwórz folder motywów - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings Ustawienia menu - + Show keyboard shortcuts in right-click menus Pokazuj skróty klawiszowe w menu kontekstowym - + Show game filter toolbar above list in room tab - + Card rendering Rendering kart - + Display card names on cards having a picture Wyświetl nazwy kart na kartach z obrazkami - + Auto-Rotate cards with sideways layout Automatycznie obracaj karty horyzontalne - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Light - + Dark - + + System - + + Show all playmats + + + + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + Active theme palette: - + + Active theme style: + + + + + Qt widget style saved to this theme ("System" lets the application decide) + + + + Edit theme palette - + Home tab settings - + Display card name of background in bottom right - + + Dim the home tab background + + + + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + Styling settings - + Style user list - + Card printings - + Bump sets that the deck contains cards from to the top in the printing selector Podbijaj dodatki z których karty są w talii. - + Scale cards on mouse over Skaluj karty po najechaniu kursorem myszy - + Use rounded card corners - + Card layout - + Minimum overlap percentage of cards on the stack and in vertical hand Minimalny procent zachodzenia kart na siebie na stosie oraz w ręce pionowej - + Maximum initial height for card view window: Maksymalna podstawowa wysokość okna podglądu karty - - + + rows rzędy - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Układ ręki - + Display hand horizontally (wastes space) Wyświetl karty na ręce poziomo (marnuje miejsce) - + Enable left justification Wyrównuj karty do lewej strony pola - + Table grid layout Układ stołu - + Invert vertical coordinate Odwróć współrzędne pionowe - + Minimum player count for multi-column layout: Minimalna liczba graczy dla widoku wielokolumnowego - + Maximum font size for information displayed on cards: Maksymalny rozmiar czcionki dla informacji wyświetlanych na kartach @@ -355,12 +490,12 @@ Please check that the directory is writable and try again. ArchidektApiResponseDeckDisplayWidget - + Back to results - + Open Deck in Deck Editor @@ -386,112 +521,112 @@ Please check that the directory is writable and try again. BanDialog - + ban &user name zbanuj nazwę &użytkownika - + ban &IP address zbanuj adres &IP - + ban client I&D ID zablokowanego konta - + Ban type Rodzaj bana - + &permanent ban ban &permanentny - + &temporary ban ban &tymczasowy - + &Days: &Dni: - + &Hours: &Godziny: - + &Minutes: &Minuty: - + Duration of the ban Czas trwania bana - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Podaj przyczynę bana. Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowanej osoby. - + Please enter the reason for the ban that will be visible to the banned person. Podaj przyczynę bana do wglądu dla banowanej osoby. - + Redact all messages from this user in all rooms Redaguj wiadomości od tego użytkownika we wszystkich pokojach - + &OK &OK - + &Cancel &Anuluj - + Ban user from server Zbanuj użytkownika z serwera - - - - + + + + Error Błąd - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Aby ustawić bana, musisz wybrać nazwę, IP, ID klienta lub dowolną kombinację tej trójki. - + You must have a value in the name ban when selecting the name ban checkbox. Pole imienia musi być wypełnione dla opcji ban imienia. - + You must have a value in the ip ban when selecting the ip ban checkbox. Pole ip musi być wypełnione dla opcji ban ip. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Pole id klienta musi być wypełnione dla opcji ban id klienta. @@ -499,22 +634,22 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan BetaReleaseChannel - + Beta - + No reply received from the release update server. Brak odpowiedzi z serwera uaktualnień wersji. - + Invalid reply received from the release update server. Nieprawidłowa odpowiedź serwera uaktualnień wersji. - + No reply received from the file update server. Brak odpowiedzi z serwera uaktualnień plików. @@ -522,7 +657,20 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardArtPreviewWidget - + + Banner preview + + + + + + + + Zoom %1× + + + + No card selected @@ -530,22 +678,22 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardArtRulesModel - + Card - + ProviderId - + Mode - + Reason @@ -553,65 +701,209 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardDatabaseModel - + Name Nazwa - + Sets Dodatki - + Mana cost Koszt many - + Card type Typ karty - + P/T S/W - + Color(s) Kolor(y) + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + CardDatabaseView - + Add to Deck - + Add to Sideboard - + Select Printing - + + Add to Zone + + + + + Create &new zone... + + + + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards @@ -721,22 +1013,22 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardInfoFrameWidget - + Image Ilustracja - + Description Opis - + Both Karta + opis - + View transformation @@ -744,22 +1036,22 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -767,27 +1059,27 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardInfoTextWidget - + Name: Nazwa: - + Set: - + Collector Number: - + Related cards: Powiązane karty: - + Unknown card: Nieznana karta: @@ -795,129 +1087,129 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Reduce life by power - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -938,7 +1230,7 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan CardSizeWidget - + Card Size Rozmiar Karty @@ -1078,6 +1370,24 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser @@ -1107,280 +1417,382 @@ Informacja ta zachowywana jest dla moderatorów i nie jest widoczna dla zbanowan + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + ConnectionController - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - - - - - - - - - - - + + + + + + + + + + - - + + + Error - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + Server timeout - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Information - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -1388,113 +1800,113 @@ To update your client, go to Help -> Check for Updates. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - + Requesting forgotten password to %1 as %2... - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. @@ -1544,7 +1956,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1560,17 +1972,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add card to &maindeck - + Add card to &sideboard @@ -1578,97 +1990,158 @@ To update your client, go to Help -> Check for Updates. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + Format: - + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1676,17 +2149,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1694,114 +2167,119 @@ To update your client, go to Help -> Check for Updates. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + + Share deck... + + + + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1809,7 +2287,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1817,110 +2295,152 @@ To update your client, go to Help -> Check for Updates. DeckEditorSettingsPage - - + + Update Spoilers Uaktualnij Spoilery - + Success Sukces - + Download URLs have been reset. Pobrane URL zostały zresetowane. - + Add URL Dodaj URL - - + + URL: URL: - - + + Edit URL Edytuj URL - + Add New URL Dodaj Nowy URL - + Remove URL Usuń URL - + Updating... Trwa aktualizacja... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path Wybierz ścieżkę - + URL Download Priority Priorytet Pobierania URL - + Spoilers Spoilery - + Download Spoilers Automatically Pobierz spoilery automatycznie - + Spoiler Location: Lokalizacja spoilera - + Last Change Ostatnia zmiana - + Spoilers download automatically on launch Spoilery są pobierane automatycznie w czasie premiery. - + Press the button to manually update without relaunching Wciśnij przycisk żeby ręcznie uaktualnić bez restartu. - + Do not close settings until manual update is complete Nie zamykaj ustawień dopóki aktualizacja manualna się nie skończy - + Download card pictures on the fly Pobierz ilustracje kart w locie - + How to add a custom URL Jak dodać niestandardowe URL - + Reset Download URLs Resetuj Pobieranie URLi @@ -1928,32 +2448,32 @@ To update your client, go to Help -> Check for Updates. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1961,27 +2481,27 @@ To update your client, go to Help -> Check for Updates. DeckListModel - + Count Ilość - + Set Dodatek - + Number Ilość - + Provider ID Provider ID - + Card Karta @@ -2002,17 +2522,22 @@ To update your client, go to Help -> Check for Updates. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -2020,7 +2545,7 @@ To update your client, go to Help -> Check for Updates. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -2028,62 +2553,62 @@ To update your client, go to Help -> Check for Updates. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2091,94 +2616,109 @@ To update your client, go to Help -> Check for Updates. DeckPreviewWidget - + Banner Card - + Open in deck editor - + + Share deck... + + + + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Rename deck + + + + + Rename file + + + + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -2186,57 +2726,102 @@ To update your client, go to Help -> Check for Updates. DeckStateManager - + Rename deck to "%1" from "%2" - + Updated comments (was %1 chars, now %2 chars) - + Set banner card to %1 (%2) - + + Set playmat to %1 + + + + Tags changed - + Set format to %1 - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + %1 1 × "%2" (%3) - + Added - + Removed @@ -2258,94 +2843,122 @@ To update your client, go to Help -> Check for Updates. DeckViewContainer - + Load deck... Wczytaj talię... - + Load remote deck... Wczytaj zdalną talię... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start Gotowe do rozpoczęcia - + Force start Wymuś rozpoczęcie - + Sideboard unlocked Biblioteka poboczna odblokowana - + Sideboard locked Biblioteka poboczna zablokowana - - + + Error Błąd - + The selected file could not be loaded. Wybrany plik nie mógł zostać wczytany. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? - + Remember and automatically apply choice in the future @@ -2358,143 +2971,143 @@ This will kick all non-ready players from the game. Pobieranie... - + Known Hosts Znane Hosty - + Delete the currently selected saved server Usuń zaznaczony zapisany serwer - + Refresh the server list with known public servers Odśwież listę serwerów ze znanymi serwerami publicznymi - + New Host Nowy Host - + Name: Nazwa: - + &Host: &Host: - + &Port: &Port: - + Player &name: &Nazwa gracza: - + P&assword: H&asło: - + &Save password Zapi&sz hasło - + A&uto connect Połącz a&utomatycznie - + Automatically connect to the most recent login when Cockatrice opens Automatycznie połącz ostatnim loginem przy otwarciu Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! Jeżeli masz problem z połączeniem lub rejestracją, skontaktuj się z obsługą serwera! - - + + Webpage Strona internetowa - + Reset Password Zresetuj hasło - + Forgot password? Zapomniałeś hasła? - + &Connect &Połącz - + Server Serwer - + Login Login - + Server Contact Kontakt z Serwerem - + Connect to Server Połącz z Serwerem - + Server URL Serwer URL - + Communication Port Port Komunikacyjny - + Unique Server Name Unikalna Nazwa Serwera - + Connection Warning Ostrzeżenie o połączeniu - + You need to name your new connection profile. Musisz nazwać swój nowy profil połączenia - + Connect Warning Ostrzeżenie połączenia - + The player name can't be empty. Nazwa użytkownika nie może być pusta @@ -2502,122 +3115,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings Zapa&miętaj ustawienia - + &Description: &Opis: - + P&layers: &Liczba graczy: - + General Ogólne - + Game type Format - + &Password: &Hasło: - + Only &buddies can join Tylko dla zn&ajomych - + Only &registered users can join Tylko dla &zarejestrowanych użytkowników - + Joining restrictions Ograniczenia dostępu - + &Spectators can watch &Widzowie dozwoleni - + Spectators &need a password to watch Widzowie muszą z&nać hasło - + Spectators can &chat Widzowie mogą korzystać z &czatu - + Spectators can see &hands Widzowie mogą oglądać &ręce - + Create game as spectator Stwórz grę jako widz - + Spectators Widzowie - + Starting life total: Początkowa ilość życia: - + Open decklists in lobby - + Create game as judge - + Game setup options Opcje tworzenia gry - + &Clear &Wyczyść - + Create game Stwórz grę - + Game information Informacje o grze - + Error Błąd - + Server error. Błąd serwera. @@ -2625,97 +3238,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Nazwa: - + Token Token - + C&olor: &Kolor: - + white biały - + blue niebieski - + black czarny - + red czerwony - + green zielony - + multicolor wielokolorowy - + colorless bezkolorowy - + &P/T: &S/W: - + &Annotation: &Adnotacja: - + &Destroy token when it leaves the table &Zniszcz tokena kiedy opuści stół - + Create face-down (Only hides name) - + Token data Dane tokena - + Show &all tokens Pok&azuj wszystkie tokeny - + Show tokens from this &deck Pokazuj tokeny z tej &talii - + Choose token from list Wybierz token z listy: - + Create token Tworzenie tokena @@ -2723,53 +3336,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + ✖ - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2778,7 +3391,7 @@ This will kick all non-ready players from the game. DlgEditAvatar - + No image chosen. Nie wybrano obrazu. @@ -2790,27 +3403,32 @@ To remove your current avatar, confirm without choosing a new image. By usunąć twojego bieżącego awatara potwierdź bez wybierania nowego obrazka. - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... Przeglądaj... - + Change avatar Zmień awatar - + Open Image Otwórz plik obrazu - + Image Files (*.png *.jpg *.bmp) Obrazy (*.png *.jpg *.bmp) - + Invalid image chosen. Nieprawidłowy obraz. @@ -2836,38 +3454,38 @@ By usunąć twojego bieżącego awatara potwierdź bez wybierania nowego obrazka DlgEditPassword - + Old password: Bieżące hasło: - + New password: Nowe hasło: - + Confirm new password: Potwierdź nowe hasło: - + Change password Zmień hasło - - + + Error Błąd - + Your password is too short. Twoje hasło jest za krótkie. - + The new passwords don't match. Hasła nie są zgodne. @@ -2875,93 +3493,93 @@ By usunąć twojego bieżącego awatara potwierdź bez wybierania nowego obrazka DlgEditTokens - + &Name: &Nazwa: - + C&olor: K&olor: - + white biały - + blue niebieski - + black czarny - + red czerwony - + green zielony - + multicolor multikolorowy - + colorless bezkolorowy - + &P/T: &S/W: - + &Annotation: &Adnotacja: - + Token data Dane tokena - - + + Add token Dodaj token - + Remove token Usuń token - + Edit custom tokens Edytuj niestandardowe żetony - + Please enter the name of the token: Wprowadź nazwę tokena: - + Error Błąd - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Wybrana nazwa koliduje z istniejącą kartą lub tokenem. @@ -3040,7 +3658,8 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami - Hide games that have started + Hide started games + Hide games that have started @@ -3076,8 +3695,9 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami - &Creator name: - Założy&ciel gry + &Host name: + &Creator name: + @@ -3143,52 +3763,52 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami DlgForgotPasswordChallenge - + Reset Password Challenge Warning Ostrzeżenie w procesie przypominania hasła - + A problem has occurred. Please try to request a new password again. Wystąpił problem. Poproś o nowe hasło ponownie. - + Enter the information of the server and the account you'd like to request a new password for. Wpisz informacje serwera oraz konta, do którego chciał(a)byś stworzyć nowe hasło. - + &Host: &Host: - + &Port: &Port: - + Player &name: &Nazwa gracza: - + Email: Email: - + Reset Password Challenge Proces przypominania hasła - + Reset Password Challenge Error Błąd w procesie przypominania hasła - + The email address can't be empty. Adres email nie może być pusty. @@ -3196,37 +3816,37 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. Wpisz informacje serwera na którym chciał(a)byś stworzyć nowe hasło. - + &Host: &Host: - + &Port: &Port: - + Player &name: &Nazwa gracza: - + Reset Password Request Żądanie Resetu Hasła - + Reset Password Error Błąd Resetu Hasła - + The player name can't be empty. Nazwa gracza nie może być pusta. @@ -3234,94 +3854,127 @@ Upewnij się że aktywowano 'Token' w oknie "Zarządzaj dodatkami DlgForgotPasswordReset - + Reset Password Warning Ostrzeżenie Resetu Hasła - + A problem has occurred. Please try to request a new password again. Wystąpił problem. Poproś o nowe hasło ponownie. - + Enter the received token and the new password in order to set your new password. Wpisz otrzymany token oraz nowe hasło aby je ustawić. - + &Host: &Host: - + &Port: &Port: - + Player &name: &Nazwa gracza: - + Token: Token: - - + + New Password: Nowe Hasło: - + Reset Password Zresetuj hasło - + The player name can't be empty. Nazwa gracza nie może być pusta. - - - - + + + + Reset Password Error Błąd Resetu Hasła - + The token can't be empty. Token nie może być pusty. - + The new password can't be empty. Nowe hasło nie może być puste. - + Error Błąd - + Your password is too short. Twoje hasło jest za krótkie. - + The passwords do not match. Hasła nie są zgodne. + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck @@ -3405,166 +4058,567 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLocalGameOptions - + Players: - + General - + Starting life total: - + Game setup options - + Remember settings - + Local game options + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): Nazwa karty (lub wyrażenie wyszukiwania): - + Number of hits: Ilość trafień: - + Auto play hits Automatycznie graj trafienia - + Put top cards on stack until... Kładź karty z wierzchu na stosie dopóki... - + No cards matching the search expression exists in the card database. Proceed anyways? W bazie kart nie znaleziono kart które spełniają twoje wyrażenie. Kontynuować mimo to? - + Cockatrice Cockatrice - + Invalid filter Nieprawidłowy filtr + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. Wpisz swoje informacje oraz informacje serwera do którego chciałbyś się zarejestrować. Twój email zostanie użyty aby zweryfikować twoje konto. - + &Host: &Host: - + &Port: &Port: - + Player &name: &Nazwa gracza: - + P&assword: H&asło: - + Password (again): Hasło (ponownie): - + Email: Email: - + Email (again): Email (ponownie): - + Country: Kraj: - + Undefined Nie wiadomo - + Real name: Prawdziwe imię: - + Register to server Zarejestruj się - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning Ostrzeżenie rejestracji - + Your password is too short. Twoje hasło jest za krótkie. - + Your passwords do not match, please try again. Wpisane hasła nie są zgodne. Spróbuj ponownie. - + Your email addresses do not match, please try again. Wpisane adresy mailowe nie są zgodne. Spróbuj ponownie. - + The player name can't be empty. Nazwa użytkownika nie może być pusta + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice @@ -3630,46 +4684,12 @@ Twój email zostanie użyty aby zweryfikować twoje konto. DlgSettings - + Unknown Error loading card database W trakcie wczytywania bazy kart wystąpił nieznany błąd - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - Nieprawidłowa baza kart. - -Cockatrice nie może działać bez prawidłowej bazy kart. - -Może istnieć potrzeba uruchomienia Oracle w celu uaktualnienia bazy kart. - -Czy chcesz zmienić ustawienia położenia bazy kart? - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - Twoja baza kart jest przestarzała. - -To może spowodować błędy we wczytywaniu kart i ich ilustracji. - -Zwykle można temu zaradzić poprzez uruchomienie narzędzia Oracle i uaktualnienie bazy kart. - -Czy chcesz zmienić ustawienia położenia bazy kart? - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3682,7 +4702,7 @@ Proszę wypełnić zgłoszenie błędu pod adresem https://github.com/Cockatrice Czy chcesz zmienić ustawienia położenia bazy kart? - + File Error loading your card database. Would you like to change your database location setting? @@ -3691,7 +4711,7 @@ Would you like to change your database location setting? Czy chcesz ustawić nową lokalizację bazy kart? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3700,7 +4720,7 @@ Would you like to change your database location setting? Czy chcesz ustawić nową lokalizację bazy kart? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3713,104 +4733,235 @@ Proszę wypełnić zgłoszenie błędu pod adresem https://github.com/Cockatrice Czy chcesz zmienić ustawienia położenia bazy kart? - - - + + + Error Błąd - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Ścieżka dostępu do twojego katalogu z taliami jest nieprawidłowa. Czy chcesz wrócić i ustawić poprawną ścieżkę? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Ścieżka dostępu do twojego katalogu z obrazkami jest nieprawidłowa. Czy chcesz wrócić i ustawić poprawną ścieżkę? - + Settings Ustawienia - + + Search settings... + + + + + OK + + + + General Ogólne - + Appearance Wygląd - + User Interface Interfejs - + Storage - + Card Sources Źródło Karty - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat Czat - + Sound Dźwięk - + Shortcuts Skróty + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3818,17 +4969,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next Następna - + Previous Poprzednia - + Tip of the Day Wskazówka na Dziś @@ -3836,165 +4987,175 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel Kanał obecnej wersji - + Reinstall Przeinstaluj - + Cancel Download Anuluj Pobieranie - + Open Download Page Otwórz stronę pobierań - + Check for Client Updates Sprawdź aktualizacje klienta - - - - + + + + Error Błąd - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice nie wspiera SSL, dlatego automatyczne pobieranie uaktualnień nie jest możliwe! Odwiedź stronę pobierania żeby uaktualnić ręcznie. - + Downloading update: %1 - + Checking for updates... Sprawdzam aktualizacje... - + Finished checking for updates Skończyłem sprawdzać aktualizacje - + No Update Available Brak nowych aktualizacji - + Cockatrice is up to date! Cockatrice jest aktualny! - + You are already running the latest version available in the chosen release channel. Używasz obecnie najnowszej dostępnęj wersji w wybranym kanale wydania - + Current version Obecna wersja - + Selected release channel Wybierz kanał wydania - - + + Update Available Dostępna aktualizacja - - + + A new version of Cockatrice is available! Nowa wersja Cockatrice jest dostępna! - - + + New version Nowa wersja - - + + Released Wydania - - + + Changelog Zmiany - + Do you want to update now? Chcesz teraz zaktualizować? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error Błąd aktualizacji - + An error occurred while checking for updates: Wystąpił błąd podczas sprawdzania aktualizacji: - + An error occurred while downloading an update: Wystąpił błąd podczas pobierania aktualizacji: - + Installing... Instaluję... - + Cockatrice is unable to open the installer. Cockatrice nie może otworzyć instalatora. - + Try to update manually by closing Cockatrice and running the installer. Spróbuj zaktualizować ręcznie poprzez zamknięcie Cockatrice i włączenie instalatora. - + Download location Lokalizacja pobierania @@ -4002,17 +5163,17 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing Wyczyść logi przy zamykaniu - + Copy to clipboard - + Debug Log Log Debugera @@ -4250,7 +5411,7 @@ You may have to manually download the new version. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -4258,7 +5419,7 @@ You may have to manually download the new version. FilterBuilder - + Type your filter here Podaj swój filtr @@ -4266,45 +5427,88 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -4312,140 +5516,156 @@ You may have to manually download the new version. GameSelector - - - - - - - - - + + + + + + + + + Error Błąd - + Please join the appropriate room first. Proszę najpierw dołączyć do właściwego pokoju. - + Wrong password. Nieprawidłowe hasło. - + Spectators are not allowed in this game. Ta rozgrywka nie dopuszcza widzów. - + The game is already full. Ta rozgrywka jest już pełna. - + The game does not exist any more. Ta rozgrywka już nie istnieje. - + This game is only open to registered users. Ta rozgrywka jest dostępna tylko dla zarejestrowanych użytkowników. - + This game is only open to its creator's buddies. Ta rozgrywka jest dostępna tylko dla znajomych jej twórcy. - + You are being ignored by the creator of this game. Twórca tej rozgrywki cię ignoruje. - + Join Game - + Spectate Game - + Game Information - + + Cop&y game link + + + + Join Game as Judge - + Spectate Game as Judge - + + Join game Dołącz do gry - - Password: - Hasło: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. Proszę najpierw dołączyć do odpowiedniego pokoju. - + &Filter games &Filtr rozgrywek - + C&lear filter Usuń fi&ltr - + C&reate Utwó&rz - + &Join &Dołącz - + Join as judge - + J&oin as spectator D&ołącz jako widz - + Join as judge spectator - + Games shown: %1 / %2 Gra pokazana: %1 / %2 - + Games Gry @@ -4486,105 +5706,105 @@ You may have to manually download the new version. GamesModel - + >1 day >1 dzień - + %1%2 hr short age in hours %1%2 godz%1%2 godz%1%2 godz%1%2 godz - + new Nowa - + %1%2 min short age in minutes %1%2 min%1%2 min%1%2 min%1%2 min - + password hasło - + buddies only tylko znajomi - + reg. users only tylko zarejestrowani użytkownicy - + open decklists - - + + can chat mogą rozmawiać - + see hands widzą ręce - + can see hands widzą ręce - + not allowed niedozwoleni - + Room Pokój - + Age Wiek - + Description Opis - - Creator - Twórca + + Host + - + Type Typ - + Restrictions Ograniczenia - + Players Gracze - + Spectators Widzowie @@ -4592,158 +5812,275 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths Resetuj wszystkie ścieżki - + All paths have been reset Wszystkie ścieżki zostały zresetowane - - - - - - - + + + + English + Polski (Polish) + + + + + + + + + Choose path Wybierz ścieżkę - + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> + + + + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + Language settings - + Language: Język: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + Version settings - + Card database - + Startup settings - + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) Scieżki (edytowanie wyłączone w trybie przenośnym) - + Paths Ścieżki - + How to help with translations - + Decks directory: Katalog z taliami: - + Filters directory: - + Replays directory: Katalog z powtórkami: - + Pictures directory: Katalog z obrazkami: - + Card database: Baza kart: - + Custom database directory: Niestandardowy folder z bazą: - + Token database: Baza tokenów: - + Update channel Aktualizuj kanał - + Check for client updates on startup Sprawdzaj aktualizacje klienta przy uruchomieniu - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client Powiadom jeśli właściwości wspierane przez serwer, brakują w kliencie - + Automatically run Oracle when running a new version of Cockatrice Automatycznie uruchom Oracle kiedy uruchomiona jest nowa wersja Cockatrice - + Show tips on startup Pokazuj wskazówki po uruchomieniu - + Last update check on %1 (%2 days ago) @@ -4751,47 +6088,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4885,59 +6222,353 @@ You may have to manually download the new version. + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu @@ -5155,273 +6786,294 @@ You may have to manually download the new version. MainWindow - - + + Player %1 Gracz %1 - + Load replay Wczytaj powtórkę - + About Cockatrice O Cockatrice - + Version Wersja - + Cockatrice Webpage Oficjalna strona Cocatrice - + Project Manager: Menadżer projektu: - + Past Project Managers: Poprzedni menadżerowie projektu: - + Developers: Twórcy: - + Our Developers Lista współpracowników - + Help Develop! Dołącz do grona twórców! - + Translators: Tłumacze: - + Our Translators Nasi Tłumacze: - + Help Translate! Pomóż w tłumaczeniu! - + Support: Wsparcie: - + Report an Issue Zgłoś problem - + Troubleshooting Rozwiązywanie problemów - + F.A.Q. Najczęściej zadawane pytania - - + + Error Błąd - + &Connect... &Połącz… - + &Disconnect &Rozłącz - + Start &local game... Rozpocznij &lokalną grę… - + &Watch replay... &Obejrzyj powtórkę… - + &Full screen Pełny ekra&n - + &Register to server... &Zarejestruj się... - + &Restore password... &Przywróć hasło... - + &Settings... &Ustawienia… - + &Exit &Wyjdź - + A&ctions Ak&cje - + &Cockatrice &Cockatrice - + C&ard Database B&aza kart - + &Manage sets... &Zarządzaj dodatkami... - + Edit custom &tokens... Edytuj niestandardowe %tokens... - + Open custom image folder Otwórz folder ze spersonalizowanymi obrazkami - + Open custom sets folder Otwórz folder ze spersonalizowanymi setami - + Add custom sets/cards Dodaj spersonalizowane sety/karty - + Reload card database Przeładuj bazę kart - + Tabs - + &Help Po&moc - + &About Cockatrice &O Cockatrice - + &Tip of the Day &Wskazówka na Dziś - + Check for Client Updates Sprawdź uaktualnienia klienta - + Check for Card Updates... Uaktualnij bazę kart... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log Pokaż log &debugowania - + Open Settings Folder - + + Re-run Onboarding Wizard... + + + + Show/Hide Pokaż/Ukryj - + + Sign-in cancelled + + + + New Version Nowa Wersja - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Gratulacje z powodu aktualizacji do Cockatrice %1! Oracle uruchomi się teraz aby zaktualizować bazę danych kart. - + Cockatrice installed Cockatrice zainstalowany - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Gratulacje z powodu instalacji Cockatrice %1! Oracle uruchomi się teraz aby zainstalować początkowe bazy danych kart. - + + Are you sure? + Czy na pewno? + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database Baza kart - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5430,29 +7082,29 @@ Czy chcesz teraz uaktualnić bazę kart? Jeżeli nie masz pewności lub jesteś nowym użytkownikiem, wybierz "Tak" - - + + Yes Tak - - + + No Nie - + Open settings Otwórz ustawienia - + New sets found Znaleziono nowe dodatki - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5467,22 +7119,22 @@ Kody dodatków: %1 Czy chcesz je teraz uaktywnić? - + Yes, always enable - + View sets Pokaż dodatki - + Welcome Witaj - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5491,89 +7143,89 @@ Wszystkie dodatki w bazie kart zostały aktywowane. Przeczytaj więcej na temat zmiany kolejności czy wyłączania poszczególnych dodatków i tym podobnych w oknie "Zarządzaj Dodatkami". - + Information Informacja - + A card database update is already running. Aktualizacja bazy kart jest w trakcie - + Unable to run the card database updater: Nie można uruchomić aktualizacji bazy kart: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - - - - - + + + + + Load sets/cards Wczytaj dodatki/karty - + Selected file cannot be found. Wybrany plik nie został znaleziony. - + You can only import XML databases at this time. Możesz zaimportować jedynie bazy danych XML w tym momencie. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Nowe dodatki/karty dodane pomyślnie. Cockatrice ponownie załaduje teraz bazę danych. - + Sets/cards failed to import. Dodatki/karty nie mogły być zaimportowane. @@ -5778,615 +7430,615 @@ Cockatrice ponownie załaduje teraz bazę danych. MessageLogWidget - + from play z gry - + from their graveyard z jego grobu - + from exile ze strefy wygnania - + from their hand z jego ręki - + the top card of %1's library karta z wierzchu biblioteki gracza %1 - + the top card of their library pierwsza karta jego biblioteki - + from the top of %1's library z wierzchu biblioteki gracza %1 - + from the top of their library z wierzchu jego biblioteki - + the bottom card of %1's library karta z wierzchu biblioteki gracza %1 - + the bottom card of their library ostatnia karta jego biblioteki - + from the bottom of %1's library z dna biblioteki gracza %1 - + from the bottom of their library z dna jego biblioteki - + from %1's library z biblioteki gracza %1 - + from their library z jego biblioteki - + from sideboard z talii pobocznej - + from the stack ze stosu - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 od teraz gra z odwróconą kartą z wierzchu %2. - + %1 is not revealing the top card %2 any longer. %1 już nie gra z odwróconą kartą z wierzchu %2. - + %1 can now look at top card %2 at any time. %1 może teraz patrzeć na kartę z wierzchu %2 w każdej chwili. - + %1 no longer can look at top card %2 at any time. %1 nie może już patrzeć na kartę z wierzchu %2 w każdej chwili. - + %1 attaches %2 to %3's %4. %1 przyłącza %2 do %4 %3 - + %1 has conceded the game. %1 poddał grę. - + %1 has unconceded the game. %1 nie poddał gry. - + %1 has restored connection to the game. %1 odzyskał połączenie z grą. - + %1 has lost connection to the game. %1 utracił połączenie z grą. - + %1 points from their %2 to themselves. %1 wskazuje z jego karty %2 na samego siebie. - + %1 points from their %2 to %3. %1 wskazuje z jego karty %2 na kartę %3. - + %1 points from %2's %3 to themselves. %1 wskazuje z karty gracza %2 %3 na samą siebie. - + %1 points from %2's %3 to %4. %1 wskazuje z karty gracza %2 %3 na kartę %4. - + %1 points from their %2 to their %3. %1 wskazuje z jego karty %2 na jego kartę %3. - + %1 points from their %2 to %3's %4. %1 wskazuje z jego karty %2 na karty %3's %4. - + %1 points from %2's %3 to their own %4. %1 wskazuje z kart %2's %3 na swoją kartę %4. - + %1 points from %2's %3 to %4's %5. %1 wskazuje z karty gracza %2 %3 na kartę gracza %4 (%5). - + %1 creates a face down token. - + %1 creates token: %2%3. %1 stworzył token: %2%3. - + %1 has loaded a deck (%2). Gracz %1 wczytał talię (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). Gracz %1 wczytał talię oraz %2 kart talii pobocznej (hash: %3). - + %1 destroys %2. %1 niszczy %2. - + a card kartę - + %1 gives %2 control over %3. %1 oddaje %3 pod kontrolę gracza %2. - + %1 puts %2 into play%3 face down. %1 zagrywa kartę %2 na pole bitwy%3 koszulką do góry. - + %1 puts %2 into play%3. %1 zagrywa kartę %2 na pole bitwy %3. - + %1 puts %2%3 into their graveyard face down. - + %1 puts %2%3 into their graveyard. %1 kładzie %2 %3 na cmentarz. - + %1 exiles %2%3 face down. - + %1 exiles %2%3. %1 przenosi kartę %2 do strefy wygnania %3. - + %1 moves %2%3 to their hand. %1 bierze %2 %3 do ręki. - + %1 puts %2%3 into their library. %1 wkłada %2 %3 do biblioteki. - + %1 puts %2%3 onto the bottom of their library. %1 przenosi %2 %3 na dno swojej biblioteki. - + %1 puts %2%3 on top of their library. %1 przenosi %2 %3 na wierzch swojej bibliotek - + %1 puts %2%3 into their library %4 cards from the top. %1 kładzie %2 %3 na jego biblioteke %4 kart z góry. - + %1 moves %2%3 to sideboard. %1 przenosi kartę %2 %3 do talii pobocznej. - + %1 plays %2%3 face down. - + %1 plays %2%3. %1 zagrywa kartę %2 %3. - + %1 moves %2%3 to custom zone '%4' face down. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1 próbuje dobrać z pustej biblioteki - + %1 draws %2 card(s). %1 dobiera %2 kartę.%1 dobiera %2 kart.%1 dobiera %2 kart.%1 dobiera %2 kart. - + %1 is looking at %2. %1 przegląda %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. %1 kładzie %2 grzbietem do góry. - + %1 turns %2 face-up. %1 kładzie %2 grzbietem do dołu. - + The game has been closed. Gra została zakończona. - + The game has started. Gra została rozpoczęta. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 dołączył do gry. - + %1 is now watching the game. %1 obserwuje grę. - + You have been kicked out of the game. Wyrzucono cię z gry. - + %1 has left the game (%2). %1 opuścił grę (%2). - + %1 is not watching the game any more (%2). %1 przestał oglądać grę (%2). - + %1 is not ready to start the game any more. %1 nie jest już gotowy do rozpoczęcia gry. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 tasuje swoją talie i dobiera nową rękę z %2 kartą.%1 tasuje swoją talie i dobiera nową rękę z %2 kartami.%1 tasuje swoją talie i dobiera nową rękę z %2 kartami.%1 tasuje swoją talie i dobiera nową rękę z %2 kartami. - + %1 shuffles their deck and draws a new hand. %1 tasuje swoją talie i dobiera nową rękę. - + You are watching a replay of game #%1. Oglądasz powtórkę gry #%1. - + %1 is ready to start the game. %1 jest gotowy do rozpoczęcia gry. - + cards an unknown amount of cards karty - + %1 card(s) a card for singular, %1 cards for plural jedną kartę%1 kart%1 kart%1 kart - + %1 lends %2 to %3. %1 pożycza %2 graczowi %3. - + %1 reveals %2 to %3. %1 pokazuję kartę %2 graczowi %3. - + %1 reveals %2. %1 pokazuję kartę %2. - + %1 randomly reveals %2%3 to %4. %1 pokazuje losowo kartę %2 %3 graczowi %4. - + %1 randomly reveals %2%3. %1 pokazuje losowo kartę %2 %3. - + %1 peeks at face down card #%2. %1 spogląda na kartę odwróconą koszulką do góry #%2. - + %1 peeks at face down card #%2: %3. %1 spogląda na kartę odwróconą koszulką do góry #%2: %3. - + %1 reveals %2%3 to %4. %1 pokazuje kartę %2 %3 graczowi %4. - + %1 reveals %2%3. %1 pokazuje kartę %2 %3. - + %1 reversed turn order, now it's %2. %1 odwrócona kolejność tur, teraz %2 - + reversed odwrócone - + normal normalne - + Heads Orzeł - + Tails Reszka - + %1 flipped a coin. It landed as %2. %1 podrzucił monetą. Wylądowała na %2. - + %1 rolls a %2 with a %3-sided die. %1 rzuca %2 kostką %3-ścienną. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 rzuca %2 monetami. Wypadło %3 orłów i %4 reszek. - + %1 rolls a %2-sided dice %3 times: %4. %1 rzuca %2-ścienną kostką %3 razy: %4. - + %1's turn. Tura gracza %1 - + %1 sets annotation of %2 to %3. %1 ustala adnotację karty %2 na %3. - + %1 places %2 %3%4 counter(s) on %5 (now %6). - + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + %1 failed to undo their last draw. - + %1 sets counter %2 to %3 (%4%5). %1 ustawia licznik %2 na %3 (%4%5). - + %1 sets %2 to not untap normally. %1 blokuje karcie %2 normalne odtapowywanie. - + %1 sets %2 to untap normally. %1 umożliwia karcie %2 normalne odtapowywanie. - + %1 removes the PT of %2. %1 usuwa PT %2. - + %1 changes the PT of %2 from nothing to %4. %1 zmienia PT %2 z niczego na %4 - + %1 changes the PT of %2 from %3 to %4. %1 zmienia PT %2 z %3 na %4. - + %1 has locked their sideboard. %1 zablokował jego bibliotekę poboczną - + %1 has unlocked their sideboard. %1 odblokował jego bibliotekę poboczną - + %1 taps their permanents. %1 tapuje swoje permanenty. - + %1 untaps their permanents. %1 odtapowuje swoje permanenty. - + %1 taps %2. %1 tapuje kartę %2. - + %1 untaps %2. %1 odtapowuje kartę %2. - + %1 shuffles %2. %1 tasuje %2. - + %1 shuffles the bottom %3 cards of %2. %1 tasuje %3 dolne karty z %2. - + %1 shuffles the top %3 cards of %2. %1 tasuje %3 górne karty z %2. - + %1 shuffles cards %3 - %4 of %2. %1 tasuje %3 - %4 z %2 kart. - + %1 unattaches %2. %1 odczepia kartę %2. - + %1 undoes their last draw. %1 cofa ostatnią dobraną kartę - + %1 undoes their last draw (%2). %1 cofa ostatnią dobraną kartę (%2) @@ -6394,115 +8046,125 @@ Cockatrice ponownie załaduje teraz bazę danych. MessagesSettingsPage - + Word1 Word2 Word3 Wyraz1 Wyraz2 Wyraz3 - + Add New Message Dodaj Nową Wiadomość - + Edit Message Edytuj Wiadomość - + Remove Message Usuń Wiadomość - + Add message Dodaj wiadomość - - + + Message: Wiadomość: - + Edit message Edytuj wiadomość - + Chat settings Ustawienia czatu - + Custom alert words Niestandardowy tekst alertu. - + Enable chat mentions Włącz wywołania na czacie - + Enable mention completer Włącz autouzupełnianie wywołania - + In-game message macros Makra wiadomości w trakcie gry - + How to use in-game message macros Jak korzystać z makr wiadomości w trakcie gry - + Ignore chat room messages sent by unregistered users Ignoruj wiadomości na czacie od niezarejestrowanych użytkowników - + Ignore private messages sent by unregistered users Ignoruj prywatne wiadomości od niezarejestrowanych użytkowników - + Ignore private messages sent by non-buddy users - - + + Invert text color Odwróć kolor tekstu - + Enable desktop notifications for private messages Włącz desktopowe notyfikacje dla prywatnych wiadomości - + Enable desktop notification for mentions Włącz desktopowe notyfikacje dla wywołań - + Enable room message history on join Włącz historię wiadomości w pokoju po dołączeniu - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) (Kolor w kodzie heksadecymalnym) - + Separate words with a space, alphanumeric characters only Rozdziel słowa spacją, tylko alfanumeryczne znaki @@ -6510,42 +8172,42 @@ Cockatrice ponownie załaduje teraz bazę danych. MoveMenu - + Move to - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + T&able - + &Hand - + &Graveyard - + &Exile @@ -6624,148 +8286,134 @@ Cockatrice ponownie załaduje teraz bazę danych. PaletteEditorDialog - - + + Reset - - - Apply - - - - - - Save && Apply - - - - + ▼ Edit Palette - - + + ▶ Edit Palette - + Palette Editor — %1 - + <b>Palette Editor</b> &nbsp;·&nbsp; %1 - - This theme ships no default palette files - - - - - Replace current colours with the theme author's defaults - - - - - Switch between the light and dark palette files + + + Save - Editing: + Delete this scheme's custom palette and revert to the theme default (or the application palette) - Show or hide the per-role colour grid for manual tweaks + Switch between the light and dark palette files + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + ↺ Revert to theme default - + Discard unsaved edits and restore the last saved palette - - Preview this palette without saving to disk + + Cannot save: this theme has no writable directory - + Write palette-%1.toml and reload the theme - - Cannot save: this theme has no directory on disk - - - - + + Save failed - + + Could not write %1 to: %2 - - - No default found - - - - - No default palette file found for the "%1" scheme. - - PaletteGridWidget - + Active - + Disabled - + Inactive - + Normal interactive state - + Widget is disabled / not interactive - + Window is in background / unfocused + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + Phase @@ -6888,30 +8536,21 @@ Cockatrice ponownie załaduje teraz bazę danych. Krok końca tury - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - PlayerActions - - - - + + + + grave - - - - + + + + exile @@ -7030,16 +8669,272 @@ Cockatrice ponownie załaduje teraz bazę danych. PlayerMenu - + Player "%1" - + &Counters + + PlaymatCollectionDialog + + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + PrintingDisabledInfoWidget @@ -7060,12 +8955,12 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelector - + Display Navigation Buttons - + Printing Selector @@ -7073,25 +8968,65 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing Przypnij wersję - + Unpin Printing Odepnij wersję - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards Pokaż powiązane karty + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -7122,28 +9057,28 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardSortingWidget - + Alphabetical Alfabetycznie - + Preference - + Release Date Data wydania - - + + Descending Malejąco - + Ascending Rosnąco @@ -7159,95 +9094,103 @@ This setting means you'll only see the default printing for each card, instead o PtMenu - + Power / toughness - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services Usługi - + Hide %1 Ukryj %1 - + Hide Others Ukryj pozostałe - + Show All Pokaż wszystkie - + Preferences... Preferencje… - + Quit %1 Zakończ %1 - + About %1 O programie %1 @@ -7255,17 +9198,17 @@ This setting means you'll only see the default printing for each card, instead o QObject - + Cockatrice card database (*.xml) Baza kart Cockatrice (*.xml) - + All files (*.*) Wszystkie pliki (*.*) - + Cockatrice replays (*.cor) Powtórki Cockatrice (*.cor) @@ -7285,17 +9228,22 @@ This setting means you'll only see the default printing for each card, instead o Tokeny - + + Maybeboard + + + + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7306,7 +9254,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7317,100 +9265,110 @@ Are you sure you would like to disable this feature? - + Confirm Change + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK OK - + Save Zapisz - + Save All Zapisz Wszystko - + Open Otwórz - + &Yes Tak - + Yes to &All Tak - wszystko - + &No Nie - + N&o to All Nie - wszystko - + Abort Przerwij - + Retry Powtórz - + Ignore Ignoruj - + Close Zamknij - + Cancel Anuluj - + Discard Odrzuć - + Help Pomoc - + Apply Aplikuj - + Reset Reset - + Restore Defaults Przywróć domyślne @@ -7418,121 +9376,189 @@ Are you sure you would like to disable this feature? QuickSetupPanel - + %1% - + <b>Quick Setup</b> - + Generate all palette roles automatically from a single accent colour - + Accent: - + Primary hue. Used directly for highlights and links. At high intensity it also tints buttons and backgrounds. - + Intensity: - + Subtle - + Full colour - + 0–30 Subtle tint — only highlights and links change hue 30–70 Accented — buttons, tooltips, and borders join in 70–100 Full colour — backgrounds, everything - + 70% - - - Generate ↓ - - - - - Derive all palette roles from the accent colour above. -Fine-tune individual colours in the grid afterwards. - - RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name Nazwa - + ID ID - + Upload time Data dodania + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID ID - + Name Nazwa - + Players Gracze - + Keep Zachowaj - + Time started Czas rozpoczęcia - + Duration (sec) Czas trwania (sek) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu @@ -7574,37 +9600,37 @@ Fine-tune individual colours in the grid afterwards. RoomSelector - + Rooms Pokoje - + Joi&n &Dołącz - + Room Pokój - + Description Opis - + Permissions Zezwolenia - + Players Gracze - + Games Gry @@ -7612,7 +9638,7 @@ Fine-tune individual colours in the grid afterwards. SayMenu - + S&ay @@ -7620,32 +9646,32 @@ Fine-tune individual colours in the grid afterwards. SequenceEdit - + Choose an action from the table Wybierz akcję z tabeli - + Hit the key/combination of keys you want to set for this action Naciśnij klawisz lub kombinację klawiszy którą chcesz ustawić na tą akcję - + Invalid key Nieprawidłowy klawisz - + Shortcut already in use by: Skrót aktualnie użyty przez: - + Clear Wyczyść - + Restore default Przywróć domyślne @@ -7678,56 +9704,92 @@ Fine-tune individual colours in the grid afterwards. Data wydania + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts Przywróć domyślne skróty - + Do you really want to restore all default shortcuts? Czy na pewno chcesz przywrócić wszystkie domyślne skróty? - + Clear all default shortcuts Wyczyść wszystkie domyślne skróty - + Do you really want to clear all shortcuts? Czy na pewno chcesz wyczyścić wszystkie skróty? - + Section: Sekcja: - + Action: Akcja: - + Shortcut: Skrót: - + + Shortcut editor + + + + How to set custom shortcuts Jak ustawić niestandardowe skróty klawiszowe - + Clear all shortcuts Wyczyść wszystkie skróty - + Search by shortcut name Szukaj po nazwie skrótu @@ -7783,12 +9845,12 @@ Sprawdź ustawienia skrótów! SideboardMenu - + &Sideboard - + &View sideboard @@ -7796,27 +9858,27 @@ Sprawdź ustawienia skrótów! SoundSettingsPage - + Enable &sounds Włącz &efekty dźwiękowe - + Current sounds theme: Aktualny zestaw dźwięków: - + Test system sound engine Testuj silnik dźwiękowy systemu - + Sound settings Ustawienia dźwięku - + Master volume Główne ustawienia głośności @@ -7824,48 +9886,48 @@ Sprawdź ustawienia skrótów! SpoilerBackgroundUpdater - + Spoilers season has ended Sezon spoilerów zakończony - + Deleting spoiler.xml. Please run Oracle Usuwanie spoiler.xml. Uruchom Oracle - - + + Spoilers download failed Pobieranie spoilerów nieudane - + No internet connection Brak połączenia z internetem - + Error Błąd - + Spoilers already up to date Spoilery są już aktualne - + No new spoilers added Nie dodano nowych spoilerów - + Spoilers have been updated! Spoilery zostały zaktualizowane. - + Last change: Ostatnia zmiana: @@ -7873,157 +9935,170 @@ Sprawdź ustawienia skrótów! StableReleaseChannel - + Default - + No reply received from the release update server. Brak odpowiedzi z serwera uaktualnień wersji. - + Invalid reply received from the release update server. Nieprawidłowa odpowiedź serwera uaktualnień wersji. - + No reply received from the tag update server. Brak odpowiedzi z serwera uaktualnień tagów. - + Invalid reply received from the tag update server. Nieprawidłowa odpowiedź serwera uaktualnień tagów. + + StatsTally + + + Total Power + + + + + Total Toughness + + + StorageSettingsPage - - - + + + Success - + Cached card pictures have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + In-memory (currently loaded) card pictures have been reset. - + Card Picture Loader Caching Method: - + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. - + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. - + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. - + Delete Cached Images - + Delete Saved Images - + Clear In-Memory Images - + Card Picture Loader Cache Method - + Network Cache - + Filesystem - + In-Memory Picture Cache - + Network Cache Size: - + On-disk cache for downloaded pictures - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + In-memory cache for pictures not currently on screen - + Naming scheme: - + Day(s) @@ -8031,17 +10106,33 @@ Sprawdź ustawienia skrótów! TabAccount - + + + My Reports + + + + Add to Buddy List - + Add to Ignore List - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account @@ -8162,118 +10253,118 @@ Sprawdź ustawienia skrótów! TabArchidekt - - - + + + Desc. - - + + AND - - + + Require ALL selected colors - - + + Deck name... - - + + Owner... - - + + Packages - - + + Advanced Filters - + Bracket: - - + + Any - - + + Contains card... - - + + Commander... - - + + Tag... - - + + Deck Size - + Cards: - - + + Asc. - + Sort by: - + Filter by: - + Display Settings - - + + Search - - + + Formats @@ -8286,42 +10377,42 @@ Sprawdź ustawienia skrótów! TabCardArtRules - + Card: - + ProviderId: - + Mode: - + Reason: - + Type a card name... - + Add rule - + Remove rule - + Refresh @@ -8334,52 +10425,52 @@ Sprawdź ustawienia skrótów! TabDeckEditor - + Card Info Informacje o karcie - + Deck Talia - + Filters Filtry - + &View &Widok - + Card Database - + Printing Wersja - + Visible Widoczne - + Floating Pływające - + Reset layout Resetuj układ - + Deck: %1 Talia: %1 @@ -8387,49 +10478,49 @@ Sprawdź ustawienia skrótów! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - + &View - + Printing - + Visible - + Floating - + Reset layout @@ -8437,22 +10528,22 @@ Sprawdź ustawienia skrótów! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -8460,139 +10551,237 @@ Sprawdź ustawienia skrótów! TabDeckStorage - + Local file system Pliki lokalne - + Server deck storage Przechowalnia talii na serwerze - - + + Open in deck editor Otwórz w edytorze talii - + Rename deck or folder - + Upload deck Wyślij talię - + Download deck Pobierz talię - - - - + + + + New folder Nowy folder - - + + Delete Usuń - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder Otwórz folder talii - + Rename local folder - + Rename local file - + New name: - - - - + + + + + + + Error Błąd - + Rename failed - - + + + Invalid deck file Nieprawidłowy plik talii - + Enter deck name Podaj nazwę talii - + This decklist does not have a name. Please enter a name: Talia nie posiada nazwy. Wprowadź nazwę: - + Unnamed deck Nienazwana talia - + Failed to upload deck to server Nie udało się wysłać talii na serwer - + Delete local file Usuń plik lokalny - + Are you sure you want to delete the selected files? Czy na pewno chcesz usunąć wybrane pliki? - + Delete remote decks Usuń zdalną talię - + Are you sure you want to delete the selected decks? Czy na pewno chcesz usunąć wybrane talie? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: Nazwa nowego folderu: - + Deck Storage @@ -8600,20 +10789,271 @@ Wprowadź nazwę: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -8626,27 +11066,27 @@ Wprowadź nazwę: TabEdhRecMain - + &Cards - + Top Commanders - + Tags - + Search for a card ... - + Search @@ -8659,191 +11099,207 @@ Wprowadź nazwę: TabGame - - - + + + Replay Powtórka - - + + Game Gra - - + + Player List Lista Graczy - - + + + Invite + + + + + Card Info Informacje o karcie - - + + Messages Wiadomości - - + + Replay Timeline Oś czasu powtórki - + &Phases &Fazy - + &Game &Gra - + Next &phase Następna &faza - + Next phase with &action Następna faza z akcją - + Next &turn Następna &tura - + Reverse turn order Odwróć kolejność tur - + &Remove all local arrows Usuń wszystkie &wskaźniki - + Rotate View Cl&ockwise Obróć widok &zgodnie z ruchem wskazówek zegara - + Rotate View Co&unterclockwise Obróć widok &przeciwnie do ruchu wskazówek zegara - + Game &information &Informacje o grze - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede Powróć do gry - - - + + + &Concede &Poddaj grę - + &Leave game &Opuść grę - + C&lose replay &Zamknij powtórkę - + &Focus Chat Skup czat - + &Say: &Powiedz: - + Selected cards - + &View &Widok - + Visible Widoczne - + Floating Pływające - + Reset layout Resetuj układ - + Concede Poddaj grę - + Are you sure you want to concede this game? Czy na pewno chcesz poddać tę grę? - + Unconcede Powróć do gry - + You have already conceded. Do you want to return to this game? Poddałeś już się. Czy chcesz powrócić do tej gry? - + Leave game Opuść grę - + Are you sure you want to leave this game? Czy na pewno chcesz zakończyć tę rozgrywkę? - + A player has joined game #%1 Gracz dołączył do gry #%1 - + %1 has joined the game %1 dołączył(a) do gry. - + You have been kicked out of the game. Wyrzucono cię z gry. @@ -8859,158 +11315,158 @@ Wprowadź nazwę: TabLog - + Logs Logi - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs Logi Pokoju - + Game Logs Logi Gry - + Chat Logs Logi Czatu - - + + Error Błąd - + You must select at least one filter. Musisz wybrać przynajmniej jeden filtr. - + You have to select a valid number of days to locate. Musisz wybrać poprawną liczbę dni żeby zlokalizować. - + Username: Nazwa użytkownika: - + IP Address: Adres IP: - + Game Name: Nazwa rozgrywki: - + GameID: ID Gry: - + Message: Wiadomość: - + Main Room Pokój Główny - + Game Room Pokój - + Private Chat Rozmowa Prywatna - + Past X Days: Ostatnie X Dni: - + Today Dziś - + Last Hour Ostatnia godzina - + Maximum Results: Maksymalny Wynik: - + At least one filter is required. The more information you put in, the more specific your results will be. Wymagany jest co najmniej jeden filtr. Im więcej podanych informacji, tym bardziej sprecyzowany wynik. - + Get User Logs Pobierz Logi Użytkownika - + Clear Filters Wyczyść Filtry - + Filters Filtry - + Log Locations Loguj Połóżenia - + Date Range Okres - + Maximum Results Maksymalny Wynik - - + + Message History Historia wiadomości - + Failed to collect message history information. Nie udało się zebrać informacji o historii wiadomości. - + There are no messages for the selected filters. Brak wiadomości dla wybranych filtrów. @@ -9018,218 +11474,558 @@ Im więcej podanych informacji, tym bardziej sprecyzowany wynik. TabMessage - + Private &chat Prywatny &chat - + &Leave &Opuść - + %1 - Private chat %1 - Prywatny chat - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. Ten użytkownik Cię ignoruje, nie może zobaczyć twoich wiadomości w głównym czacie oraz nie możesz dołączyć do jego gier. - + Private message from Prywatna wiadomość od - + %1 has left the server. Gracz %1 opuścił serwer. - + %1 has joined the server. Gracz %1 pojawił się na serwerze. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system Lokalny system plików - + Server replay storage Powtórki na serwerze - - + + Watch replay Obejrzyj powtórkę - + Rename Zmień nazwę - - + + New folder Nowy folder - - + + Delete Usuń - + Open replays folder Otwórz folder powtórek - + Download replay Pobierz powtórkę - + Toggle expiration lock Wł./Wył. ochronę przed automatycznym usunięciem - + Get replay share code - - + + Look up replay by share code - + Rename local folder Zmień nazwę lokalnego folderu - + Rename local file Zmień nazwę lokalnego pliku - + New name: Nowa nazwa: - + Error Błąd - + Rename failed Zmiana nazwy nie powiodła się - + Name of new folder: Nazwa nowego folderu: - + Delete local file Usuń plik lokalny - + Are you sure you want to delete the selected files? Czy na pewno chcesz usunąć wybrane pliki? - + Are you sure you want to delete the selected replays? Czy na pewno chcesz usunąć wybrane powtórki? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay Usuń przechowywaną powtórkę @@ -9239,65 +12035,478 @@ Im więcej podanych informacji, tym bardziej sprecyzowany wynik. + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - - Friends - - - - - Online - - - - - Ignored - - - - + &Say: &Powiedz: - + Chat Czat - + &Room P&okój - + &Leave room Op&uść pokój - + &Clear chat Wyczyść &czat - + Chat Settings... Ustawienia czatu… - + mentioned you. wspomniał o tobie. - + Click to view Kliknij aby podglądnąć - + You are flooding the chat. Please wait a couple of seconds. Spamujesz. Poczekaj chwilę. @@ -9305,35 +12514,35 @@ Im więcej podanych informacji, tym bardziej sprecyzowany wynik. TabServer - + Server Serwer - - - - + + + + Error Błąd - + Failed to join the server room: it doesn't exist on the server. Błąd podczas próby dołączenia do pokoju: nie istnieje na serwerze. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. Serwer myśli że jesteś w pokoju ale twój klient nie może tego wyświetlić. Spróbuj zrestartować twojego klienta. - + You do not have the required permission to join this server room. Nie masz uprawnień do dołączenia do tego pokoju. - + Failed to join the server room due to an unknown error: %1. Błąd podczas próby dołączenia do pokoju z powodu nieznanego błędu: %1 @@ -9341,97 +12550,173 @@ Im więcej podanych informacji, tym bardziej sprecyzowany wynik. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - + Archidekt - + Home - + &Visual Deck Storage - + Visual Database Display - + Server - + Account - + Deck Storage - + Game Replays - + Administration - + Logs - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? Czy na pewno? - + There are still open games. Are you sure you want to quit? Ciągle masz otwarte gry. Czy na pewno chcesz wyjść? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view Kliknij aby podglądnąć - + Your buddy %1 has signed on! Twój znajomy %1 zalogował się! - + Unknown Event Nieznane zdarzenie - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -9442,39 +12727,39 @@ Ta wiadomośc może oznaczać że jest dostępna nowa wersja Cockatrice, lub że Aby aktualizować klienta, otwórz okno Pomoc -> Sprawdź Aktualizacje - + Idle Timeout Przekroczono czas bezczynności - + You are about to be logged out due to inactivity. Zostaniesz wylogowany ze względu na nieaktywność. - + Promotion Awans - + You have been promoted. Please log out and back in for changes to take effect. Zostałeś awansowany. Proszę wyloguj się i zaloguj ponownie aby zmiany zostały wprowadzone. - + Warned Ostrzeżony - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Otrzymałeś ostrzeżenie z powodu %1. Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. Jeżeli masz pytania, wyślij prywatną wiadomość do moderatora. - + You have received the following message from the server. (custom messages like these could be untranslated) Otrzymałeś następującą wiadomość od serwera. @@ -9494,6 +12779,34 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -9508,6 +12821,65 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. Nie można przeanalizować talii. + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -9577,57 +12949,52 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. UserCardArtSettingsDialog - + Card Art Settings - + Type a card name... - + Card name: - - Card ProviderId: + + Printing: - + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + Left margin (%): - + Right margin (%): - - Vertical offset: - - - - - Zoom: - - - - - Parameters - - - - - Preview - - - - + Remove Banner Card @@ -9635,216 +13002,266 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. UserContextMenu - + User &details &Dane użytkownika - + Private &chat Prywatny &chat - + Show this user's &games Wyświetl &gry użytkownika - + + View this user's &public decks + + + + Add to &buddy list &Dodaj do listy znajomych - + Remove from &buddy list &Usuń z listy znajomych - + Add to &ignore list Dodaj do listy &ignorowanych - + Remove from &ignore list Usuń z listy &ignorowanych - + Kick from &game Wyrzuć z &gry - + + Report user + + + + Warn user Daj ostrzeżenie użytkownikowi - + View user's war&n history Zobacz historie ostrzeżeń użytkownika - + Ban from &server Zbanuj z &serwera - + View user's &ban history Zobacz historie blokad użytkownika - + &Promote user to moderator &Daj użytkownikowi uprawnienia moderatora - + Dem&ote user from moderator &Zabierz użytkownikowi uprawnienia moderatora - + Promote user to &judge &Daj użytkownikowi uprawnienia sędziego - + Demote user from judge &Zabierz użytkownikowi uprawnienia sędziego - + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes Sprawdź notatki administratorskie - - + Investigate user + + + + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games Gry gracza %1 - - - + + + Ban History Historia blokad - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Blokada Czas;Moderator;Czas Blokady;Powód Blokady;Widoczny Powód - + User has never been banned. Użytkownik nigdy nie został zablokowany - + Failed to collect ban information. Błąd podczas zbierania informacji o blokadach. - - - + + + Warning History Historia ostrzeżeń - + Warning Time;Moderator;User Name;Reason Ostrzeżenie Czas;Moderator;Nazwa Użytkownika;Powód - + User has never been warned. Użytkownik nigdy nie dostał ostrzeżenia. - + Failed to collect warning information. Błąd podczas zbierania informacji o ostrzeżeniach. - + Failed to get admin notes. Błąd podczas zbierania notatek administratorskich. - - + + Success Operacja zakończona pomyślnie - + Successfully promoted user. Pomyślny awans gracza. - + Successfully demoted user. Pomyślna degradacja gracza. - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + Kick Player - + Are you sure you want to kick this player from the game? - - - + + + Failed Wystąpił błąd - + Failed to promote user. Błąd podczas awansu gracza. - + Failed to demote user. Błąd podczas degradacji gracza. - + Copy hash to clipboard Zapisz hash do schowka - + Remove this user's messages Usuń wiadomości tego użytkownika @@ -9882,61 +13299,61 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. Administrator - + Moderator Moderator - + Registered user Zarejestrowany użytkownik - - + + Unregistered user Niezarejestrowany użytkownik - + Judge Sędzia - + Unknown Nieznany - + The entered password does not match your account. Wpisane hasło nie pasuje do twojego konta. - - - - + + + + Information Informacje - + User information updated. Informacje o użytkowniku zaktualizowane. - - - - - - + + + + + - - - + + + + Error Błąd @@ -9960,283 +13377,292 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. Edit Banner Card + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) %n Rok%n Lat%n Lat%n Lat - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format %10%n Dzień %20%10%n Dni %20%10%n Dni %20%10%n Dni %20 - + Enter Password Wpisz hasło - + Password verification is required in order to change your email address Weryfikacja hasłem jest wymagana aby zmienić twój adres email. - - - + + + An error occurred while trying to update your user information. Wystąpił błąd podczas aktualizacji informacji o użytkowniku. - + The selected card is blacklisted on this server or another error occurred. - + Banner card removed. - + Banner card updated. - + This server does not permit you to update your user informations. Ten serwer nie pozwala na aktualizację twoich informacji o użytkowniku. - + Password changed. Hasło zmienione. - + This server does not permit you to change your password. Ten serwer nie pozwala na zmianę twojego hasła. - + The new password is too short. Nowe hasło jest za krótkie. - + The old password is incorrect. Stare hasło jest niepoprawne. - + Avatar updated. Awatar zaktualizowany - + This server does not permit you to update your avatar. Ten serwer nie pozwala na aktualizację twojego awatara. - + An error occured while trying to update your avatar. - - - This server does not permit you to update your user informations. - An error occured while trying to updater your avatar. - Ten serwer nie pozwala na aktualizację twoich informacji o użytkowniku. - UserInfoPopup - - + + Games - + Chat - + Open private chat - + Profile - + View user profile - + Show this user's games - + + Invite + + + + + Invite to your game + + + + − Buddy - + Remove from buddy list - + + Buddy - + Add to buddy list - + − Ignore - + Remove from ignore list - + + Ignore - + Add to ignore list - + Ban - + Ban from server - + Warn - + Warn user - + Ban log - + View ban history - + Warn log - + View warning history - + Notes - + View admin notes - + − Mod - + Demote from moderator - + + Mod - + Promote to moderator - + − Judge - + Demote from judge - + + Judge - + Promote to judge - + Join game - + Spectate - - + + Loading games… - + Could not load games. - + No active games. @@ -10244,223 +13670,344 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. UserInterfaceSettingsPage - + General interface settings Ogólne ustawienia interfejsu - + &Double-click cards to play them (instead of single-click) Zagrywaj karty po&dwójnym kliknięciem (zamiast pojedynczym) - + &Clicking plays all selected cards (instead of just the clicked card) Kliknięcie zagrywa wszystkie wybrane karty (zamiast tylko klikniętej karty) - + &Play all nonlands onto the stack (not the battlefield) by default Karty nie będące lądami zagrywaj domyślnie na stos (zamiast na &pole bitwy) - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens Dodaj tekst karty na tokenach - + Show selection count during drag selection - + Show total selection count - - Show subtype breakdown in selection tally + + + Disabled - + + + Enabled + + + + + + Automatic + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + Use tear-off menus, allowing right click menus to persist on screen Użycie odczepianych menu, pozwalające na zatrzymanie menu kontekstowego na ekranie - + Keep game chat focused when clicking in game (Note: disables card view search bar) - + Notifications settings Ustawienia powiadomień - + Enable notifications in taskbar Włącz &powiadomienia na pasku zadań - + Notify in the taskbar for game events while you are spectating Powiadomienia na pasku zadań dla gier, które &obserwujesz - + Notify in the taskbar when users in your buddy list connect Powiadamiaj na pasku zadań kiedy użytkownik z twojej listy znajomych zaloguje się. - + Animation settings Ustawienia animacji - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation Animacja &tapowania/odtapowania - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings - + Open deck in new tab by default Domyślnie otwórz talię w nowej karcie - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - - do nothing + + Do nothing - - ask to convert to .cod + + Ask to convert to .cod - - always convert to .cod + + Always convert to .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings Ustawienia powtórek - + Buffer time for backwards skip via shortcut: Buforuj czas do przeskoku w tył wywołane przez skrót: + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + Flip coin - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -10497,22 +14044,22 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... - + Save Filter - + Save all currently applied filters to a file - + Enter filename... @@ -10520,72 +14067,72 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDatabaseDisplayFilterToolbarWidget - + Sort by - + Filter by - + Save and load filters - + Filter by exact card name - + Filter by card main-type - + Filter by card sub-type - + Filter by set - + Filter by format legality - + Save/Load - + Name - + Main Type - + Sub Type - + Sets - + Formats @@ -10687,7 +14234,7 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -10695,19 +14242,19 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -10753,28 +14300,28 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - - + + Visual - + Loading database ... - + Clear all filters - + Table @@ -10782,43 +14329,43 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat @@ -10834,12 +14381,12 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -10847,17 +14394,17 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -10865,7 +14412,7 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -10873,52 +14420,57 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + Show Color Identity - + Show Tags On Deck Previews - + + Show Upload Time + + + + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -10926,7 +14478,7 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -10934,22 +14486,22 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -10957,17 +14509,22 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + + Select decks to share + + + + Visual Deck Storage Settings @@ -10975,177 +14532,202 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. WarningDialog - + Which warning would you like to send? Jakie ostrzeżenie chciałbyś wysłać? - + Redact all messages from this user in all rooms Redaguj wiadomości od tego użytkownika we wszystkich pokojach - + &OK &OK - + &Cancel &Anuluj - + Warn user for misconduct Ostrzeż gracza za złe zachowanie - - + + Error Błąd - + User name to send a warning to can not be blank, please specify a user to warn. Nazwa gracza do wysłania ostrzeżenia nie może być pusta, prosze podać gracza. - + Warning to use can not be blank, please select a valid warning to send. Ostrzeżenie nie może być puste, zaznacz poprawne ostrzeżenie do wysłania. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top Przesuń zaznaczony set do góry - + Move selected set up Przesuć wybrany dodatek w górę - + Move selected set down Przesuń wybrany dodatek w dół - + Move selected set to the bottom Przesuń zaznaczony set na dół - - Search by set name, code, or type - Szukaj po nazwie dodatku, kodzie lub typie - - - + Default order Domyślna kolejność - + Restore original art priority order Przywróć oryginalny priorytet obrazów kart - + Enable all sets Włącz wszystkie dodatki - + Disable all sets Wyłącz wszystkie dodatki - + Enable selected set(s) Włącz zaznaczony/e set(y) - + Disable selected set(s) Wyłącz zaznaczony/e set(y) - + Deck Editor Edycja Talii - + Use CTRL+A to select all sets in the view. Użyj CTRL+A aby wybrać wszystkie dodatki w widoku. - + Only cards in enabled sets will appear in the card list of the deck editor. Tylko karty z odblokowanych dodatków pojawią się w edytorze talii kart - + Image priority is decided in the following order: Priorytet obrazów jest ustalany w następujący sposób: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki najpierw folder NIESTANDARDOWYCH (%1), potem Włączone Dodatki w tym oknie dialogowym (Z góry na dół) - + Include cards rebalanced for Alchemy [requires restart] - + Card Art Obraz karty - + + Search by set name, code, type, or release date + + + + How to use custom card art Jak ustawić niestandardowe obrazy do kart - + Hints Wskazówki - + Note Notatka - + Sorting by column allows you to find a set while not changing set priority. Sortowane kolumnami pozwala ci znaleźć dodatek nie zmieniając ich priorytetów. - + To enable ordering again, click the column header until this message disappears. Aby wyłączyć sortowanie klikaj ponownie na ten sam nagłówek kolumny aż zniknie ten komunikat. - + Use the current sorting as the set priority instead Użyj tymczasowego sortowania jako priorytet dodatków - + Sorts the set priority using the same column Sortuje priorytet dodatków korzystając z tej samej kolumny - + Manage sets Zarządzaj dodatkami @@ -11153,93 +14735,85 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped Niepogrupowane - + Group by Type Grupuj po typie - + Group by Mana Value Grupuj po wartości many - + Group by Color Grupuj po kolorze - + Unsorted Nieposortowane - + Sort by Name Sortuj według nazwy - + Sort by Type Sortuj według typu - + Sort by Mana Cost Sortuj według wartości many - + Sort by Colors Sortuj według koloru - + Sort by P/T Sortuj według P/T - + Sort by Set Sortuj według dodatku - + shuffle when closing przetasuj przy zamykaniu - + pile view widok sterty - - i18n - - - English - Polski (Polish) - - main - + Connect on startup Zaloguj po uruchowmieniu - + Debug to file Debuguj do pliku @@ -12023,6 +15597,16 @@ Unikaj tego zachowania, w przeciwnym mogą zostać pozwięte dalsze czynności. Graveyard Grób + + + Report Queue + + + + + Moderation + + diff --git a/cockatrice/translations/cockatrice_pt.ts b/cockatrice/translations/cockatrice_pt.ts index 912ee09d0..12cd17f74 100644 --- a/cockatrice/translations/cockatrice_pt.ts +++ b/cockatrice/translations/cockatrice_pt.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... Definir marcador @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter - + New value for counter '%1': @@ -36,64 +36,113 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. - + There are no cards in your deck to be exported + + AccountSetupPage + + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + AddAnalyticsPanelDialog @@ -105,12 +154,12 @@ Please check that the directory is writable and try again. AdminNotesDialog - + Update Notes - + Admin Notes for %1 @@ -136,218 +185,304 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error - + Could not create themes directory at '%1'. - + Theme settings Definições do Tema - + Current theme: Tema actual: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering Rendering da carta - + Display card names on cards having a picture Mostrar o nome em cartas com imagem - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Light - + Dark - + + System - + + Show all playmats + + + + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + Active theme palette: - + + Active theme style: + + + + + Qt widget style saved to this theme ("System" lets the application decide) + + + + Edit theme palette - + Home tab settings - + Display card name of background in bottom right - + + Dim the home tab background + + + + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + Styling settings - + Style user list - + Card printings - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Aumentar as cartas ao passar o rato - + Use rounded card corners - + Card layout - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Disposição da Mão - + Display hand horizontally (wastes space) Mostrar mão horizontalmente (desperdiça espaço) - + Enable left justification Permitir justificação de texto à esquerda - + Table grid layout Esquema da mesa - + Invert vertical coordinate Inverter coordenada vertical - + Minimum player count for multi-column layout: Número mínimo de kogadores para layout com múltiplas colunas: - + Maximum font size for information displayed on cards: @@ -355,12 +490,12 @@ Please check that the directory is writable and try again. ArchidektApiResponseDeckDisplayWidget - + Back to results - + Open Deck in Deck Editor @@ -386,112 +521,112 @@ Please check that the directory is writable and try again. BanDialog - + ban &user name Banir nome de &utilizador - + ban &IP address Banir endereço de &IP - + ban client I&D banir I&D do cliente - + Ban type Tipo de Ban - + &permanent ban Ban &permanente - + &temporary ban Ban &temporário - + &Days: &Dias: - + &Hours: &Horas: - + &Minutes: &Minutos: - + Duration of the ban Duração do ban - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Por favor introduza o motivo do ban. Isto apenas é guardado para os moderadores e não é visível para a pessoa banida. - + Please enter the reason for the ban that will be visible to the banned person. Por favor introduza o motivo do ban que será visível à pessoa banida. - + Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Cancelar - + Ban user from server Banir utilizador do servidor - - - - + + + + Error Erro - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Tem de seleccionar um banimento baseado no nome, no IP ou ambos. - + You must have a value in the name ban when selecting the name ban checkbox. Deve preencher o campo de nome a banir quando selecciona a caixa de verificação para banir um nome. - + You must have a value in the ip ban when selecting the ip ban checkbox. Deve preencher o campo de IP a banir quando selecciona a caixa de verificação para banir um IP. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Deve preencher o campo de identificação de cliente quando selecciona a caixa de verificação para banir uma identificação de cliente. @@ -499,22 +634,22 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban BetaReleaseChannel - + Beta - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the file update server. @@ -522,7 +657,20 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardArtPreviewWidget - + + Banner preview + + + + + + + + Zoom %1× + + + + No card selected @@ -530,22 +678,22 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardArtRulesModel - + Card - + ProviderId - + Mode - + Reason @@ -553,65 +701,209 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardDatabaseModel - + Name Nome - + Sets Expansões - + Mana cost Custo de mana - + Card type Tipo de carta - + P/T P/R - + Color(s) Cor(es) + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + CardDatabaseView - + Add to Deck - + Add to Sideboard - + Select Printing - + + Add to Zone + + + + + Create &new zone... + + + + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards @@ -721,22 +1013,22 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardInfoFrameWidget - + Image - + Description - + Both - + View transformation @@ -744,22 +1036,22 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -767,27 +1059,27 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardInfoTextWidget - + Name: - + Set: - + Collector Number: - + Related cards: - + Unknown card: @@ -795,129 +1087,129 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Reduce life by power - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -938,7 +1230,7 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban CardSizeWidget - + Card Size @@ -1078,6 +1370,24 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser @@ -1107,280 +1417,382 @@ Isto apenas é guardado para os moderadores e não é visível para a pessoa ban + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + ConnectionController - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - - - - - - - - - - - + + + + + + + + + + - - + + + Error - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + Server timeout - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Information - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -1388,113 +1800,113 @@ To update your client, go to Help -> Check for Updates. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - + Requesting forgotten password to %1 as %2... - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. @@ -1544,7 +1956,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1560,17 +1972,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add card to &maindeck - + Add card to &sideboard @@ -1578,97 +1990,158 @@ To update your client, go to Help -> Check for Updates. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + Format: - + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1676,17 +2149,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1694,114 +2167,119 @@ To update your client, go to Help -> Check for Updates. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + + Share deck... + + + + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1809,7 +2287,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1817,110 +2295,152 @@ To update your client, go to Help -> Check for Updates. DeckEditorSettingsPage - - + + Update Spoilers - + Success - + Download URLs have been reset. - + Add URL - - + + URL: - - + + Edit URL - + Add New URL - + Remove URL - + Updating... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - + Reset Download URLs @@ -1928,32 +2448,32 @@ To update your client, go to Help -> Check for Updates. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1961,27 +2481,27 @@ To update your client, go to Help -> Check for Updates. DeckListModel - + Count - + Set - + Number Número - + Provider ID - + Card Carta @@ -2002,17 +2522,22 @@ To update your client, go to Help -> Check for Updates. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -2020,7 +2545,7 @@ To update your client, go to Help -> Check for Updates. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -2028,62 +2553,62 @@ To update your client, go to Help -> Check for Updates. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2091,94 +2616,109 @@ To update your client, go to Help -> Check for Updates. DeckPreviewWidget - + Banner Card - + Open in deck editor - + + Share deck... + + + + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Rename deck + + + + + Rename file + + + + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -2186,57 +2726,102 @@ To update your client, go to Help -> Check for Updates. DeckStateManager - + Rename deck to "%1" from "%2" - + Updated comments (was %1 chars, now %2 chars) - + Set banner card to %1 (%2) - + + Set playmat to %1 + + + + Tags changed - + Set format to %1 - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + %1 1 × "%2" (%3) - + Added - + Removed @@ -2258,94 +2843,122 @@ To update your client, go to Help -> Check for Updates. DeckViewContainer - + Load deck... &Carregar deck... - + Load remote deck... Carregar deck remoto... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start - + Force start - + Sideboard unlocked - + Sideboard locked - - + + Error Erro - + The selected file could not be loaded. O ficheiro seleccionado não pôde ser carregado. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? - + Remember and automatically apply choice in the future @@ -2358,143 +2971,143 @@ This will kick all non-ready players from the game. - + Known Hosts - + Delete the currently selected saved server - + Refresh the server list with known public servers - + New Host Novo anfitrião - + Name: - + &Host: &Servidor: - + &Port: &Porta: - + Player &name: &Nome do jogador: - + P&assword: P&assword: - + &Save password &Guardar password - + A&uto connect Conectar a&utomaticamente no arranque - + Automatically connect to the most recent login when Cockatrice opens Conectar automaticamente ao mais recente login ao abrir o Cockatrice - + If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage - + Reset Password - + Forgot password? - + &Connect - + Server Servidor - + Login Login - + Server Contact - + Connect to Server - + Server URL - + Communication Port - + Unique Server Name - + Connection Warning - + You need to name your new connection profile. - + Connect Warning Aviso de conexão - + The player name can't be empty. O nome de utilizador tem de ser inserido. @@ -2502,122 +3115,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings Re&lembrar definições. - + &Description: &Descrição: - + P&layers: &Jogadores: - + General - + Game type Tipo de jogo - + &Password: &Password: - + Only &buddies can join Apenas &amigos podem entrar - + Only &registered users can join Apenas utilizadores &registados podem entrar - + Joining restrictions Restrições para ligar - + &Spectators can watch E&spectadores podem ver - + Spectators &need a password to watch Espectadores &necessitam de palavra-passe - + Spectators can &chat Espectadores podem c&onversar - + Spectators can see &hands &Espectadores podem ver as mãos - + Create game as spectator - + Spectators Espectadores - + Starting life total: - + Open decklists in lobby - + Create game as judge - + Game setup options - + &Clear &Limpar - + Create game Criar jogo - + Game information Informação do jogo - + Error Erro - + Server error. Erro do servidor. @@ -2625,97 +3238,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Nome: - + Token Ficha - + C&olor: C&or: - + white branco - + blue azul - + black preto - + red vermelho - + green verde - + multicolor multicolor - + colorless incolor - + &P/T: &P/R: - + &Annotation: &Nota: - + &Destroy token when it leaves the table &Destruir ficha quando ela deixar a mesa - + Create face-down (Only hides name) - + Token data Informação de ficha - + Show &all tokens Mostrar &todas as fichas - + Show tokens from this &deck Mostrar &fichas deste deck - + Choose token from list Escolha ficha da lista - + Create token Criar ficha @@ -2723,53 +3336,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + ✖ - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2778,7 +3391,7 @@ This will kick all non-ready players from the game. DlgEditAvatar - + No image chosen. Não escolheu nenhuma imagem. @@ -2790,27 +3403,32 @@ To remove your current avatar, confirm without choosing a new image. Para remover o seu avatar actual, confirme SEM escolher uma nova imagem. - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... Procurar... - + Change avatar Alterar avatar - + Open Image Abrir Imagem - + Image Files (*.png *.jpg *.bmp) Ficheiros de Imagem (*.png *.jpg *.bmp) - + Invalid image chosen. Escolheu uma imagem inválida. @@ -2836,38 +3454,38 @@ Para remover o seu avatar actual, confirme SEM escolher uma nova imagem. DlgEditPassword - + Old password: Password antiga: - + New password: Nova password: - + Confirm new password: Confirme a nova password: - + Change password Alterar a password - - + + Error Erro - + Your password is too short. - + The new passwords don't match. As novas passwords que digitou não coincidem. @@ -2875,93 +3493,93 @@ Para remover o seu avatar actual, confirme SEM escolher uma nova imagem. DlgEditTokens - + &Name: &Nome: - + C&olor: C&or: - + white branco - + blue azul - + black preto - + red vermelho - + green verde - + multicolor multicolor - + colorless incolor - + &P/T: &P/R: - + &Annotation: &Nota: - + Token data Informação de ficha - - + + Add token Adicionar ficha - + Remove token Remover ficha - + Edit custom tokens - + Please enter the name of the token: Por favor introduza o nome da ficha: - + Error Erro - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. @@ -3039,7 +3657,8 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games that have started + Hide started games + Hide games that have started @@ -3075,8 +3694,9 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - &Creator name: - &Nome do criador: + &Host name: + &Creator name: + @@ -3142,52 +3762,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning - + A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. - + &Host: - + &Port: - + Player &name: - + Email: - + Reset Password Challenge - + Reset Password Challenge Error - + The email address can't be empty. @@ -3195,37 +3815,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. - + &Host: - + &Port: - + Player &name: - + Reset Password Request - + Reset Password Error - + The player name can't be empty. @@ -3233,94 +3853,127 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning - + A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. - + &Host: - + &Port: - + Player &name: - + Token: - - + + New Password: - + Reset Password - + The player name can't be empty. - - - - + + + + Reset Password Error - + The token can't be empty. - + The new password can't be empty. - + Error - + Your password is too short. - + The passwords do not match. + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck @@ -3404,165 +4057,566 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLocalGameOptions - + Players: - + General - + Starting life total: - + Game setup options - + Remember settings - + Local game options + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: &Servidor: - + &Port: &Porta: - + Player &name: &Nome do jogador: - + P&assword: P&assword: - + Password (again): Password (novamente): - + Email: Email: - + Email (again): Email (novamente): - + Country: País: - + Undefined Indefinido - + Real name: Nome real: - + Register to server Registe-se no server - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning Registo recusado - + Your password is too short. - + Your passwords do not match, please try again. As passwords que digitou não coincidem. - + Your email addresses do not match, please try again. Os emails que digitou não coincidem. - + The player name can't be empty. Tem de escolher um nome de utilizador. + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice @@ -3628,46 +4682,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Erro desconhecido ao carregar a base de dados das cartas - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - Base de dados de cartas inválida. - -Cockatrice pode não funcionar correctamente com uma base de dados inválida. - -Poderá necessitar de correr o Oracle para actualizar a base de dados. - -Gostaria de alterar a localização da base de dados? - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - A versão da base de dados está desactualizada. - -Poderá causar problemas ao carregar a informação ou a imagem das cartas - -Pode ser reparado correndo o Oracle de novo para actualizar a base de dados das cartas. - -Gostaria de alterar a localização da base de dados? - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3676,7 +4696,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3685,7 +4705,7 @@ Would you like to change your database location setting? Gostaria de alterar a localização da base de dados? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3694,7 +4714,7 @@ Would you like to change your database location setting? Gostaria de alterar a localização da base de dados? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3703,104 +4723,235 @@ Would you like to change your database location setting? - - - + + + Error Erro - + The path to your deck directory is invalid. Would you like to go back and set the correct path? O directório do seu deck é inválido. Gostaria de voltar atrás e corrigir o directório? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? O directório das imagens das cartas é inválido. Gostaria de voltar atrás e corrigir o directório? - + Settings Definições - + + Search settings... + + + + + OK + + + + General Geral - + Appearance Aparência - + User Interface Interface do utilizador - + Storage - + Card Sources - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat Chat - + Sound Som - + Shortcuts Atalhos + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3808,17 +4959,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next - + Previous - + Tip of the Day @@ -3826,164 +4977,174 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel - + Reinstall - + Cancel Download - + Open Download Page Abrir a página de downloads - + Check for Client Updates - - - - + + + + Error Erro - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - + Downloading update: %1 - + Checking for updates... A procurar actualizações... - + Finished checking for updates - + No Update Available - + Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. - + Current version - + Selected release channel - - + + Update Available - - + + A new version of Cockatrice is available! - - + + New version - - + + Released - - + + Changelog - + Do you want to update now? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error Erro na actualização - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Installing... A instalar... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -3991,17 +5152,17 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing - + Copy to clipboard - + Debug Log Registo de erros @@ -4239,7 +5400,7 @@ You may have to manually download the new version. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -4247,7 +5408,7 @@ You may have to manually download the new version. FilterBuilder - + Type your filter here @@ -4255,45 +5416,88 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -4301,140 +5505,156 @@ You may have to manually download the new version. GameSelector - - - - - - - - - + + + + + + + + + Error Erro - + Please join the appropriate room first. Por favor entre na sala apropriada primeiro. - + Wrong password. Password incorrecta. - + Spectators are not allowed in this game. Não são permitidos espectadores neste jogo. - + The game is already full. O jogo já se encontra cheio. - + The game does not exist any more. O jogo já não existe. - + This game is only open to registered users. Este jogo só está aberto a utilizadores registados. - + This game is only open to its creator's buddies. Este jogo só está aberto aos amigos do seu criador. - + You are being ignored by the creator of this game. Você está a ser ignorado pelo criador deste jogo. - + Join Game - + Spectate Game - + Game Information - + + Cop&y game link + + + + Join Game as Judge - + Spectate Game as Judge - + + Join game Entrar no jogo - - Password: - Password: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. Por favor entre na sala respectiva primeiro. - + &Filter games &Filtrar jogos - + C&lear filter &Limpar filtros - + C&reate C&riar - + &Join &Entrar - + Join as judge - + J&oin as spectator Entrar como &espectador - + Join as judge spectator - + Games shown: %1 / %2 - + Games Jogos @@ -4475,105 +5695,105 @@ You may have to manually download the new version. GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password senha - + buddies only amigos apenas - + reg. users only utilizadores registados apenas - + open decklists - - + + can chat pode falar - + see hands ver mãos - + can see hands pode ver as mãos - + not allowed não permitidos - + Room Sala - + Age Idade - + Description Descrição - - Creator - Criador + + Host + - + Type Tipo - + Restrictions Restrições - + Players Jogadores - + Spectators Espectadores @@ -4581,158 +5801,275 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + English + Português (Portuguese) + + + + + + + + + Choose path Escolher directório - + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> + + + + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + Language settings - + Language: Língua: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + Version settings - + Card database - + Startup settings - + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) Diretórios (edição desativada em modo portátil) - + Paths Directórios - + How to help with translations - + Decks directory: Directório dos decks: - + Filters directory: - + Replays directory: Directório de replays: - + Pictures directory: Directório das imagens: - + Card database: Base de dados das cartas: - + Custom database directory: - + Token database: Base de dados das fichas: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup - + Last update check on %1 (%2 days ago) @@ -4740,47 +6077,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4874,59 +6211,353 @@ You may have to manually download the new version. + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu @@ -5144,271 +6775,292 @@ You may have to manually download the new version. MainWindow - - + + Player %1 Jogador %1 - + Load replay Carregar replay - + About Cockatrice Sobre o Cockatrice - + Version - + Cockatrice Webpage Website Cockatrice - + Project Manager: Gestor do projecto: - + Past Project Managers: Anteriores gestores do projecto: - + Developers: Desenvolvedores: - + Our Developers Os nossos desenvolvedores - + Help Develop! Ajuda Desenvolvimento! - + Translators: Tradutores: - + Our Translators - + Help Translate! Ajude a traduzir! - + Support: Ajuda técnica: - + Report an Issue Reportar um Problema - + Troubleshooting Solução de problemas - + F.A.Q. Perguntas frequentes - - + + Error Erro - + &Connect... &Ligar... - + &Disconnect &Desligar - + Start &local game... Começar &jogo local... - + &Watch replay... &Ver replay... - + &Full screen Ecrã &inteiro - + &Register to server... &A registar no servidor... - + &Restore password... - + &Settings... &Configurações... - + &Exit &Sair - + A&ctions A&cções - + &Cockatrice &Cockatrice - + C&ard Database B&ase de dados de cartas - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder Abrir a pasta de imagens personalizadas - + Open custom sets folder Abrir a pasta de expansões personalizadas - + Add custom sets/cards Adicione cartas/expansões personalizadas - + Reload card database - + Tabs - + &Help &Ajuda - + &About Cockatrice S&obre o Cockatrice - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + + Re-run Onboarding Wizard... + + + + Show/Hide - + + Sign-in cancelled + + + + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + + Are you sure? + Tens a certeza? + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database Base de dados de cartas - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5417,140 +7069,140 @@ Deseja actualizar a sua base de dados agora?%s Se não tem a certeza ou é a primeira vez que está a utilizar o Cockatrice, escolha "Sim" - - + + Yes Sim - - + + No Não - + Open settings Abrir definições - + New sets found Novas expansões foram encontradas - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + Yes, always enable - + View sets Ver expansões - + Welcome Bem-vindo/a - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - + Information Informação - + A card database update is already running. A actualização da base de dados das cartas já está a correr. - + Unable to run the card database updater: Incapaz de correr o actualizador da base de dados das cartas: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - - - - - + + + + + Load sets/cards Carregar expansões/cartas - + Selected file cannot be found. O ficheiro escolhido não foi encontrado. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Actualização completa com sucesso. %s O Cockatrice irá agora recarregar a base de dados das cartas. - + Sets/cards failed to import. Falha na importação de expansões/cartas. @@ -5755,615 +7407,615 @@ O Cockatrice irá agora recarregar a base de dados das cartas. MessageLogWidget - + from play do jogo - + from their graveyard do seu cemitério - + from exile vindo do exílio - + from their hand da sua mão - + the top card of %1's library a carta do topo do grimório de %1 - + the top card of their library a carta do topo dos seus grimórios - + from the top of %1's library do topo do grimório de %1 - + from the top of their library do topo dos seus grimórios - + the bottom card of %1's library a carta do fundo do grimório de %1 - + the bottom card of their library a carta do fundo dos seus grimórios - + from the bottom of %1's library do fundo do grimório de %1 - + from the bottom of their library do fundo dos seus grimórios - + from %1's library do grimório de %1 - + from their library dos seus grimórios - + from sideboard do sideboard - + from the stack da pilha - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 está agora a manter a carta do topo %2 revelada. - + %1 is not revealing the top card %2 any longer. %1 já não está a manter a carta do topo %2 revelada. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. %1 anexa %2 a %4 de %3. - + %1 has conceded the game. %1 concedeu o jogo. - + %1 has unconceded the game. - + %1 has restored connection to the game. %1 restabeleceu a ligação ao jogo. - + %1 has lost connection to the game. %1 perdeu a ligação ao jogo. - + %1 points from their %2 to themselves. %1 aponta do seu %2 para si próprio. - + %1 points from their %2 to %3. %1 aponta do seu %2 para %3. - + %1 points from %2's %3 to themselves. %1 aponta do %3 de %2 para si própria. - + %1 points from %2's %3 to %4. %1 aponta de %3 de %2 para %4. - + %1 points from their %2 to their %3. %1 aponta do seu %2 para %3. - + %1 points from their %2 to %3's %4. %1 aponta do seu %2 para o %4 de %3. - + %1 points from %2's %3 to their own %4. %1 aponta de %3 de %2 para o seu %4. - + %1 points from %2's %3 to %4's %5. %1 aponta de %3 de %2 para %5 de %4. - + %1 creates a face down token. - + %1 creates token: %2%3. %1 cria ficha: %2%3. - + %1 has loaded a deck (%2). %1 carregou um deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 carregou um deck com %2 cartas na sideboard (%3). - + %1 destroys %2. %1 destrói %2. - + a card uma carta - + %1 gives %2 control over %3. %1 dá controlo sobre %3 a %2. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1 coloca %2 em jogo %3. - + %1 puts %2%3 into their graveyard face down. - + %1 puts %2%3 into their graveyard. %1 coloca %2%3 no seu grimório. - + %1 exiles %2%3 face down. - + %1 exiles %2%3. %1 exila %2%3. - + %1 moves %2%3 to their hand. %1 move %2%3 para a sua mão. - + %1 puts %2%3 into their library. %1 coloca %2%3 no seu grimório. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. %1 coloca %2%3 no topo do seu grimório. - + %1 puts %2%3 into their library %4 cards from the top. %1 põe %2%3 no seu grimório, %4 cartas a contar do topo. - + %1 moves %2%3 to sideboard. %1 move %2%3 para o sideboard. - + %1 plays %2%3 face down. - + %1 plays %2%3. %1 joga %2%3. - + %1 moves %2%3 to custom zone '%4' face down. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. %1 está a olhar para %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. Este jogo foi encerrado. - + The game has started. O jogo começou. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 entrou no jogo. - + %1 is now watching the game. %1 está agora a ver o jogo. - + You have been kicked out of the game. Você foi expulso do jogo. - + %1 has left the game (%2). %1 abandonou o jogo (%2). - + %1 is not watching the game any more (%2). %1 não está mais a observar o jogo (%2). - + %1 is not ready to start the game any more. %1 já não está pronto a começar o jogo. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. Está a ver um replay do jogo #%1. - + %1 is ready to start the game. %1 está pronto a começar o jogo. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. %1 revela %2 a %3. - + %1 reveals %2. %1 revela %2. - + %1 randomly reveals %2%3 to %4. %1 revela aleatoreamente %2%3. a %4. - + %1 randomly reveals %2%3. %1 revela aleatoreamente %2%3. - + %1 peeks at face down card #%2. %1 espreita a carta virada para baixo #%2. - + %1 peeks at face down card #%2: %3. %1 espreita a carta virada para baixo #%2: %3. - + %1 reveals %2%3 to %4. %1 revela %2%3 a %4. - + %1 reveals %2%3. %1 revela %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads Cara - + Tails Coroa - + %1 flipped a coin. It landed as %2. %1 atirou uma moeda ao ar. Calhou %2. - + %1 rolls a %2 with a %3-sided die. %1 obteve %2 com um dado de %3 faces. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. Turno de %1. - + %1 sets annotation of %2 to %3. %1 coloca uma nota de %2 em%3. - + %1 places %2 %3%4 counter(s) on %5 (now %6). - + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + %1 failed to undo their last draw. - + %1 sets counter %2 to %3 (%4%5). %1 altera o número de marcadores %2 para %3(%4%5). - + %1 sets %2 to not untap normally. %1 define %2 para não desvirar normalmente. - + %1 sets %2 to untap normally. %1 define %2 para desvirar normalmente. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. %1 bloqueou o seu sideboard. - + %1 has unlocked their sideboard. %1 desbloqueou o seu sideboard. - + %1 taps their permanents. %1 vira as suas permanentes. - + %1 untaps their permanents. %1 desvira as suas permanentes. - + %1 taps %2. %1 vira %2. - + %1 untaps %2. %1 desvira %2. - + %1 shuffles %2. %1 baralha %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. %1 desanexa %2. - + %1 undoes their last draw. %1 desfaz a sua última compra. - + %1 undoes their last draw (%2). %1 desfaz a sua última compra (%2). @@ -6371,115 +8023,125 @@ O Cockatrice irá agora recarregar a base de dados das cartas. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message Adicionar mensagem - - + + Message: Mensagem: - + Edit message - + Chat settings Definições do Chat - + Custom alert words Palavras alerta personalizáveis - + Enable chat mentions Permitir menções no chat - + Enable mention completer Permitir complementação de menções - + In-game message macros Macros de mensagem no decorrer do jogo - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users Ignorar mensagens de chat enviadas por utilizadores não registados - + Ignore private messages sent by unregistered users Ignorar mensagens privadas enviadas por utilizadores não registados - + Ignore private messages sent by non-buddy users - - + + Invert text color Inverter cor do texto - + Enable desktop notifications for private messages Permitir notificações de mensagens privadas no Ambiente de Trabalho - + Enable desktop notification for mentions Permitir notificações de menções no ambiente de trabalho - + Enable room message history on join Permitir o histórico de mensagens de uma sala ao entrar - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) (A cor é hexadecimal) - + Separate words with a space, alphanumeric characters only Palavras separadas com espaço, apenas caracteres alfanuméricos @@ -6487,42 +8149,42 @@ O Cockatrice irá agora recarregar a base de dados das cartas. MoveMenu - + Move to - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + T&able - + &Hand - + &Graveyard - + &Exile @@ -6601,148 +8263,134 @@ O Cockatrice irá agora recarregar a base de dados das cartas. PaletteEditorDialog - - + + Reset - - - Apply - - - - - - Save && Apply - - - - + ▼ Edit Palette - - + + ▶ Edit Palette - + Palette Editor — %1 - + <b>Palette Editor</b> &nbsp;·&nbsp; %1 - - This theme ships no default palette files - - - - - Replace current colours with the theme author's defaults - - - - - Switch between the light and dark palette files + + + Save - Editing: + Delete this scheme's custom palette and revert to the theme default (or the application palette) - Show or hide the per-role colour grid for manual tweaks + Switch between the light and dark palette files + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + ↺ Revert to theme default - + Discard unsaved edits and restore the last saved palette - - Preview this palette without saving to disk + + Cannot save: this theme has no writable directory - + Write palette-%1.toml and reload the theme - - Cannot save: this theme has no directory on disk - - - - + + Save failed - + + Could not write %1 to: %2 - - - No default found - - - - - No default palette file found for the "%1" scheme. - - PaletteGridWidget - + Active - + Disabled - + Inactive - + Normal interactive state - + Widget is disabled / not interactive - + Window is in background / unfocused + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + Phase @@ -6865,30 +8513,21 @@ O Cockatrice irá agora recarregar a base de dados das cartas. Fase Final - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - pt - - PlayerActions - - - - + + + + grave - - - - + + + + exile @@ -7007,16 +8646,272 @@ O Cockatrice irá agora recarregar a base de dados das cartas. PlayerMenu - + Player "%1" - + &Counters + + PlaymatCollectionDialog + + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + PrintingDisabledInfoWidget @@ -7037,12 +8932,12 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelector - + Display Navigation Buttons - + Printing Selector @@ -7050,25 +8945,65 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -7099,28 +9034,28 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -7136,95 +9071,103 @@ This setting means you'll only see the default printing for each card, instead o PtMenu - + Power / toughness - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services Serviços - + Hide %1 Ocultar %1 - + Hide Others Ocultar outros - + Show All Mostrar tudo - + Preferences... Preferências... - + Quit %1 Sair %1 - + About %1 Cerca %1 @@ -7232,17 +9175,17 @@ This setting means you'll only see the default printing for each card, instead o QObject - + Cockatrice card database (*.xml) Base de dados de cartas Cockatrice (*.xml) - + All files (*.*) Todos os ficheiros (*.*) - + Cockatrice replays (*.cor) Replays do Cockatrice (*.cor) @@ -7262,17 +9205,22 @@ This setting means you'll only see the default printing for each card, instead o Tokens - + + Maybeboard + + + + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7283,7 +9231,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7294,100 +9242,110 @@ Are you sure you would like to disable this feature? - + Confirm Change + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -7395,121 +9353,189 @@ Are you sure you would like to disable this feature? QuickSetupPanel - + %1% - + <b>Quick Setup</b> - + Generate all palette roles automatically from a single accent colour - + Accent: - + Primary hue. Used directly for highlights and links. At high intensity it also tints buttons and backgrounds. - + Intensity: - + Subtle - + Full colour - + 0–30 Subtle tint — only highlights and links change hue 30–70 Accented — buttons, tooltips, and borders join in 70–100 Full colour — backgrounds, everything - + 70% - - - Generate ↓ - - - - - Derive all palette roles from the accent colour above. -Fine-tune individual colours in the grid afterwards. - - RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name Nome - + ID ID - + Upload time Hora de upload + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID ID - + Name Nome - + Players Jogadores - + Keep Keep - + Time started Tempo de começo - + Duration (sec) Duração (seg) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu @@ -7551,37 +9577,37 @@ Fine-tune individual colours in the grid afterwards. RoomSelector - + Rooms Salas - + Joi&n E&ntrar - + Room Sala - + Description Descrição - + Permissions Permissões - + Players Jogadores - + Games Jogos @@ -7589,7 +9615,7 @@ Fine-tune individual colours in the grid afterwards. SayMenu - + S&ay @@ -7597,32 +9623,32 @@ Fine-tune individual colours in the grid afterwards. SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - + Invalid key - + Shortcut already in use by: - + Clear - + Restore default @@ -7655,56 +9681,92 @@ Fine-tune individual colours in the grid afterwards. Data de lançamento + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + + Shortcut editor + + + + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -7758,12 +9820,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -7771,27 +9833,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Permitir &sons - + Current sounds theme: Tema sonoro actual: - + Test system sound engine Teste motor de sistema de som - + Sound settings Definições de Som - + Master volume Volume mestre @@ -7799,48 +9861,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -7848,157 +9910,170 @@ Please check your shortcut settings! StableReleaseChannel - + Default - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. + + StatsTally + + + Total Power + + + + + Total Toughness + + + StorageSettingsPage - - - + + + Success - + Cached card pictures have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + In-memory (currently loaded) card pictures have been reset. - + Card Picture Loader Caching Method: - + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. - + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. - + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. - + Delete Cached Images - + Delete Saved Images - + Clear In-Memory Images - + Card Picture Loader Cache Method - + Network Cache - + Filesystem - + In-Memory Picture Cache - + Network Cache Size: - + On-disk cache for downloaded pictures - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + In-memory cache for pictures not currently on screen - + Naming scheme: - + Day(s) @@ -8006,17 +10081,33 @@ Please check your shortcut settings! TabAccount - + + + My Reports + + + + Add to Buddy List - + Add to Ignore List - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account @@ -8137,118 +10228,118 @@ Please check your shortcut settings! TabArchidekt - - - + + + Desc. - - + + AND - - + + Require ALL selected colors - - + + Deck name... - - + + Owner... - - + + Packages - - + + Advanced Filters - + Bracket: - - + + Any - - + + Contains card... - - + + Commander... - - + + Tag... - - + + Deck Size - + Cards: - - + + Asc. - + Sort by: - + Filter by: - + Display Settings - - + + Search - - + + Formats @@ -8261,42 +10352,42 @@ Please check your shortcut settings! TabCardArtRules - + Card: - + ProviderId: - + Mode: - + Reason: - + Type a card name... - + Add rule - + Remove rule - + Refresh @@ -8309,52 +10400,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info Informação da carta - + Deck Baralho - + Filters Filtros - + &View &Vista - + Card Database - + Printing - + Visible Visível - + Floating Flutuando - + Reset layout Repor disposição - + Deck: %1 Deck:%1 @@ -8362,49 +10453,49 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - + &View - + Printing - + Visible - + Floating - + Reset layout @@ -8412,22 +10503,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -8435,139 +10526,237 @@ Please check your shortcut settings! TabDeckStorage - + Local file system Ficheiros locais - + Server deck storage Decks armazenados no servidor - - + + Open in deck editor Abrir no editor de decks - + Rename deck or folder - + Upload deck Upload do deck - + Download deck Download do deck - - - - + + + + New folder Nova pasta - - + + Delete Apagar - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + + + + Error - + Rename failed - - + + + Invalid deck file - + Enter deck name Introduza o nome do deck - + This decklist does not have a name. Please enter a name: Este deck nao tem um nome Por favor introduza um nome: - + Unnamed deck Deck sem nome - + Failed to upload deck to server - + Delete local file Apagar ficheiro local - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: Nome da nova pasta: - + Deck Storage @@ -8575,20 +10764,271 @@ Por favor introduza um nome: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -8601,27 +11041,27 @@ Por favor introduza um nome: TabEdhRecMain - + &Cards - + Top Commanders - + Tags - + Search for a card ... - + Search @@ -8634,191 +11074,207 @@ Por favor introduza um nome: TabGame - - - + + + Replay Repetição - - + + Game Jogo - - + + Player List Jogadores - - + + + Invite + + + + + Card Info Informação da carta - - + + Messages Mensagens - - + + Replay Timeline Repetir Cronologia - + &Phases Fa&ses - + &Game &Jogo - + Next &phase Próxima &fase - + Next phase with &action - + Next &turn Próximo &turno - + Reverse turn order - + &Remove all local arrows &Remover todas as setas locais - + Rotate View Cl&ockwise Rodar a vista no se&ntido dos ponteiros - + Rotate View Co&unterclockwise rodar a vista no se&ntido contrário aos ponteiros - + Game &information &Informação do jogo - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede - - - + + + &Concede &Conceder - + &Leave game Sair do &jogo - + C&lose replay &Fechar replay - + &Focus Chat - + &Say: &Dizer: - + Selected cards - + &View &Vista - + Visible Visível - + Floating Flutuando - + Reset layout Repor disposição - + Concede Conceder - + Are you sure you want to concede this game? Tem a certeza que deseja conceder este jogo? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game Sair do jogo - + Are you sure you want to leave this game? Tem a certeza que deseja sair deste jogo? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. Foi expulso do jogo. @@ -8834,158 +11290,158 @@ Por favor introduza um nome: TabLog - + Logs Registos - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Tempo;NomeEnviador;IPEnviador;Mensagem;IDAlvo;NomeAlvo - + Room Logs Registos das salas - + Game Logs Registos dos jogos - + Chat Logs Registos das conversas - - + + Error Erro - + You must select at least one filter. Deve seleccionar pelo menos um filtro - + You have to select a valid number of days to locate. Deve seleccionar um número válido de dias para localizar. - + Username: Nome de utilizador: - + IP Address: Morada IP: - + Game Name: Nome do Jogo: - + GameID: ID do Jogo - + Message: Mensagem: - + Main Room Sala principal - + Game Room Sala de Jogo - + Private Chat Chat Privado - + Past X Days: Últimos X Dias: - + Today Hoje - + Last Hour Última Hora - + Maximum Results: Resultados Máximos: - + At least one filter is required. The more information you put in, the more specific your results will be. É requerido pelo menos um filtro. %s Quanta mais informação der, mais específicos serão os resultados. - + Get User Logs Obter registos dos utilizadores - + Clear Filters Limpar Filtros - + Filters Filtros: - + Log Locations Localização dos Registos - + Date Range Alcance de dados - + Maximum Results Resultados máximos - - + + Message History Histórico de mensagens - + Failed to collect message history information. Falha na obtenção do histórico de mensagens - + There are no messages for the selected filters. Não há mensagens para os filtros seleccionados @@ -8993,218 +11449,558 @@ Quanta mais informação der, mais específicos serão os resultados. TabMessage - + Private &chat Chat &privado - + &Leave &Abandonar - + %1 - Private chat %1 - Chat privado - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from - + %1 has left the server. %1 abandonou o servidor. - + %1 has joined the server. %1 entrou no servidor. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system Ficheiros locais - + Server replay storage Armazenamento de replays no servidor - - + + Watch replay Ver replay - + Rename - - + + New folder - - + + Delete Apagar - + Open replays folder - + Download replay Download replay - + Toggle expiration lock Alternar bloqueio de expiração - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file Apagar ficheiro local - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay Apagar replay remoto @@ -9214,65 +12010,478 @@ Quanta mais informação der, mais específicos serão os resultados. + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - - Friends - - - - - Online - - - - - Ignored - - - - + &Say: &Dizer: - + Chat Chat - + &Room &Sala - + &Leave room &Abandonar a sala - + &Clear chat &Limpar chat - + Chat Settings... Definições do chat... - + mentioned you. mencionou-o. - + Click to view Clicar para ver - + You are flooding the chat. Please wait a couple of seconds. Estás a inundar o chat .Por favor aguarde alguns segundos. @@ -9280,35 +12489,35 @@ Quanta mais informação der, mais específicos serão os resultados. TabServer - + Server Servidor - - - - + + + + Error Erro - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -9316,97 +12525,173 @@ Quanta mais informação der, mais específicos serão os resultados. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - + Archidekt - + Home - + &Visual Deck Storage - + Visual Database Display - + Server - + Account - + Deck Storage - + Game Replays - + Administration - + Logs - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? Tem a certeza? - + There are still open games. Are you sure you want to quit? Ainda estão a decorrer alguns jogos. Tem a certeza que quer sair? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -9414,39 +12699,39 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion Promoção - + You have been promoted. Please log out and back in for changes to take effect. - + Warned Avisado - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Recebeu um aviso devido a %1. %s Por favor evite continuar essa actividade ou acções serão tomadas contra si futuramente. Se tem alguma questão, por favor envie uma mensagem privada a um moderador. - + You have received the following message from the server. (custom messages like these could be untranslated) @@ -9465,6 +12750,34 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -9479,6 +12792,65 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f Incapaz de analisar o baralho. + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -9548,57 +12920,52 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f UserCardArtSettingsDialog - + Card Art Settings - + Type a card name... - + Card name: - - Card ProviderId: + + Printing: - + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + Left margin (%): - + Right margin (%): - - Vertical offset: - - - - - Zoom: - - - - - Parameters - - - - - Preview - - - - + Remove Banner Card @@ -9606,216 +12973,266 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f UserContextMenu - + User &details Detalhes do &utilizador - + Private &chat Chat &privado - + Show this user's &games Mostrar os &jogos deste utilizador - + + View this user's &public decks + + + + Add to &buddy list Adicionar à &lista de amigos - + Remove from &buddy list Remover da lista de &amigos - + Add to &ignore list Adicionar a lista a i&gnorar - + Remove from &ignore list Remover da lista a &ignorar - + Kick from &game Expulsar do &jogo - - - Warn user - Avisar utilizador - - - - View user's war&n history - Ver o histórico de avi&sos do utilizador - - - - Ban from &server - Banir do &servidor - - - - View user's &ban history - Ver o histórico de &bans do utilizador - - - - &Promote user to moderator - &Promover utilizador a moderador - - - - Dem&ote user from moderator - &Despromover utilizador do cargo de moderador - - - - Promote user to &judge - - - Demote user from judge + Report user + Warn user + Avisar utilizador + + + + View user's war&n history + Ver o histórico de avi&sos do utilizador + + + + Ban from &server + Banir do &servidor + + + + View user's &ban history + Ver o histórico de &bans do utilizador + + + + &Promote user to moderator + &Promover utilizador a moderador + + + + Dem&ote user from moderator + &Despromover utilizador do cargo de moderador + + + + Promote user to &judge + + + + + Demote user from judge + + + + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes - - + Investigate user + + + + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games jogos de %1 - - - + + + Ban History Histórico de banições - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Hora de Banição; Moderador; Duração da Banição; Razão da Banição; Razão visível - + User has never been banned. O utilizador nunca foi banido. - + Failed to collect ban information. - - - + + + Warning History Histórico de avisos - + Warning Time;Moderator;User Name;Reason Hora do aviso; Moderador; Nome do Utilizador; Razão - + User has never been warned. O utilizador nunca foi avisado. - + Failed to collect warning information. - + Failed to get admin notes. - - + + Success Sucesso - + Successfully promoted user. Utilizador promovido com sucesso. - + Successfully demoted user. Utilizador despromovido com sucesso. - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + Kick Player - + Are you sure you want to kick this player from the game? - - - + + + Failed Falhou - + Failed to promote user. Falha na promoção do utilizador. - + Failed to demote user. Falha na despromoção do utilizador. - + Copy hash to clipboard - + Remove this user's messages @@ -9853,61 +13270,61 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f Administrador - + Moderator Moderador - + Registered user Utilizador registado - - + + Unregistered user Utilizador não registado - + Judge - + Unknown Desconhecido - + The entered password does not match your account. - - - - + + + + Information Informação - + User information updated. Informação do utilizador actualizada. - - - - - - + + + + + - - - + + + + Error Erro @@ -9931,283 +13348,292 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f Edit Banner Card + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + The selected card is blacklisted on this server or another error occurred. - + Banner card removed. - + Banner card updated. - + This server does not permit you to update your user informations. Este servidor não permite a actualização da sua informação pessoal. - + Password changed. Password alterada. - + This server does not permit you to change your password. Este servidor não permite a actualização da sua password. - + The new password is too short. A password nova é demasiado curta. - + The old password is incorrect. A password antiga está incorrecta. - + Avatar updated. Avatar actualizado. - + This server does not permit you to update your avatar. Este servidor não permite a actualização do seu avatar. - + An error occured while trying to update your avatar. - - - This server does not permit you to update your user informations. - An error occured while trying to updater your avatar. - Este servidor não permite a actualização da sua informação pessoal. - UserInfoPopup - - + + Games - + Chat - + Open private chat - + Profile - + View user profile - + Show this user's games - + + Invite + + + + + Invite to your game + + + + − Buddy - + Remove from buddy list - + + Buddy - + Add to buddy list - + − Ignore - + Remove from ignore list - + + Ignore - + Add to ignore list - + Ban - + Ban from server - + Warn - + Warn user - + Ban log - + View ban history - + Warn log - + View warning history - + Notes - + View admin notes - + − Mod - + Demote from moderator - + + Mod - + Promote to moderator - + − Judge - + Demote from judge - + + Judge - + Promote to judge - + Join game - + Spectate - - + + Loading games… - + Could not load games. - + No active games. @@ -10215,223 +13641,344 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f UserInterfaceSettingsPage - + General interface settings Configurações gerais da interface - + &Double-click cards to play them (instead of single-click) Clicar &duas vezes nas cartas para as jogar (ao invés de clicar apenas uma vez) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default &Jogar todos os não-terrenos para a pilha (em vez de para o campo de batalha) por definição - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens Permitir anotações em tokens - + Show selection count during drag selection - + Show total selection count - - Show subtype breakdown in selection tally + + + Disabled - + + + Enabled + + + + + + Automatic + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + Use tear-off menus, allowing right click menus to persist on screen - + Keep game chat focused when clicking in game (Note: disables card view search bar) - + Notifications settings - + Enable notifications in taskbar Permitir notificações na barra de tarefas - + Notify in the taskbar for game events while you are spectating Notificar eventos de jogos na barra de tarefas - + Notify in the taskbar when users in your buddy list connect - + Animation settings Configurações de Animações - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation Animação de &virar/desvirar - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings - + Open deck in new tab by default - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - - do nothing + + Do nothing - - ask to convert to .cod + + Ask to convert to .cod - - always convert to .cod + + Always convert to .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings - + Buffer time for backwards skip via shortcut: + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + Flip coin - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -10468,22 +14015,22 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... - + Save Filter - + Save all currently applied filters to a file - + Enter filename... @@ -10491,72 +14038,72 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDatabaseDisplayFilterToolbarWidget - + Sort by - + Filter by - + Save and load filters - + Filter by exact card name - + Filter by card main-type - + Filter by card sub-type - + Filter by set - + Filter by format legality - + Save/Load - + Name - + Main Type - + Sub Type - + Sets - + Formats @@ -10658,7 +14205,7 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -10666,19 +14213,19 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -10724,28 +14271,28 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - - + + Visual - + Loading database ... - + Clear all filters - + Table @@ -10753,43 +14300,43 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat @@ -10805,12 +14352,12 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -10818,17 +14365,17 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -10836,7 +14383,7 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -10844,52 +14391,57 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + Show Color Identity - + Show Tags On Deck Previews - + + Show Upload Time + + + + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -10897,7 +14449,7 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -10905,22 +14457,22 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -10928,17 +14480,22 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + + Select decks to share + + + + Visual Deck Storage Settings @@ -10946,177 +14503,202 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f WarningDialog - + Which warning would you like to send? Que aviso gostaria de enviar? - + Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Cancelar - + Warn user for misconduct Aviso por má conduta - - + + Error Erro - + User name to send a warning to can not be blank, please specify a user to warn. O nome de utilizador não se pode encontrar vazio, por favor especifique o utilizador que deseja avisar. - + Warning to use can not be blank, please select a valid warning to send. O aviso a enviar não se pode encontrar vazio, por favor seleccione um aviso válido para enviar. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top Mover expansão seleccionada para o topo - + Move selected set up Mover expansão seleccionada para cima - + Move selected set down Mover expansão seleccionada para baixo - + Move selected set to the bottom Mover expansão seleccionada para o fundo - - Search by set name, code, or type - - - - + Default order - + Restore original art priority order - + Enable all sets Permitir todas as expansões - + Disable all sets Inactivar todas as expansões - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - - How to use custom card art + + Search by set name, code, type, or release date + How to use custom card art + + + + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -11124,93 +14706,85 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing baralhar quando terminar - + pile view vista em pilha - - i18n - - - English - Português (Portuguese) - - main - + Connect on startup - + Debug to file @@ -11994,6 +15568,16 @@ Por favor evite continuar essa actividade ou acções serão tomadas contra si f Graveyard Cemitério + + + Report Queue + + + + + Moderation + + diff --git a/cockatrice/translations/cockatrice_pt_BR.ts b/cockatrice/translations/cockatrice_pt_BR.ts index 422ed5a4b..9d81f631c 100644 --- a/cockatrice/translations/cockatrice_pt_BR.ts +++ b/cockatrice/translations/cockatrice_pt_BR.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Definir marcador... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Definir marcador - + New value for counter '%1': Novo valor para marcador '%1': @@ -36,66 +36,115 @@ AbstractTabDeckEditor - + Open in new tab Abrir em uma nova aba - + Are you sure? Você tem certeza? - + The decklist has been modified. Do you want to save the changes? A lista foi modificada. Gostaria de salvar as alterações? - - - - - - + + + + + + Error Erro - + Could not open deck at %1 Não foi possível abrir o deck em %1 - + Could not save remote deck O deck não pode ser salvo remotamente. - - + + The deck could not be saved. Please check that the directory is writable and try again. O deck não pôde ser salvo. Por favor, verifique se a pasta não é somente leitura e tente novamente. - + Save deck Salvar deck - + + + Share deck + Compartilhar Deck + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. O deck não pôde ser salvo. - + There are no cards in your deck to be exported Não há cartas em seu deck a serem exportadas + + AccountSetupPage + + + Join a Server + Conectar ao Servidor + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + Crie uma conta... + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + AddAnalyticsPanelDialog @@ -107,12 +156,12 @@ Por favor, verifique se a pasta não é somente leitura e tente novamente. AdminNotesDialog - + Update Notes Notas de Atualização - + Admin Notes for %1 Notas do Administrador para %1 @@ -132,224 +181,310 @@ Por favor, verifique se a pasta não é somente leitura e tente novamente. Tokens - + Fichas AppearanceSettingsPage - + seconds segundos - + Error Erro - + Could not create themes directory at '%1'. Não foi possível criar o diretório de temas em '%1'. - + Theme settings Configurações de Tema - + Current theme: Tema atual: - + Open themes folder Abra a pasta de temas - + Home tab background source: - Fonte do background da tab Home: + Fonte da imagem de fundo da aba Início: - + Home tab background shuffle frequency: - Frequência de escolha aleatória de background da tab Home: + Frequência de escolha aleatória da imagem de fundo da aba Início: - + Disabled Desabilitado - + Menu settings Configurações do Menu - + Show keyboard shortcuts in right-click menus Mostrar atalhos de teclado nos menus de clique com o botão direito - + Show game filter toolbar above list in room tab Mostrar o filtro de jogos acima da lista da tab de sala - + Card rendering Renderização das cartas - + Display card names on cards having a picture Mostrar nome nas cartas que possuem imagem - + Auto-Rotate cards with sideways layout Rotacionar automaticamente cartas com layout horizontal - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Light - + Claro - + Dark - + Escuro - + + System + Sistema + + + + Show all playmats - + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + Active theme palette: + Paleta de tema ativa: + + + + Active theme style: - + + Qt widget style saved to this theme ("System" lets the application decide) + + + + Edit theme palette - + Editar paleta de tema - + Home tab settings - + Configurações da aba Início - + Display card name of background in bottom right - + + Dim the home tab background + + + + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + Styling settings - + Style user list - + Card printings - + Bump sets that the deck contains cards from to the top in the printing selector Priorizar conjuntos que o deck contém no topo do seletor de impressão - + Scale cards on mouse over Redimensionar cartas ao passar o mouse - + Use rounded card corners Usar cantos de cartas arredondados - + Card layout - + Minimum overlap percentage of cards on the stack and in vertical hand Porcentagem mínima de sobreposição de cartas na pilha e na mão vertical - + Maximum initial height for card view window: Altura inicial máxima para a janela de visualização de cartas: - - + + rows linhas - + Maximum expanded height for card view window: Altura expandida máxima para a janela de visualização de cartas: - + Card counters Marcadores de carta - + Counter %1 Marcador %1 - + Hand layout Organização da mão - + Display hand horizontally (wastes space) Mostrar a mão na horizontal (desperdiça espaço) - + Enable left justification Habilitada justificação à esquerda - + Table grid layout Organização do campo de batalha - + Invert vertical coordinate Inverter a coordenada vertical - + Minimum player count for multi-column layout: Número mínimo de jogadores para o layout de multi-colunas - + Maximum font size for information displayed on cards: Tamanho máximo da fonte para informações nas cartas: @@ -357,12 +492,12 @@ Por favor, verifique se a pasta não é somente leitura e tente novamente. ArchidektApiResponseDeckDisplayWidget - + Back to results - + Voltar para resultados - + Open Deck in Deck Editor Abrir no Editor de Decks @@ -388,112 +523,112 @@ Por favor, verifique se a pasta não é somente leitura e tente novamente. BanDialog - + ban &user name banir &usuário pelo nome - + ban &IP address banir endereço &IP - + ban client I&D banir cliente por I&D: - + Ban type Tipo de banimento - + &permanent ban &banir permanentemente - + &temporary ban &banir temporariamente - + &Days: &Dias: - + &Hours: &Horas: - + &Minutes: &Minutos: - + Duration of the ban Duração do banimento - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Por favor, entre com o motivo do banimento. Isto será visto somente por moderadores e não pode ser visto pela pessoa banida. - + Please enter the reason for the ban that will be visible to the banned person. Por favor, entre com o motivo para o banimento que será visível pela pessoa banida. - + Redact all messages from this user in all rooms Remover todas mensagens deste usuário em todas as salas - + &OK &OK - + &Cancel &Cancelar - + Ban user from server Banir usuário do servidor - - - - + + + + Error Erro - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Você deve selecionar um nome, IP, cliente ou alguma combinação desses três itens para banir. - + You must have a value in the name ban when selecting the name ban checkbox. Você têm que colocar um nome válido quando seleciona a caixa de banimento por nome. - + You must have a value in the ip ban when selecting the ip ban checkbox. Você têm que colocar um IP válido quando seleciona a caixa de banimento por IP. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Você têm que colocar um ID de cliente válido quando seleciona a caixa de banimento por ID de cliente. @@ -501,22 +636,22 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid BetaReleaseChannel - + Beta Beta - + No reply received from the release update server. Nenhuma resposta recebida do servidor de atualização. - + Invalid reply received from the release update server. Resposta inválida recebida do servidor de atualização. - + No reply received from the file update server. Nenhuma resposta recebida do servidor de atualização. @@ -524,96 +659,253 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid CardArtPreviewWidget - - No card selected + + Banner preview + + + + + + Zoom %1× + + + + + No card selected + Nenhuma carta selecionada + CardArtRulesModel - + Card - + Carta - + ProviderId - + Mode - + Modo - + Reason - + Razão CardDatabaseModel - + Name Nome - + Sets Expansões - + Mana cost Custo de mana - + Card type Tipo de carta - + P/T P/R - + Color(s) Cor(es) + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + CardDatabaseView - + Add to Deck - + Adicionar ao deck - + Add to Sideboard - - - - - Select Printing - + Adicionar ao Sideboard + Select Printing + Selecionar Impressão + + + + Add to Zone + + + + + Create &new zone... + + + + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards @@ -723,22 +1015,22 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid CardInfoFrameWidget - + Image Imagem - + Description Descrição - + Both Ambos - + View transformation Ver transformação @@ -746,22 +1038,22 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid CardInfoPictureWidget - + View related cards Visualizar cartas relacionadas - + Add card to deck Adicionar carta ao deck - + Mainboard Main - + Sideboard Side @@ -769,27 +1061,27 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid CardInfoTextWidget - + Name: Nome: - + Set: Coleção: - + Collector Number: Número de Colecionador: - + Related cards: Cartas relacionadas: - + Unknown card: Carta desconhecida: @@ -797,129 +1089,129 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid CardMenu - + Re&veal to... Revelar para... - + &All players Todos os &jogadores - + View related cards Visualizar cartas relacionadas - + Token: Ficha: - + All tokens Todas as fichas - + &Select All &Marque todos - + S&elect Row Selecionar Linha - + S&elect Column Selecionar Coluna - + &Play &Jogar - + &Hide &Ocultar - + Play &Face Down Jogue &virada para baixo - + &Tap / Untap Turn sideways or back again &Virar / Desvirar - + Skip &untapping - + T&urn Over Turn face up/face down &Virar - + &Peek at card face &Olhar a face da carta - + &Clone &Clonar - + Attac&h to card... Ane&xar na carta... - + Unattac&h De&sanexar - + &Draw arrow... &Desenhar seta... - + &Set annotation... Colocar &nota... - + Reduce life by power - + Ca&rd counters Marcadores de carta - + &Add counter (%1) &Adicionar marcador(%1) - + &Remove counter (%1) &Remover marcador (%1) - + &Set counters (%1)... &Definir marcadores (%1)... @@ -940,7 +1232,7 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid CardSizeWidget - + Card Size Tamanho da Carta @@ -1080,6 +1372,24 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid zona customizada '%2' de %1 + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser @@ -1109,280 +1419,382 @@ Isto será visto somente por moderadores e não pode ser visto pela pessoa banid + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + ConnectionController - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - - - - - - - - - - - + + + + + + + + + + - - + + + Error - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + Server timeout - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Information - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -1390,113 +1802,113 @@ To update your client, go to Help -> Check for Updates. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - + Requesting forgotten password to %1 as %2... - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. @@ -1546,7 +1958,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorCardDatabaseDockWidget - + Card Database Banco de Dados de Cartas @@ -1562,17 +1974,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) Pesquisar pelo nome da carta (ou expressões de busca) - + Add card to &maindeck Adicionar carta ao &baralho principal - + Add card to &sideboard Adicionar carta ao &sideboard @@ -1580,97 +1992,158 @@ To update your client, go to Help -> Check for Updates. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card Carta em Destaque - + Main Type - + Mana Cost - + Colors Cores - - Select Printing - Escolher Impressão + + Playmat + - + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + + Select Printing + Selecionar Impressão + + + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck Deck - + Deck &name: &Nome do Deck: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: &Comments: - + Group by: Agrupar por: - + Format: Formato: - + Hash: Hash: - + &Increment number &Aumentar número - + &Decrement number &Diminuir número - + &Remove row &Remover linha - + Swap card to/from sideboard Mover carta para/do Side @@ -1678,17 +2151,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorFilterDockWidget - + Filters Filtros - + &Clear all filters &Limpar todos os filtros - + Delete selected Apagar selecionados @@ -1696,114 +2169,119 @@ To update your client, go to Help -> Check for Updates. DeckEditorMenu - + &Deck Editor &Editor de Decks - + &New deck &Novo deck - + &Load deck... &Carregar deck... - + Load recent deck... Carregar deck recente... - + Clear Limpar - + &Save deck &Salvar deck - + Save deck &as... Salvar deck &como... - + + Share deck... + + + + Load deck from cl&ipboard... Carregar deck da área de &transferência... - + Edit deck in clipboard Editar deck na área de transferência - - + + Annotated Anotado - - + + Not Annotated Não anotado - + Save deck to clipboard Salvar deck para a área de transferência - + Annotated (No set info) Anotado (Sem informação de expansão) - + Not Annotated (No set info) Não Anotado (Sem informação de expansão) - + &Print deck... &Imprimir deck... - + Load deck from online service... Carregar baralho de serviço online... - + &Send deck to online service &Enviar deck para serviço online - + Create decklist (decklist.org) Criar decklist (decklist.org) - + Create decklist (decklist.xyz) Criar decklist (decklist.xyz) - + Analyze deck (deckstats.net) &Analisar deck (deckstats.net) - + Analyze deck (tappedout.net) &Analisar deck (tappedout.net) - + &Close &Fechar @@ -1811,7 +2289,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorPrintingSelectorDockWidget - + Printing Selector Seletor de Impressão @@ -1819,110 +2297,152 @@ To update your client, go to Help -> Check for Updates. DeckEditorSettingsPage - - + + Update Spoilers Atualizar Spoilers - + Success Sucesso - + Download URLs have been reset. As URLs de download foram redefinidas. - + Add URL Adicionar URL - - + + URL: URL: - - + + Edit URL Editar URL - + Add New URL Adicionar nova URL - + Remove URL Remover URL - + Updating... Atualizando... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path Selecione o caminho - + URL Download Priority Prioridade de download de URL - + Spoilers 'Spoilers' - + Download Spoilers Automatically Baixar 'spoilers' automaticamente - + Spoiler Location: Localização do 'spoiler': - + Last Change Última Alteração - + Spoilers download automatically on launch Baixar automaticamente 'spoilers' ao iniciar - + Press the button to manually update without relaunching Pressione o botão para atualizar manualmente sem reinicializar - + Do not close settings until manual update is complete Não feche as configurações até que a atualização manual termine - + Download card pictures on the fly Baixar imagens de cartas em tempo real - + How to add a custom URL Como adicionar uma URL customizada - + Reset Download URLs Resetar URLs de download @@ -1930,32 +2450,32 @@ To update your client, go to Help -> Check for Updates. DeckListHistoryManagerWidget - + Undo Desfazer - + Redo Refazer - + Undo/Redo history Histórico Desfazer/Refazer - + Click on an entry to revert to that point in the history. - + [redo] [refazer] - + [undo] [desfazer] @@ -1963,27 +2483,27 @@ To update your client, go to Help -> Check for Updates. DeckListModel - + Count Contagem - + Set Conjunto - + Number Número - + Provider ID ID do Provedor - + Card Card @@ -2004,17 +2524,22 @@ To update your client, go to Help -> Check for Updates. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match Modo: Correspondência Exata - + Mode: Includes Modo: Inclui - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -2022,7 +2547,7 @@ To update your client, go to Help -> Check for Updates. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... Editar tags ... @@ -2030,62 +2555,62 @@ To update your client, go to Help -> Check for Updates. DeckPreviewTagDialog - + Deck Tags Manager Gerenciador de Tags de Deck - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: Gerencie suas tags de deck. Marque ou desmarque as tags conforme necessário ou adicione novas: - + Add a new tag (e.g., Aggro️) Adicionar uma nova tag (ex.: Aggro) - + Add Tag Adicionar Tag - + Filter tags... Filtrar tags... - + OK OK - + Edit default tags Editar tags padrão - + Cancel Cancelar - + Invalid Input Entrada Inválida - + Tag name cannot be empty! O nome da tag não pode estar vazio! - + Duplicate Tag Tag Duplicada - + This tag already exists. Essa tag já existe. @@ -2093,94 +2618,109 @@ To update your client, go to Help -> Check for Updates. DeckPreviewWidget - + Banner Card Carta em Destaque - + Open in deck editor Abrir no editor de decks - + + Share deck... + + + + Edit Tags Editar Tags - + Rename Deck Renomear Deck - + Save Deck to Clipboard Salvar deck na área de transferência - + Annotated Anotado - + Annotated (No set info) Anotado (Sem informação de expansão) - + Not Annotated Não Anotado - + Not Annotated (No set info) Não Anotado (Sem informação de expansão) - + Rename File Renomear Arquivo - + Delete File Deletar Arquivo - + Set Banner Card Escolher Carta em Destaque - - + + New name: Novo nome: - - + + Rename deck + + + + + Rename file + + + + + Error Erro - + Rename failed Falha ao renomear - + Delete file Deletar Arquivo - + Are you sure you want to delete the selected file? Tem certeza de que deseja excluir o arquivo selecionado? - + Delete failed Falha ao deletar @@ -2188,57 +2728,102 @@ To update your client, go to Help -> Check for Updates. DeckStateManager - + Rename deck to "%1" from "%2" Renomear baralho de "%1" para "%2" - + Updated comments (was %1 chars, now %2 chars) Comentários atualizados (eram %1 caracteres, agora são %2 caracteres) - + Set banner card to %1 (%2) - + + Set playmat to %1 + + + + Tags changed Tags alteradas - + Set format to %1 Definir formato para %1 - + Added (%1): %2 (%3) %4 Adicionado (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) Movido para %1 1 × "%2" (%3) - + Removed "%1" (all copies) Removido "%1" (todas as cópias) - + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + %1 1 × "%2" (%3) %1 1 × "%2" (%3) - + Added Adicionado - + Removed Removido @@ -2260,88 +2845,116 @@ To update your client, go to Help -> Check for Updates. DeckViewContainer - + Load deck... Carregar deck... - + Load remote deck... Carregar deck remoto... - + Load from clipboard... Carregado da área de transferência... - + Load from website... Carregado do website... - + Unload deck Descarregar deck - + Ready to start Preparadx para começar - + Force start Forçar início - + Sideboard unlocked Sideboard aberto - + Sideboard locked Sideboard fechado - - + + Error Erro - + The selected file could not be loaded. O arquivo selecionado não pode ser carregado - + Deck is greater than maximum file size. O baralho é maior que o tamanho máximo de arquivo. - + Are you sure you want to force start? This will kick all non-ready players from the game. Tem certeza que deseja forçar o início? Isso irá expulsar todos os jogadores não preparados do jogo. - + Cockatrice Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion Converter formato do deck - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? @@ -2350,7 +2963,7 @@ Tags só podem ser adicionadas a decks no formato .cod. Deseja converter o deck para .cod? - + Remember and automatically apply choice in the future Lembrar e aplicar essa escolha automaticamente no futuro @@ -2363,143 +2976,143 @@ Deseja converter o deck para .cod? Baixando... - + Known Hosts Servidores conhecidos - + Delete the currently selected saved server Excluir o servidor salvo atualmente selecionado - + Refresh the server list with known public servers Atualizar a lista de servidores com servidores públicos conhecidos - + New Host Novo servidor - + Name: Nome: - + &Host: &Servidor: - + &Port: &Porta: - + Player &name: &Nome do jogador: - + P&assword: S&enha: - + &Save password &Salvar senha - + A&uto connect &Conectar automaricamente - + Automatically connect to the most recent login when Cockatrice opens Conectar automaticamente com o login mais recente quando Cockatrice for iniciado - + If you have any trouble connecting or registering then contact the server staff for help! Se você tiver problemas ao se conectar ou registar entre em contato com os responsáveis pelo servidor! - - + + Webpage Página da Web - + Reset Password Resetar Senha - + Forgot password? Esqueceu a senha? - + &Connect &Conectar - + Server Servidor - + Login Login - + Server Contact Salvar Contato - + Connect to Server Conectar ao Servidor - + Server URL URL do Servidor - + Communication Port Porta de comunicação - + Unique Server Name Nome Único de Servidor - + Connection Warning Aviso de conexão - + You need to name your new connection profile. Você deve nomear seu novo perfil de conexão - + Connect Warning Aviso de conexão - + The player name can't be empty. O nome do jogador é obrigatório. @@ -2507,122 +3120,122 @@ Deseja converter o deck para .cod? DlgCreateGame - + Re&member settings Le&mbrar configurações. - + &Description: &Descrição: - + P&layers: &Jogadores: - + General Geral - + Game type Tipo de jogo - + &Password: &Senha: - + Only &buddies can join Apenas &amigos podem entrar - + Only &registered users can join Apenas usuários &registrados podem entrar - + Joining restrictions Restrições para entrar - + &Spectators can watch &Espectadores podem assistir - + Spectators &need a password to watch &Espectadores precisam de uma senha para assistir - + Spectators can &chat Visitantes podem &conversar - + Spectators can see &hands Espectadores podem ver &mãos - + Create game as spectator Criar jogo como espectador - + Spectators Visitantes - + Starting life total: Total de vida inicial: - + Open decklists in lobby Abrir lista de baralhos no lobby - + Create game as judge Criar jogo como juíz - + Game setup options Opções de configuração do jogo - + &Clear &Limpar - + Create game Criar jogo - + Game information Informação de jogo - + Error Erro - + Server error. Erro do servidor. @@ -2630,97 +3243,97 @@ Deseja converter o deck para .cod? DlgCreateToken - + &Name: &Nome: - + Token Ficha - + C&olor: C&or: - + white branco - + blue azul - + black preto - + red vermelho - + green verde - + multicolor multicolorido - + colorless incolor - + &P/T: &P/R: - + &Annotation: &Nota: - + &Destroy token when it leaves the table Destruir a &ficha quando ela sair do campo de batalha - + Create face-down (Only hides name) - + Token data Informação de ficha - + Show &all tokens Mostre &todas fichas - + Show tokens from this &deck Mostre fichas deste &deck - + Choose token from list Mostre fichas da lista - + Create token Criar ficha @@ -2728,53 +3341,53 @@ Deseja converter o deck para .cod? DlgDefaultTagsEditor - + Edit Tags Editar Tags - + Add Adicionar - + Confirm Confirmar - + Cancel Cancelar - + Enter a tag and press Enter Insira a tag e aperte Enter - - + + ✖ ✖ - + Invalid Input Valor Inválido - + Tag name cannot be empty! Nomes de tags não podem ser vazios! - + Duplicate Tag Tag Duplicada - + This tag already exists. Essa tag já existe. @@ -2783,7 +3396,7 @@ Deseja converter o deck para .cod? DlgEditAvatar - + No image chosen. Nenhuma imagem selecionada. @@ -2795,27 +3408,32 @@ To remove your current avatar, confirm without choosing a new image. Para remover o avatar atual, confirme sem escolher uma nova imagem. - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... Procurar... - + Change avatar Mudar avatar - + Open Image Abrir imagem - + Image Files (*.png *.jpg *.bmp) Arquivos de imagem (*.png *.jpg *.bmp) - + Invalid image chosen. Imagem inválida. @@ -2841,38 +3459,38 @@ Para remover o avatar atual, confirme sem escolher uma nova imagem. DlgEditPassword - + Old password: Senha antiga: - + New password: Nova senha: - + Confirm new password: Confirmar senha: - + Change password Trocar senha: - - + + Error Erro - + Your password is too short. Sua senha é muito curta. - + The new passwords don't match. As novas senhas não coincidem. @@ -2880,93 +3498,93 @@ Para remover o avatar atual, confirme sem escolher uma nova imagem. DlgEditTokens - + &Name: &Nome: - + C&olor: &Cor: - + white branco - + blue azul - + black preto - + red vermelho - + green verde - + multicolor multicor - + colorless incolor - + &P/T: &P/R: - + &Annotation: &Anotação - + Token data Informação da ficha - - + + Add token Adicionar ficha - + Remove token Remover ficha - + Edit custom tokens Editar fichas personalizadas - + Please enter the name of the token: Por favor, entre com o nome da ficha: - + Error Erro - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. O nome escolhido está em conflito com uma carta ou ficha existente. @@ -3045,8 +3663,9 @@ Certifique-se de habilitar a expansão 'Fichas' em "Gerenciar exp - Hide games that have started - Ocultar jogos já iniciados + Hide started games + Hide games that have started + @@ -3081,8 +3700,9 @@ Certifique-se de habilitar a expansão 'Fichas' em "Gerenciar exp - &Creator name: - Nome do &criador: + &Host name: + &Creator name: + @@ -3148,52 +3768,52 @@ Certifique-se de habilitar a expansão 'Fichas' em "Gerenciar exp DlgForgotPasswordChallenge - + Reset Password Challenge Warning Aviso de redefinição de desafio de senha - + A problem has occurred. Please try to request a new password again. Houve um problema. Por favor, tente solicitar uma nova senha novamente. - + Enter the information of the server and the account you'd like to request a new password for. Coloque a informação do servidor e a conta que você gostaria de solicitar uma nova senha. - + &Host: &Servidor: - + &Port: &Porta: - + Player &name: Jogador &nome - + Email: Email: - + Reset Password Challenge Desafio de redefinir senha - + Reset Password Challenge Error Erro no desafio de redefinir senha - + The email address can't be empty. O endereço de email não pode estar vazio. @@ -3201,37 +3821,37 @@ Certifique-se de habilitar a expansão 'Fichas' em "Gerenciar exp DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. Coloque a informação do servidor que você gostaria de solicitar uma nova senha. - + &Host: &Servidor: - + &Port: &Porta: - + Player &name: Nome do &jogador - + Reset Password Request Pedido de redefinição de senha. - + Reset Password Error Erro de redefinição de senha - + The player name can't be empty. O nome do jogador não pode ficar vazio. @@ -3239,94 +3859,127 @@ Certifique-se de habilitar a expansão 'Fichas' em "Gerenciar exp DlgForgotPasswordReset - + Reset Password Warning Aviso de Redefinição de Senha - + A problem has occurred. Please try to request a new password again. Um problema ocorreu. Por favor tente requisitar uma nova senha novamente. - + Enter the received token and the new password in order to set your new password. Insira o código recebido e a nova senha para definir sua nova senha. - + &Host: &Servidor: - + &Port: &Porta: - + Player &name: Jogador &nome: - + Token: Ficha: - - + + New Password: Nova senha: - + Reset Password Redefinir Senha - + The player name can't be empty. O nome do jogador não pode estar vazio. - - - - + + + + Reset Password Error Erro de Redefinição de Senha - + The token can't be empty. O código não pode estar vazia. - + The new password can't be empty. A nova senha não pode estar vazia. - + Error Erro - + Your password is too short. Sua senha é muito curta. - + The passwords do not match. As senhas não correspondem. + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck Carregar Deck @@ -3410,166 +4063,567 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLocalGameOptions - + Players: Jogadores: - + General Geral - + Starting life total: Vida inicial total: - + Game setup options - + Remember settings Lembrar configurações - + Local game options Opções de jogo local + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): Nome da carta (ou expressões de busca): - + Number of hits: Número de resultados: - + Auto play hits Reproduzir resultados automaticamente - + Put top cards on stack until... Colocar as cartas do topo na pilha até... - + No cards matching the search expression exists in the card database. Proceed anyways? Nenhuma carta correspondente à expressão de busca existe no banco de dados de cartas. Deseja continuar assim mesmo? - + Cockatrice Cockatrice - + Invalid filter Filtro inválido + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. Insira seus dados e os do servidor que você gostaria de se registrar. Seu e-mail será utilizado para verificar sua conta. - + &Host: &Servidor: - + &Port: &Porta: - + Player &name: Nome do &jogador: - + P&assword: S&enha: - + Password (again): Senha (novamente): - + Email: Email: - + Email (again): Email (novamente): - + Country: País: - + Undefined Indefinido - + Real name: Nome real: - + Register to server Registrar ao servidor: - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning Aviso de Registro - + Your password is too short. Sua senha é muito curta. - + Your passwords do not match, please try again. Suas senhas não coincidem, por favor tente novamente. - + Your email addresses do not match, please try again. Seus endereços de email não coincidem, por favor tente novamente. - + The player name can't be empty. O nome de jogador não pode estar vazio. + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice @@ -3635,46 +4689,12 @@ Seu e-mail será utilizado para verificar sua conta. DlgSettings - + Unknown Error loading card database Erro desconhecido ao carregar bando de dados de cartas - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - Seu banco de dados de cartas está inválido. - -Cockatrice pode não funcionar corretamente com um banco de dados inválido. - -Você pode precisar rodar o Oracle novamente para atualizar o banco de dados de cartas. - -Você gostaria de mudar o local da configuração de seu banco de dados? - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - A versão de seu banco de dados de cartas é muito velho. - -Isto pode causar problemas em carregar informações de cartas ou imagens. - -Usualmente isto pode ser consertado rodando novamente o Oracle para atualizar seu banco de dados de cartas. - -Você gostaria de alterar o local da configuração de seu banco de dados? - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3687,7 +4707,7 @@ Por favor, envie um ticket para https://github.com/Cockatrice/Cockatrice/issues Gostaria de mudar a definição da localização da sua base de dados? - + File Error loading your card database. Would you like to change your database location setting? @@ -3696,7 +4716,7 @@ Would you like to change your database location setting? Você gostaria de alterar seu local de configuração de seu banco de dados? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3705,7 +4725,7 @@ Would you like to change your database location setting? Você gostaria de alterar seu local de configuração de banco de dados? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3718,104 +4738,235 @@ Por favor, crie um ticket em https://github.com/Cockatrice/Cockatrice/issues Você gostaria de alterar seu local de configuração do banco de dados? - - - + + + Error Erro - + The path to your deck directory is invalid. Would you like to go back and set the correct path? O caminho para a sua pasta de decks é inválido. Você gostaria de voltar e corrigir o caminho? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? O caminho para a sua pasta de imagens de cards é inválido. Você gostaria de voltar e corrigir o caminho? - + Settings Configurações - + + Search settings... + + + + + OK + + + + General Geral - + Appearance Aparência - + User Interface Interface do usuário - + Storage - + Card Sources Fonte de Cartas - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat Conversa - + Sound Som - + Shortcuts Atalhos + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check Checar Atualização de Carta - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time Não executar desta vez @@ -3823,17 +4974,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next Próximo - + Previous Anterior - + Tip of the Day Dica do Dia @@ -3841,165 +4992,175 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel Canal de lançamento atual - + Reinstall Reinstalar - + Cancel Download Cancelar Download - + Open Download Page Abrir página de download - + Check for Client Updates Procurar por atualizações do cliente - - - - + + + + Error Erro - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice não possui suporte a SSL, por causa disso você não pode baixar updates automaticamente! Por favor visite a página de downloads para atualizar manualmente.  - + Downloading update: %1 Baixando atualização: %1 - + Checking for updates... Verificando atualizações... - + Finished checking for updates Busca por atualizações concluída - + No Update Available Sem atualização disponível - + Cockatrice is up to date! Cockatrice está atualizado! - + You are already running the latest version available in the chosen release channel. Você já está executando a última versão disponível no canal de lançamento escolhido. - + Current version Versão atual - + Selected release channel Canal de lançamento selecionado - - + + Update Available Atualização disponível - - + + A new version of Cockatrice is available! Uma nova versão do Cockatrice está disponível! - - + + New version Nova versão - - + + Released Lançado - - + + Changelog Log de mudanças - + Do you want to update now? Você quer atualizar agora? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. Infelizmente, o atualizador automática não encontrou um download compatível. Talvez você tenha que baixar a nova versão manualmente. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. Por favor confira a <a href="%1">página de releases</a> no nosso repositório do Github e baixe o programa para o seu sistema. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error Erro na atualização - + An error occurred while checking for updates: Ocorreu um erro enquanto procurávamos por atualizações: - + An error occurred while downloading an update: Um erro ocorreu enquanto uma atualização era baixada: - + Installing... Instalando... - + Cockatrice is unable to open the installer. Cockatrice está incapaz de abrir o instalador. - + Try to update manually by closing Cockatrice and running the installer. Tente atualizar o Cockatrice manualmente fechando-o e rodando o instalador. - + Download location Localização do download @@ -4007,17 +5168,17 @@ Talvez você tenha que baixar a nova versão manualmente. DlgViewLog - + Clear log when closing Limpar log ao fechar - + Copy to clipboard Copiar para área de transferência - + Debug Log Registro de Depuração @@ -4255,7 +5416,7 @@ Talvez você tenha que baixar a nova versão manualmente. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: Sal: @@ -4263,7 +5424,7 @@ Talvez você tenha que baixar a nova versão manualmente. FilterBuilder - + Type your filter here Digite seu filtro aqui @@ -4271,45 +5432,88 @@ Talvez você tenha que baixar a nova versão manualmente. FilterDisplayWidget - + Confirm Delete Confirmar Exclusão - + Are you sure you want to delete the filter '%1'? - + Delete Failed Falha na Exclusão - + Failed to delete filter '%1'. Falha ao remover filtro '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator - + player left the game jogador saiu do jogo - + player disconnected from server jogador desconectado do servidor - + reason unknown motivo desconhecido @@ -4317,140 +5521,156 @@ Talvez você tenha que baixar a nova versão manualmente. GameSelector - - - - - - - - - + + + + + + + + + Error Erro - + Please join the appropriate room first. Por favor, entre na sala apropriada primeiramente - + Wrong password. Senha incorreta. - + Spectators are not allowed in this game. Não são permitidos visitantes neste jogo. - + The game is already full. O jogo está cheio. - + The game does not exist any more. O jogo não existe mais. - + This game is only open to registered users. Este jogo é aberto apenas para usuários registrados. - + This game is only open to its creator's buddies. Este jogo é aberto apenas para os amigos de quem criou o jogo. - + You are being ignored by the creator of this game. Você está sendo ignorado pelo criador deste jogo. - + Join Game Entrar no jogo - + Spectate Game Assistir ao jogo - + Game Information Informações do jogo - + + Cop&y game link + + + + Join Game as Judge Entrar no Jogo como Juíz - + Spectate Game as Judge - + + Join game Entrar no jogo - - Password: - Senha: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. Por favor, entre na respectiva sala primeiro. - + &Filter games &Filtrar jogos. - + C&lear filter L&impar Filtros - + C&reate C&riar - + &Join &Entrar - + Join as judge - + J&oin as spectator E&ntrar como visitante - + Join as judge spectator - + Games shown: %1 / %2 Jogos mostrados: %1 / %2 - + Games Jogos @@ -4491,105 +5711,105 @@ Talvez você tenha que baixar a nova versão manualmente. GamesModel - + >1 day >1 dia - + %1%2 hr short age in hours %1%2 h%1%2 h%1%2 h - + new novo - + %1%2 min short age in minutes %1%2 min%1%2 min%1%2 min - + password senha - + buddies only apenas amigos - + reg. users only usuários reg. apenas - + open decklists - - + + can chat pode conversar - + see hands ver mãos - + can see hands pode ver mãos - + not allowed não permitidos - + Room Sala - + Age Idade - + Description Descrição - - Creator - Criador + + Host + - + Type Tipo - + Restrictions Restrições - + Players Jogadores - + Spectators Visitantes @@ -4597,158 +5817,275 @@ Talvez você tenha que baixar a nova versão manualmente. GeneralSettingsPage - + Reset all paths Redefinir todos os diretórios - + All paths have been reset Todos os caminhos para diretórios foram redefinidos - - - - - - - + + + + English + Português do Brasil (Brazilian Portuguese) + + + + + + + + + Choose path Escolher caminho - + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> + + + + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + Language settings - + Language: Idioma: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + Version settings - + Card database - + Startup settings - + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) Caminhos (edição desabilitada no modo portátil) - + Paths Caminhos - + How to help with translations Como ajudar com as traduções - + Decks directory: Pasta de decks: - + Filters directory: - + Replays directory: Pasta de replays: - + Pictures directory: Pasta de imagens: - + Card database: Banco de dados de cartas: - + Custom database directory: Diretório do banco de dados de cartas customizadas - + Token database: Banco de dados de fichas: - + Update channel Canal de atualização - + Check for client updates on startup Verificar atualizações do cliente ao iniciar - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client Notificar se uma função suportada pelo servidor está faltando em meu programa. - + Automatically run Oracle when running a new version of Cockatrice Rodar Oracle automaticamente quando rodar uma nova versão de Cockatrice - + Show tips on startup Exibir dicas ao iniciar - + Last update check on %1 (%2 days ago) @@ -4756,47 +6093,47 @@ Talvez você tenha que baixar a nova versão manualmente. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4890,59 +6227,353 @@ Talvez você tenha que baixar a nova versão manualmente. + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu @@ -5160,273 +6791,294 @@ Talvez você tenha que baixar a nova versão manualmente. MainWindow - - + + Player %1 Jogador %1 - + Load replay Carregar replay - + About Cockatrice Sobre o Cockatrice - + Version Versão - + Cockatrice Webpage Pagina da Web do Cockatrice - + Project Manager: Diretor do projeto: - + Past Project Managers: Antigos diretores do projeto: - + Developers: Desenvolvedores: - + Our Developers Nossos desenvolvedores - + Help Develop! Ajude a desenvolver! - + Translators: Tradutores: - + Our Translators Nossos tradutores. - + Help Translate! Ajude a traduzir! - + Support: Suporte: - + Report an Issue Reporte um problema: - + Troubleshooting Solução de problemas - + F.A.Q. F.A.Q. - - + + Error Erro - + &Connect... &Conectar... - + &Disconnect &Desconectar - + Start &local game... Iniciar jogo &local... - + &Watch replay... &Assistir replay... - + &Full screen Tela &cheia - + &Register to server... &Registrar ao servidor: - + &Restore password... &Recuperar senha... - + &Settings... &Configurações... - + &Exit &Sair - + A&ctions Açõ&es - + &Cockatrice &Cockatrice - + C&ard Database Base de Cartas - + &Manage sets... &Gerenciar expansões... - + Edit custom &tokens... Editar &fichas personalizadas - + Open custom image folder Abrir pasta customizada de imagens. - + Open custom sets folder Abrir pasta de sets personalizados - + Add custom sets/cards Adicionar sets/cartas personalizadas - + Reload card database Recarregar banco de dados de cartas - + Tabs Abas - + &Help &Ajuda - + &About Cockatrice So&bre o Cockatrice - + &Tip of the Day &Dica do Dia - + Check for Client Updates Procurar por atualizações do cliente - + Check for Card Updates... Procurar por atualizações de cartas... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log Ver registro de &depuração - + Open Settings Folder Abrir Pasta de Configurações - + + Re-run Onboarding Wizard... + + + + Show/Hide Mostrar/Ocultar - + + Sign-in cancelled + + + + New Version Nova Versão - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. Parabéns por atualizar o Cockatrice %1! Oracle será inicializado agora para atualizar sua base de cartas - + Cockatrice installed Cockatrice instalado - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. Parabéns pela instalação do Cockatrice %1! O Oracle agora será iniciado para instalar o banco de dados inicial de cartas. - + + Are you sure? + Você tem certeza? + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database Base de cartas - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5435,29 +7087,29 @@ Gostaria de atualizar seu banco de dados de cartas agora? Se estiver em dúvida ou for um novo usuário selecione "Sim" - - + + Yes Sim - - + + No Não - + Open settings Abrir configurações - + New sets found Novas expansões encontradas - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5470,22 +7122,22 @@ Códigos das coleções: %1 Deseja ativá-las? - + Yes, always enable - + View sets Visualizar expansões - + Welcome Bem vindo - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5494,89 +7146,89 @@ Todas as expansões do banco de dados de cartas foram habilitadas. Leia mais sobre mudar a ordem das expansões ou desabilitando expansões específicas na aba "Gerenciar Expansões". - + Information Informação - + A card database update is already running. Uma atualização da base de dados de cartas já está em andamento. - + Unable to run the card database updater: Não foi possível executar a atualização da base de dados de cartas. - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. Falha ao começar. O arquivo pode não existir ou as permissões estão incorretas. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - - - - - + + + + + Load sets/cards Carregar expansões/cards - + Selected file cannot be found. Arquivo selecionado não pode ser encontrado - + You can only import XML databases at this time. Você só pode importar bancos de dados XML neste momento. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. As novas cartas/coleções foram adicionadas com sucesso. O Cockatrice reiniciará imediatamente o banco de dados de cartas. - + Sets/cards failed to import. Falha ao importar expansões/cartas. @@ -5781,615 +7433,615 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. MessageLogWidget - + from play do jogo - + from their graveyard do seu cemitério - + from exile vindo do exílio - + from their hand da sua mão - + the top card of %1's library a carta do topo do grimório de %1 - + the top card of their library a carta do topo do seu grimório - + from the top of %1's library do topo do grimório de %1 - + from the top of their library do topo do seu grimório - + the bottom card of %1's library a carta do fundo do grimório de %1 - + the bottom card of their library a carta do fundo do seu grimório - + from the bottom of %1's library do fundo do grimório de %1 - + from the bottom of their library do fundo do seu grimório - + from %1's library do grimório de %1 - + from their library do seu grimório - + from sideboard vindo do sideboard - + from the stack vindo da pilha - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 está mantendo as carta do topo %2 reveladas. - + %1 is not revealing the top card %2 any longer. %1 não está mais revelando as cartas do topo %2. - + %1 can now look at top card %2 at any time. %1 agora pode olhar a carta do topo de %2 a qualquer momento. - + %1 no longer can look at top card %2 at any time. %1 não pode mais olhar a carta do topo de %2 a qualquer momento. - + %1 attaches %2 to %3's %4. %1 vincula %2 a %4 de %3. - + %1 has conceded the game. %1 concedeu o jogo. - + %1 has unconceded the game. %1 desfez a concessão do jogo - + %1 has restored connection to the game. %1 restaurou a conexão com o jogo. - + %1 has lost connection to the game. %1 perdeu conexão com o jogo. - + %1 points from their %2 to themselves. %1 aponta de sua %2 para si - + %1 points from their %2 to %3. %1 aponta de sua %2 para %3. - + %1 points from %2's %3 to themselves. %1 aponta da %3 de %2 para si - + %1 points from %2's %3 to %4. %1 aponta da %3 de %2 para %4 - + %1 points from their %2 to their %3. %1 aponta da sua %2 para sua %3 - + %1 points from their %2 to %3's %4. %1 aponta de sua %2 para %4 de %3 - + %1 points from %2's %3 to their own %4. %1 aponta da %3 de %2 para seu próprio %4. - + %1 points from %2's %3 to %4's %5. %1 aponta da %3 de %2 para a %5 de %4. - + %1 creates a face down token. - + %1 creates token: %2%3. %1 cria uma ficha: %2%3. - + %1 has loaded a deck (%2). %1 carregou um deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 carregou um deck com %2 cartas no sideboard (%3). - + %1 destroys %2. %1 destrói %2. - + a card um card - + %1 gives %2 control over %3. %1 dá controle para %2 sobre %3. - + %1 puts %2 into play%3 face down. %1 coloca %2 em jogo%3 com a face voltada para baixo. - + %1 puts %2 into play%3. %1 põe %2 no campo de batalha %3. - + %1 puts %2%3 into their graveyard face down. - + %1 puts %2%3 into their graveyard. %1 coloca %2%3 em seu cemitério. - + %1 exiles %2%3 face down. - + %1 exiles %2%3. %1 exila %2%3. - + %1 moves %2%3 to their hand. %1 moveu %2%3 para sua mão. - + %1 puts %2%3 into their library. %1 coloca %2%3 em seu grimório - + %1 puts %2%3 onto the bottom of their library. %1 coloca %2%3 no fundo de seu grimório. - + %1 puts %2%3 on top of their library. %1 coloca %2%3 no topo de seu grimório. - + %1 puts %2%3 into their library %4 cards from the top. %1 coloca %2%3 do seu grimório %4 cartas abaixo do topo. - + %1 moves %2%3 to sideboard. %1 move %2%3 para o sideboard. - + %1 plays %2%3 face down. - + %1 plays %2%3. %1 põe %2 na pilha%3. - + %1 moves %2%3 to custom zone '%4' face down. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1 tenta comprar de uma biblioteca vazia - + %1 draws %2 card(s). %1 comprou %2 carta(s).%1 comprou %2 carta(s).%1 comprou %2 carta(s). - + %1 is looking at %2. %1 está olhando para %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 está olhando para a(s) %3 carta(s) %4 %2.%1 está olhando para a(s) %3 carta(s) %4 %2.%1 está olhando para a(s) %3 carta(s) %4 %2. - + bottom de fundo - + top de topo - + %1 turns %2 face-down. %1 turnos %2 cartas viradas para baixo. - + %1 turns %2 face-up. %1 turnos %2 cartas viradas para cima. - + The game has been closed. O jogo foi fechado. - + The game has started. O jogo começou. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 entrou no jogo. - + %1 is now watching the game. %1 está assistindo o jogo agora. - + You have been kicked out of the game. Você foi 'chutado' do jogo. - + %1 has left the game (%2). %1 deixou o jogo (%2) - + %1 is not watching the game any more (%2). %1 não está mais assistindo o jogo (%2). - + %1 is not ready to start the game any more. %1 não está mais pronto para inciar o jogo. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 embaralhou seu grimório e comprou uma nova mão com %2 card.%1 embaralhou seu grimório e comprou uma nova mão com %2 cards.%1 embaralhou seu grimório e comprou uma nova mão com %2 cards. - + %1 shuffles their deck and draws a new hand. %1 embaralhou seu grimório e comprou uma nova mão. - + You are watching a replay of game #%1. Você está assistindo o replay do jogo #%1. - + %1 is ready to start the game. %1 está pronto para inciar o jogo. - + cards an unknown amount of cards cartas - + %1 card(s) a card for singular, %1 cards for plural %1 carta%1 carta(s)%1 carta(s) - + %1 lends %2 to %3. %1 empresta %2 para %3. - + %1 reveals %2 to %3. %1 revela %2 para %3. - + %1 reveals %2. %1 revela %2. - + %1 randomly reveals %2%3 to %4. %1 revela aleatoriamente %2%3 para %4. - + %1 randomly reveals %2%3. %1 revela aleatoriamente %2%3. - + %1 peeks at face down card #%2. %1 olha a carta virada para baixo #%2. - + %1 peeks at face down card #%2: %3. %1 espia a face para baixo da carta #%2: %3. - + %1 reveals %2%3 to %4. %1 revela %2 %3 para %4. - + %1 reveals %2%3. %1 revela %2%3. - + %1 reversed turn order, now it's %2. %1 reverteu a ordem dos turnos, agora é a vez de %2. - + reversed revertido - + normal normal - + Heads Cara - + Tails Coroa - + %1 flipped a coin. It landed as %2. %1 jogou uma moeda. Saiu %2. - + %1 rolls a %2 with a %3-sided die. %1 tirou %2 em um D%3. - + %1 flips %2 coins. There are %3 heads and %4 tails. %1 joga %2 moedas. São %3 caras e %4 coroas. - + %1 rolls a %2-sided dice %3 times: %4. %1 rola um dado de %2 lados %3 vezes: %4. - + %1's turn. Turno de %1. - + %1 sets annotation of %2 to %3. %1 altera a anotação de %2 para %3. - + %1 places %2 %3%4 counter(s) on %5 (now %6). - + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + %1 failed to undo their last draw. - + %1 sets counter %2 to %3 (%4%5). %1 ajusta o marcador %2 para %3 (%4%5). - + %1 sets %2 to not untap normally. %1 determina que %2 não desvira normalmente. - + %1 sets %2 to untap normally. %1 determina que %2 desvira normalmente. - + %1 removes the PT of %2. %1 remove o P/R de %2 - + %1 changes the PT of %2 from nothing to %4. %1 alterar o PR de %2 de nada para %4. - + %1 changes the PT of %2 from %3 to %4. %1 alterar o P/R de %2 de %3 para %4. - + %1 has locked their sideboard. %1 bloqueou seu sideboard - + %1 has unlocked their sideboard. %1 desbloqueou seu sideboard - + %1 taps their permanents. %1 vira suas permanentes. - + %1 untaps their permanents. %1 desvira suas permanentes. - + %1 taps %2. %1 vira %2 - + %1 untaps %2. %1 desvira %2. - + %1 shuffles %2. %1 embaralha %2 - + %1 shuffles the bottom %3 cards of %2. %1 embaralha as %3 cartas do fundo de %2 - + %1 shuffles the top %3 cards of %2. %1 embaralha as %3 cartas do topo de %2 - + %1 shuffles cards %3 - %4 of %2. %1 embaralha as cartas %3 - %4 de %2 - + %1 unattaches %2. %1 desanexa %2. - + %1 undoes their last draw. %1 desfaz sua última compra. - + %1 undoes their last draw (%2). %1 desfaz sua última compra (%2). @@ -6397,115 +8049,125 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. MessagesSettingsPage - + Word1 Word2 Word3 Palavra1 Palavra2 Palavra3 - + Add New Message Adicionar nova mensagem - + Edit Message Editar mensagem - + Remove Message Remover mensagem - + Add message Adicionar mensagem - - + + Message: Mensagem: - + Edit message Editar mensagem - + Chat settings Configurações de Conversa - + Custom alert words Palavras de alertas customizadas. - + Enable chat mentions Habilitar menções em conversa - + Enable mention completer Habilitar completador de menções. - + In-game message macros Mensagem no jogo - + How to use in-game message macros Como usar macros de mensagens no jogo - + Ignore chat room messages sent by unregistered users Ignorar mensagens na sala enviadas por usuários não registrados - + Ignore private messages sent by unregistered users Ignorar mensagens privadas enviadas por usuários não registrados - + Ignore private messages sent by non-buddy users - - + + Invert text color Inverter cor do texto - + Enable desktop notifications for private messages Ativar notificações da área de trabalho para mensagens privadas. - + Enable desktop notification for mentions Habilitar notificações de desktop para menções. - + Enable room message history on join Ativar histórico de mensagens da sala ao entrar - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) (Cor em hexadecimal) - + Separate words with a space, alphanumeric characters only Separar palavras com um espaço, apenas caracteres alfanuméricos @@ -6513,42 +8175,42 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. MoveMenu - + Move to - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + T&able - + &Hand - + &Graveyard - + &Exile @@ -6627,148 +8289,134 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. PaletteEditorDialog - - + + Reset - - - Apply - - - - - - Save && Apply - - - - + ▼ Edit Palette - - + + ▶ Edit Palette - + Palette Editor — %1 - + <b>Palette Editor</b> &nbsp;·&nbsp; %1 - - This theme ships no default palette files - - - - - Replace current colours with the theme author's defaults - - - - - Switch between the light and dark palette files + + + Save - Editing: + Delete this scheme's custom palette and revert to the theme default (or the application palette) - Show or hide the per-role colour grid for manual tweaks + Switch between the light and dark palette files + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + ↺ Revert to theme default - + Discard unsaved edits and restore the last saved palette - - Preview this palette without saving to disk + + Cannot save: this theme has no writable directory - + Write palette-%1.toml and reload the theme - - Cannot save: this theme has no directory on disk - - - - + + Save failed - + + Could not write %1 to: %2 - - - No default found - - - - - No default palette file found for the "%1" scheme. - - PaletteGridWidget - + Active - + Disabled - + Inactive - + Normal interactive state - + Widget is disabled / not interactive - + Window is in background / unfocused + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + Phase @@ -6891,30 +8539,21 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. Etapa de fim de combate - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - pt - - PlayerActions - - - - + + + + grave - - - - + + + + exile @@ -7033,16 +8672,272 @@ O Cockatrice reiniciará imediatamente o banco de dados de cartas. PlayerMenu - + Player "%1" - + &Counters + + PlaymatCollectionDialog + + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + PrintingDisabledInfoWidget @@ -7063,12 +8958,12 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelector - + Display Navigation Buttons - + Printing Selector @@ -7076,25 +8971,65 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardOverlayWidget - + Preference Preferência - + Pin Printing Fixar Impressão - + Unpin Printing Desafixar Impressão - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards Mostrar Cartas Relacionadas + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -7125,28 +9060,28 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardSortingWidget - + Alphabetical Alfabética - + Preference Preferência - + Release Date Data de Lançamento - - + + Descending Decrescente - + Ascending Crescente @@ -7162,95 +9097,103 @@ This setting means you'll only see the default printing for each card, instead o PtMenu - + Power / toughness - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services Serviços - + Hide %1 Esconder %1 - + Hide Others Esconder outros - + Show All Mostrar tudo - + Preferences... Preferências - + Quit %1 Sair %1 - + About %1 Sobre %1 @@ -7258,17 +9201,17 @@ This setting means you'll only see the default printing for each card, instead o QObject - + Cockatrice card database (*.xml) Banco de dados de cartas do Cockatrice (*.xml) - + All files (*.*) Todos arquivos (*.*) - + Cockatrice replays (*.cor) Cockatrice replays (*.cor) @@ -7288,17 +9231,22 @@ This setting means you'll only see the default printing for each card, instead o Fichas - + + Maybeboard + + + + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7309,7 +9257,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7320,100 +9268,110 @@ Are you sure you would like to disable this feature? - + Confirm Change + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK OK - + Save Salvar - + Save All Salvar Todos - + Open Abrir - + &Yes &Sim - + Yes to &All Sim para &Todos - + &No &Não - + N&o to All Não para &Todos - + Abort Abortar - + Retry Tentar Novamente - + Ignore Ignorar - + Close Fechar - + Cancel Cancelar - + Discard Descartar - + Help Ajuda - + Apply Aplicar - + Reset Resetar - + Restore Defaults Restaurar Padrão @@ -7421,121 +9379,189 @@ Are you sure you would like to disable this feature? QuickSetupPanel - + %1% - + <b>Quick Setup</b> - + Generate all palette roles automatically from a single accent colour - + Accent: - + Primary hue. Used directly for highlights and links. At high intensity it also tints buttons and backgrounds. - + Intensity: - + Subtle - + Full colour - + 0–30 Subtle tint — only highlights and links change hue 30–70 Accented — buttons, tooltips, and borders join in 70–100 Full colour — backgrounds, everything - + 70% - - - Generate ↓ - - - - - Derive all palette roles from the accent colour above. -Fine-tune individual colours in the grid afterwards. - - RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name Nome - + ID ID - + Upload time Hora do upload + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID Código - + Name Nome - + Players Jogadores - + Keep Manter - + Time started Tempo iniciado - + Duration (sec) Duração (seg) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu @@ -7577,37 +9603,37 @@ Fine-tune individual colours in the grid afterwards. RoomSelector - + Rooms Salas - + Joi&n &Entrar - + Room Sala - + Description Descrição - + Permissions Permissões - + Players Jogadores - + Games Jogos @@ -7615,7 +9641,7 @@ Fine-tune individual colours in the grid afterwards. SayMenu - + S&ay @@ -7623,32 +9649,32 @@ Fine-tune individual colours in the grid afterwards. SequenceEdit - + Choose an action from the table Escolha uma ação da tabela - + Hit the key/combination of keys you want to set for this action Pressione a tecla/combinação de teclas que deseja definir para esta ação - + Invalid key Tecla inválida - + Shortcut already in use by: Atalho já está sendo usado por: - + Clear Limpar - + Restore default Restaurar padrão @@ -7681,56 +9707,92 @@ Fine-tune individual colours in the grid afterwards. Data lançamento + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts Restaurar todos atalhos padrões - + Do you really want to restore all default shortcuts? Você tem certeza que deseja restaurar todos atalhos padrões? - + Clear all default shortcuts Limpar todos os atalhos padrão - + Do you really want to clear all shortcuts? Você realmente quer limpar todos os atalhos? - + Section: Seção: - + Action: Ação: - + Shortcut: Atalho: - + + Shortcut editor + + + + How to set custom shortcuts Como definir atalhos personalizados - + Clear all shortcuts Limpar todos os atalhos - + Search by shortcut name Pesquisar pelo nome do atalho @@ -7786,12 +9848,12 @@ Por favor, verifique suas configurações de atalho! SideboardMenu - + &Sideboard - + &View sideboard @@ -7799,27 +9861,27 @@ Por favor, verifique suas configurações de atalho! SoundSettingsPage - + Enable &sounds Habilitar &sons - + Current sounds theme: Tema sonoro atual: - + Test system sound engine Testar sistema de som - + Sound settings Configurações de som - + Master volume Volume principal @@ -7827,48 +9889,48 @@ Por favor, verifique suas configurações de atalho! SpoilerBackgroundUpdater - + Spoilers season has ended Temporada de 'spoilers' encerrada - + Deleting spoiler.xml. Please run Oracle Deletando spoiler.xml. Favor rodar o Oracle - - + + Spoilers download failed Falha no download de 'spoilers' - + No internet connection Sem conexão com a internet - + Error Erro - + Spoilers already up to date 'Spoilers' já atualizados - + No new spoilers added Nenhum 'spoiler' novo adicionado - + Spoilers have been updated! 'Spoilers' foram adicionados! - + Last change: Última alteração: @@ -7876,157 +9938,170 @@ Por favor, verifique suas configurações de atalho! StableReleaseChannel - + Default Padrão - + No reply received from the release update server. Sem resposta do servidor de atualização - + Invalid reply received from the release update server. Resposta inválida do servidor de atualização - + No reply received from the tag update server. Nenhuma resposta recebida do servidor de atualização de tags. - + Invalid reply received from the tag update server. Resposta inválida recebida do servidor de atualização de tags. + + StatsTally + + + Total Power + + + + + Total Toughness + + + StorageSettingsPage - - - + + + Success - + Cached card pictures have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + In-memory (currently loaded) card pictures have been reset. - + Card Picture Loader Caching Method: - + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. - + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. - + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. - + Delete Cached Images - + Delete Saved Images - + Clear In-Memory Images - + Card Picture Loader Cache Method - + Network Cache - + Filesystem - + In-Memory Picture Cache - + Network Cache Size: - + On-disk cache for downloaded pictures - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + In-memory cache for pictures not currently on screen - + Naming scheme: - + Day(s) @@ -8034,17 +10109,33 @@ Por favor, verifique suas configurações de atalho! TabAccount - + + + My Reports + + + + Add to Buddy List Adicionar à lista de amigos - + Add to Ignore List Adicionar à lista de ignorados - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account Conta @@ -8165,118 +10256,118 @@ Por favor, verifique suas configurações de atalho! TabArchidekt - - - + + + Desc. - - + + AND - - + + Require ALL selected colors - - + + Deck name... - - + + Owner... - - + + Packages - - + + Advanced Filters - + Bracket: - - + + Any - - + + Contains card... - - + + Commander... - - + + Tag... - - + + Deck Size - + Cards: - - + + Asc. - + Sort by: - + Filter by: - + Display Settings - - + + Search - - + + Formats @@ -8289,42 +10380,42 @@ Por favor, verifique suas configurações de atalho! TabCardArtRules - + Card: - + ProviderId: - + Mode: - + Reason: - + Type a card name... - + Add rule - + Remove rule - + Refresh @@ -8337,52 +10428,52 @@ Por favor, verifique suas configurações de atalho! TabDeckEditor - + Card Info Informação da carta. - + Deck Deck - + Filters Filtros - + &View &Visualizar - + Card Database - + Printing Impressão - + Visible Visível - + Floating Flutuante - + Reset layout Reiniciar layout - + Deck: %1 Deck: %1 @@ -8390,49 +10481,49 @@ Por favor, verifique suas configurações de atalho! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - + &View - + Printing - + Visible - + Floating - + Reset layout @@ -8440,22 +10531,22 @@ Por favor, verifique suas configurações de atalho! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -8463,139 +10554,237 @@ Por favor, verifique suas configurações de atalho! TabDeckStorage - + Local file system Sistema de arquivos local - + Server deck storage Armazenamento de decks no servidor - - + + Open in deck editor Abrir no editor de decks - + Rename deck or folder - + Upload deck Upload do deck - + Download deck Download do deck - - - - + + + + New folder Nova pasta - - + + Delete Apagar - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder Abrir pasta de decks - + Rename local folder - + Rename local file - + New name: - - - - + + + + + + + Error Error - + Rename failed - - + + + Invalid deck file Arquivo de deck inválido - + Enter deck name Entre o nome do deck - + This decklist does not have a name. Please enter a name: Este deck não tem um nome. Por favor, entre um nome: - + Unnamed deck Deck sem nome - + Failed to upload deck to server Falha ao enviar deck ao servidor - + Delete local file Excluir arquivo local - + Are you sure you want to delete the selected files? Tem certeza de que deseja excluir os arquivos selecionados? - + Delete remote decks Excluir decks remotos - + Are you sure you want to delete the selected decks? Tem certeza de que deseja excluir os decks selecionados? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: Nome da nova pasta: - + Deck Storage Armazenamento de Deck @@ -8603,20 +10792,271 @@ Por favor, entre um nome: TabDeckStorageVisual - + Visual Deck Storage Decks Visuais - + Error - + Could not open deck at %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -8629,27 +11069,27 @@ Por favor, entre um nome: TabEdhRecMain - + &Cards - + Top Commanders - + Tags - + Search for a card ... - + Search @@ -8662,191 +11102,207 @@ Por favor, entre um nome: TabGame - - - + + + Replay Replay - - + + Game Jogo - - + + Player List Lista de jogadores. - - + + + Invite + + + + + Card Info Informação da carta - - + + Messages Mensagens - - + + Replay Timeline Linha do tempo do Replay - + &Phases &Etapas - + &Game &Jogo - + Next &phase Próxima &etapa - + Next phase with &action Próxima etapa com &acao - + Next &turn Próximo &turno - + Reverse turn order Ordem dos turnos revertida - + &Remove all local arrows &Apagar todas as setas locais - + Rotate View Cl&ockwise Girar visualização em sentido h&orário - + Rotate View Co&unterclockwise Girar visualização em sentido a&nti-horário - + Game &information &Informação de jogo - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede Cancelar Rendição - - - + + + &Concede &Conceder - + &Leave game &Sair do jogo - + C&lose replay &Fechar replay - + &Focus Chat &Focar no chat - + &Say: &Falar: - + Selected cards - + &View &Visualizar - + Visible Visível - + Floating Flutuante - + Reset layout Reiniciar layout - + Concede Conceder - + Are you sure you want to concede this game? Você tem certeza que deseja conceder este jogo? - + Unconcede Defazer concessão - + You have already conceded. Do you want to return to this game? Você já concedeu. Quer retornar a esse jogo? - + Leave game Sair do jogo - + Are you sure you want to leave this game? Você tem certeza que deseja sair deste jogo? - + A player has joined game #%1 Um jogador entrou no jogo #%1 - + %1 has joined the game %1 entrou no jogo - + You have been kicked out of the game. Você foi 'chutado' do jogo. @@ -8856,164 +11312,164 @@ Por favor, entre um nome: Home - + Início TabLog - + Logs Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Tempo;RemetenteNome;RemetenteIP;Mensagem;AlvoID;AlvoNome - + Room Logs Logs de salas - + Game Logs Logs de jogos - + Chat Logs Logs de conversas - - + + Error Erro - + You must select at least one filter. Você precisa selecionar pelo menos um filtro. - + You have to select a valid number of days to locate. Você precisa selecionar um número de dias válido para localizar - + Username: Nome de usuário. - + IP Address: Endereço de IP: - + Game Name: Nome do jogo: - + GameID: ID do Jogo: - + Message: Mensagem: - + Main Room Sala Principal - + Game Room Sala de Jogo - + Private Chat Chat Privado. - + Past X Days: X dias anteriores: - + Today Hoje - + Last Hour Última hora - + Maximum Results: Máximo de resultados: - + At least one filter is required. The more information you put in, the more specific your results will be. Ao menos um filtro deve ser selecionado. Quanto mais informação você inserir, mais específicos seus resultados serão. - + Get User Logs Obter logs de usuário - + Clear Filters Limpar Filtros - + Filters Filtros - + Log Locations Logs de localização - + Date Range Intervalo de data - + Maximum Results Máximo de resultados - - + + Message History Histórico de Mensagens - + Failed to collect message history information. Falha ao coletar informação do histórico de mensagens. - + There are no messages for the selected filters. Não existe mensagem que corresponda aos filtros selecionados. @@ -9021,218 +11477,558 @@ Quanto mais informação você inserir, mais específicos seus resultados serão TabMessage - + Private &chat Conversa privada - + &Leave &Sair - + %1 - Private chat %1 - Conversa privada - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. Este usuário esta ignorando você, ele não pode ver suas mensagens no chat principal e você não pode entrar nos jogos dele. - + Private message from Mensagem privada de - + %1 has left the server. %1 saiu do servidor. - + %1 has joined the server. %1 entrou no servidor. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system Sistema de arquivos local - + Server replay storage Servidor de armazenamento de replays - - + + Watch replay Assistir replay - + Rename Renomear - - + + New folder Nova pasta - - + + Delete Excluir - + Open replays folder Abrir pasta de repetições - + Download replay Baixar replay - + Toggle expiration lock Trocar expiração da trava - + Get replay share code - - + + Look up replay by share code - + Rename local folder Renomear pasta local - + Rename local file Renomear arquivo local - + New name: Novo nome: - + Error Erro - + Rename failed Falha ao renomear - + Name of new folder: Nome da nova pasta: - + Delete local file Excluir arquivo local - + Are you sure you want to delete the selected files? Tem certeza de que deseja excluir os arquivos selecionados? - + Are you sure you want to delete the selected replays? Tem certeza de que deseja excluir as repetições selecionadas? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay Excluir replay remoto @@ -9242,65 +12038,478 @@ Quanto mais informação você inserir, mais específicos seus resultados serão Repetições de jogo + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - - Friends - - - - - Online - - - - - Ignored - - - - + &Say: &Falar: - + Chat Chat - + &Room &Sala - + &Leave room S&air da sala - + &Clear chat Limpar conversa - + Chat Settings... Configurações de Conversa - + mentioned you. mencionou você. - + Click to view Clique para ver - + You are flooding the chat. Please wait a couple of seconds. Você está flodando o chat. Por favor, espere alguns segundos. @@ -9308,35 +12517,35 @@ Quanto mais informação você inserir, mais específicos seus resultados serão TabServer - + Server Servidor - - - - + + + + Error Erro - + Failed to join the server room: it doesn't exist on the server. Falha ao entrar na sala: ela não existe no servidor. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. O servidor pensa que você está na sala, mas seu cliente não consegue exibi-la. Tente reiniciar o seu cliente. - + You do not have the required permission to join this server room. Você não tem permissão para entrar nesta sala. - + Failed to join the server room due to an unknown error: %1. Falha ao entrar na sala devido a um erro desconhecido: %1. @@ -9344,97 +12553,173 @@ Quanto mais informação você inserir, mais específicos seus resultados serão TabSupervisor - + Deck Editor Editor de Deck - + Visual Deck Editor - + EDHRec - + Archidekt - + Home - + Início - + &Visual Deck Storage &Decks Visuais - + Visual Database Display - + Server Servidor - + Account Conta - + Deck Storage Armazenamento de Deck - + Game Replays Repetições de jogo - + Administration Administração - + Logs Registros - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? Você tem certeza? - + There are still open games. Are you sure you want to quit? Ainda existem jogos abertos. Você tem certeza que deseja sair? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view Clique para visualizar - + Your buddy %1 has signed on! Seu amigo %1 entrou no servidor! - + Unknown Event Evento desconhecido. - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -9445,39 +12730,39 @@ Esta mensagem pode significar que existe uma nova versão do Cockatrice disponí Para atualizar seu cliente, vá para Ajuda -> Verificar atualizações. - + Idle Timeout Tempo esgotado de inatividade - + You are about to be logged out due to inactivity. Você irá ser desconectado por inatividade - + Promotion Promoção - + You have been promoted. Please log out and back in for changes to take effect. Você foi promovido a moderador(a). Por favor faça logout e depois acesse novamente para que as mudanças sejam efetivadas. - + Warned Advertido - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Você recebeu uma advertência por %1. Por favor evite tais ações ou outras medidas poderão ser tomadas contra você. Se você tem qualquer dúvida, por favor envie uma mensagem privada um dos moderadores. - + You have received the following message from the server. (custom messages like these could be untranslated) Você recebeu a seguinte mensagem do servidor. @@ -9497,6 +12782,34 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -9511,6 +12824,65 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você Não foi possível analisar o deck. + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -9582,57 +12954,52 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você UserCardArtSettingsDialog - + Card Art Settings - + Type a card name... - + Card name: - - Card ProviderId: + + Printing: - + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + Left margin (%): - + Right margin (%): - - Vertical offset: - - - - - Zoom: - - - - - Parameters - - - - - Preview - - - - + Remove Banner Card @@ -9640,216 +13007,266 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você UserContextMenu - + User &details &Detalhes do usuário - + Private &chat Conversa privada - + Show this user's &games Mostrar este usuário de jogo - + + View this user's &public decks + + + + Add to &buddy list Adicionar à &lista de amigos - + Remove from &buddy list Remover da li&sta de amigos - + Add to &ignore list Adicionar à li&sta dos ignorados - + Remove from &ignore list Remover da lista dos i&gnorados - + Kick from &game C&hutar do jogo - + + Report user + + + + Warn user Advertir usuário - + View user's war&n history Visualizar o histórico de advertê&ncias do usuário. - + Ban from &server Ban&ir do servidor - + View user's &ban history Visualizar o histórico de %banimento do usuário. - + &Promote user to moderator &Promover usuário a moderador - + Dem&ote user from moderator Rev&ogar as permissões de moderação do usuário - + Promote user to &judge Promover usuário a &juiz - + Demote user from judge Remover usuário da posição de juiz - + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes Visualizar notas do administrador - - + Investigate user + + + + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games Jogos de %1 - - - + + + Ban History Histórico de banimento - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason Tempo de banimento;Moderador;Duração do Banimento;Razão do Banimento;Razão Visível - + User has never been banned. O usuário nunca foi banido. - + Failed to collect ban information. Falha ao coletar informações de banidas. - - - + + + Warning History Histórico de advertências - + Warning Time;Moderator;User Name;Reason Tempo de advertência;Moderador;Nome de Usuário;Razão - + User has never been warned. Usuário nunca foi advertido. - + Failed to collect warning information. Falha ao coletar informações de aviso. - + Failed to get admin notes. Falha ao obter as notas do administrador. - - + + Success Sucesso - + Successfully promoted user. Usuário promovido com sucesso. - + Successfully demoted user. Permissões de moderação revogadas com sucesso. - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + Kick Player - + Are you sure you want to kick this player from the game? - - - + + + Failed Falha - + Failed to promote user. Falha ao promover usuário. - + Failed to demote user. Falha ao revogar permissões de moderação do usuário. - + Copy hash to clipboard Copiar hash para área de transferência - + Remove this user's messages Remover mensagens deste usuário @@ -9887,61 +13304,61 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você Administrador - + Moderator Moderador - + Registered user Usuário registrado - - + + Unregistered user Usuário não registrado - + Judge Juíz - + Unknown Desconhecido - + The entered password does not match your account. A senha inserida não coincide com de sua conta. - - - - + + + + Information Informação - + User information updated. Informações do usuário atualizadas - - - - - - + + + + + - - - + + + + Error Erro @@ -9965,283 +13382,292 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você Edit Banner Card + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) %n ano(s),%n ano(s),%n ano(s), - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format %10%n dia(s) %20%10%n dia(s) %20%10%n dia(s) %20 - + Enter Password Insira a senha - + Password verification is required in order to change your email address Uma verificação de senha é obrigatória para alterar seu endereço de e-mail - - - + + + An error occurred while trying to update your user information. Ocorreu um erro ao tentar atualizar suas informações de usuário. - + The selected card is blacklisted on this server or another error occurred. - + Banner card removed. - + Banner card updated. - + This server does not permit you to update your user informations. Este servidor não permite que você atualize suas informações de usuário. - + Password changed. Senha alterada. - + This server does not permit you to change your password. Este servidor não permite que você altere sua senha. - + The new password is too short. A nova senha é muito curta. - + The old password is incorrect. A senha antiga está incorreta. - + Avatar updated. Avatar alterado. - + This server does not permit you to update your avatar. Este servidor não permite que você altere seu avatar. - + An error occured while trying to update your avatar. - - - This server does not permit you to update your user informations. - An error occured while trying to updater your avatar. - Este servidor não permite que você atualize suas informações de usuário. - UserInfoPopup - - + + Games - + Chat - + Open private chat - + Profile - + View user profile - + Show this user's games - + + Invite + + + + + Invite to your game + + + + − Buddy - + Remove from buddy list - + + Buddy - + Add to buddy list - + − Ignore - + Remove from ignore list - + + Ignore - + Add to ignore list - + Ban - + Ban from server - + Warn - + Warn user - + Ban log - + View ban history - + Warn log - + View warning history - + Notes - + View admin notes - + − Mod - + Demote from moderator - + + Mod - + Promote to moderator - + − Judge - + Demote from judge - + + Judge - + Promote to judge - + Join game - + Spectate - - + + Loading games… - + Could not load games. - + No active games. @@ -10249,223 +13675,344 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você UserInterfaceSettingsPage - + General interface settings Configurações gerais de interface - + &Double-click cards to play them (instead of single-click) &Duplo clique nos cards para jogá-los (ao invés de clique simples) - + &Clicking plays all selected cards (instead of just the clicked card) Clicar reproduz todas as cartas selecionadas (em vez de apenas a carta clicada) - + &Play all nonlands onto the stack (not the battlefield) by default &Jogar todos não terrenos na pilha (não campo de batalha) por padrão - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed Fechar janela de visualização ao remover a última carta - + Auto focus search bar when card view window is opened - + Annotate card text on tokens Anotar texto de card nas fichas - + Show selection count during drag selection - + Show total selection count - - Show subtype breakdown in selection tally + + + Disabled - + + + Enabled + + + + + + Automatic + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + Use tear-off menus, allowing right click menus to persist on screen Usar menus tear-off, permitindo que menus de clique com botão direito persistam na tela - + Keep game chat focused when clicking in game (Note: disables card view search bar) - + Notifications settings Configurações de Notificações - + Enable notifications in taskbar Habilitar notificações na barra de tarefas - + Notify in the taskbar for game events while you are spectating Notificar na barra de tarefas para eventos do jogo enquanto você está como espectador - + Notify in the taskbar when users in your buddy list connect Notificar na barra de tarefas quando usuários na sua lista de amigos se conectar - + Animation settings Configurações de animação - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation Animação de &virar/desvirar - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings Configurações do editor/armazenamento de decks - + Open deck in new tab by default Abrir deck em uma nova aba por padrão - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby Usar decks visuais no lobby do jogo - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - - do nothing + + Do nothing - - ask to convert to .cod + + Ask to convert to .cod - - always convert to .cod + + Always convert to .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings Configurações de repetição - + Buffer time for backwards skip via shortcut: Tempo de buffer para retroceder usando atalho: + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 Usuários conectados ao servidor: %1 - + Users in this room: %1 Usuários nesta sala: %1 - + Buddies online: %1 / %2 Amigos online: %1 / %2 - + Ignored users online: %1 / %2 Usuários ignorados online: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + Flip coin - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -10502,22 +14049,22 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... - + Save Filter - + Save all currently applied filters to a file - + Enter filename... @@ -10525,72 +14072,72 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDatabaseDisplayFilterToolbarWidget - + Sort by - + Filter by - + Save and load filters - + Filter by exact card name - + Filter by card main-type - + Filter by card sub-type - + Filter by set - + Filter by format legality - + Save/Load - + Name - + Main Type - + Sub Type - + Sets - + Formats @@ -10692,7 +14239,7 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -10700,19 +14247,19 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -10758,28 +14305,28 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - - + + Visual - + Loading database ... - + Clear all filters - + Table @@ -10787,43 +14334,43 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat @@ -10839,12 +14386,12 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -10852,17 +14399,17 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -10870,7 +14417,7 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDeckStorageFolderDisplayWidget - + Deck Storage Decks Visuais @@ -10878,52 +14425,57 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + Show Color Identity - + Show Tags On Deck Previews - + + Show Upload Time + + + + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -10931,7 +14483,7 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -10939,22 +14491,22 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) Ordenar Alfabeticamente (Nome do Deck) - + Sort Alphabetically (Filename) Ordenar Alfabeticamente (Nome do Arquivo) - + Sort by Last Modified Ordenar por Última Modificação - + Sort by Last Loaded Ordenar por Último Carregamento @@ -10962,17 +14514,22 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você VisualDeckStorageWidget - + Loading database ... Carregando banco de dados ... - + Refresh loaded files - + + Select decks to share + + + + Visual Deck Storage Settings @@ -10980,177 +14537,202 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você WarningDialog - + Which warning would you like to send? Qual advertência você gostaria de enviar? - + Redact all messages from this user in all rooms Remover todas mensagens deste usuário em todas salas - + &OK %OK - + &Cancel &Cancelar - + Warn user for misconduct Advertir usuario por mau comportamento. - - + + Error Erro - + User name to send a warning to can not be blank, please specify a user to warn. O campo "nome de usuário" ao qual se deseja enviar uma advertência não pode permanecer em branco, por favor especifique um usuário para ser advertido. - + Warning to use can not be blank, please select a valid warning to send. O campo "advertência" não pode permanecer em branco, por favor selecione uma advertência para ser enviada. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top Mover o selecionado para o topo - + Move selected set up Mover o selecionado para cima - + Move selected set down Mover o selecionado para baixo - + Move selected set to the bottom Mover o selecionado para o fundo - - Search by set name, code, or type - Procurar por expansão, código ou tipo - - - + Default order Ordem padrão - + Restore original art priority order Restaurar prioridade da ordem de arte original - + Enable all sets Habilitar todas expansões - + Disable all sets Desabilitar todas expansões - + Enable selected set(s) Habilitar expansão/expansões selecionada(s) - + Disable selected set(s) Desabilitar expansão/expansões selecionada(s) - + Deck Editor Editor de Deck - + Use CTRL+A to select all sets in the view. Use CTRL+A para selecionar todas expansões visíveis. - + Only cards in enabled sets will appear in the card list of the deck editor. Apenas cartas em expansões ativadas irão aparecer na lista de cartas do editor de deck. - + Image priority is decided in the following order: Prioridade da imagem é decidida na seguinte ordem: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki primeiro a pasta de PERSONALIDADAS (%1), depois as expansões habilitadas neste diálogo (de cima para baixo) - + Include cards rebalanced for Alchemy [requires restart] - + Card Art Arte da Carta - + + Search by set name, code, type, or release date + + + + How to use custom card art Como usar arte de carta personalizada - + Hints Dicas - + Note Nota - + Sorting by column allows you to find a set while not changing set priority. Classificar por coluna te permite encontrar um set sem mudar a prioridade de sets - + To enable ordering again, click the column header until this message disappears. Para reabilitar a ordenação, clique no cabeçalho da coluna até que esta mensagem desapareça - + Use the current sorting as the set priority instead Usar a classificação atual como a prioridade de sets - + Sorts the set priority using the same column Classificar a prioridade de sets usando a mesma coluna - + Manage sets Gerenciar expansões @@ -11158,93 +14740,85 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped Não agrupado - + Group by Type Agrupar por Tipo - + Group by Mana Value Agrupar por Valor de Mana - + Group by Color Agrupar por Cor - + Unsorted Não ordenado - + Sort by Name Ordenar por Nome - + Sort by Type Ordenar por Tipo - + Sort by Mana Cost Ordenar por Custo de Mana - + Sort by Colors Ordenar por Cores - + Sort by P/T Ordenar por P/T - + Sort by Set Ordenar por Conjunto - + shuffle when closing embaralhar quando fechar - + pile view visualização da pilha - - i18n - - - English - Português do Brasil (Brazilian Portuguese) - - main - + Connect on startup Conectar ao iniciar - + Debug to file Depurar para um arquivo @@ -12028,6 +15602,16 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você Graveyard Cemitério + + + Report Queue + + + + + Moderation + + @@ -12295,7 +15879,7 @@ Por favor evite tais ações ou outras medidas poderão ser tomadas contra você Home - + Início diff --git a/cockatrice/translations/cockatrice_ru.ts b/cockatrice/translations/cockatrice_ru.ts index 26d95fe1c..9bdedd7b9 100644 --- a/cockatrice/translations/cockatrice_ru.ts +++ b/cockatrice/translations/cockatrice_ru.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Установить жетоны... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Установить жетоны - + New value for counter '%1': Новое значение жетонов '%1': @@ -36,66 +36,115 @@ AbstractTabDeckEditor - + Open in new tab Открыть в новой вкладке - + Are you sure? Вы уверены? - + The decklist has been modified. Do you want to save the changes? Деклист был изменен. Вы хотите сохранить изменения? - - - - - - + + + + + + Error Ошибка - + Could not open deck at %1 Не удалось открыть колоду по пути «%1» - + Could not save remote deck Не удалось сохранить сетевую колоду - - + + The deck could not be saved. Please check that the directory is writable and try again. Колода не может быть сохранена. Проверьте что директория доступна для записи и попробуйте снова. - + Save deck Сохранить колоду - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. Не удалось сохранить колоду. - + There are no cards in your deck to be exported В колоде нет карт для экспорта + + AccountSetupPage + + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + AddAnalyticsPanelDialog @@ -107,12 +156,12 @@ Please check that the directory is writable and try again. AdminNotesDialog - + Update Notes Список изменений - + Admin Notes for %1 Примечания администратора для %1 @@ -138,218 +187,304 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds секунд - + Error Ошибка - + Could not create themes directory at '%1'. Не удалость создать папку для тем в '%1'. - + Theme settings Настройки темы - + Current theme: Текущая тема: - + Open themes folder Открыть папку с темами - + Home tab background source: - Путь до обоев на домашней вкладке: + Путь к обоям вкладки навигации: - + Home tab background shuffle frequency: Частота смены обоев: - + Disabled Отключено - + Menu settings Настройки меню - + Show keyboard shortcuts in right-click menus Отображать сочетания клавиш в контекстном меню - + Show game filter toolbar above list in room tab - + Card rendering Отрисовка карт - + Display card names on cards having a picture Отображать название карты поверх изображения - + Auto-Rotate cards with sideways layout Автоматически поворачивать карты альбомной ориентации - + Override all card art with personal set preference (Pre-ProviderID change behavior) Перезаписать все арты в соответствии с выбранными изданиями (Pre-ProviderID меняет это поведение) - + Light - + Светлая - + Dark - + Тёмная - + + System + Системная + + + + Show all playmats - + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + Active theme palette: - + + Active theme style: + + + + + Qt widget style saved to this theme ("System" lets the application decide) + + + + Edit theme palette - + Home tab settings - + Настройки вкладки навигации - + Display card name of background in bottom right + Отображать название карты в правом нижнем углу + + + + Dim the home tab background - + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + Styling settings - + Style user list - + Card printings - + Издания карт - + Bump sets that the deck contains cards from to the top in the printing selector Перемещать в начало списка выбора издания те выпуски, из которых в колоде есть карты. - + Scale cards on mouse over Увеличивать карты при наведении мыши - + Use rounded card corners Использовать скругленные углы карт - + Card layout - + Minimum overlap percentage of cards on the stack and in vertical hand Минимальный процент наложения карт в стеке и вертикальной руке - + Maximum initial height for card view window: Максимальная начальная высота для окна просмотра карты - - + + rows строки - + Maximum expanded height for card view window: Макс. высота окна для просмотра карт - + Card counters Жетоны на картах - + Counter %1 Жетон %1 - + Hand layout Раскладка руки - + Display hand horizontally (wastes space) Отображать руку горизонтально (требует места) - + Enable left justification Выравнивание по левому краю - + Table grid layout Сетка - + Invert vertical coordinate Инвертировать вертикальные координаты - + Minimum player count for multi-column layout: Минимальное количество игроков для столбчатого расположения: - + Maximum font size for information displayed on cards: Максимальный размер шрифта для текста карт: @@ -357,14 +492,14 @@ Please check that the directory is writable and try again. ArchidektApiResponseDeckDisplayWidget - + Back to results - + Open Deck in Deck Editor - + Открыть колоду в редакторе @@ -388,112 +523,112 @@ Please check that the directory is writable and try again. BanDialog - + ban &user name Забанить по &Имени игрока - + ban &IP address Забанить по &адресу IP - + ban client I&D Забанить по I&D клиента - + Ban type Тип бана - + &permanent ban &постоянный бан - + &temporary ban &временный бан - + &Days: &Дни - + &Hours: &Часы - + &Minutes: &Минуты - + Duration of the ban Длительность бана - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Пожалуйста, назовите причину бана. Эта информация сохраняется для модераторов и не будет видна забаненному игроку - + Please enter the reason for the ban that will be visible to the banned person. Пожалуйста, назовите причину бана, которая будет видна забаненному игроку. - + Redact all messages from this user in all rooms Отредактировать все сообщения от этого пользователя во всех комнатах - + &OK &ОК - + &Cancel &Отмена - + Ban user from server Забанить игрока на этом сервере - - - - + + + + Error Ошибка - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Необходимо выбрать тип бана: по имени, по адресу IP, по ID клиента или комбинацию этих трех типов. - + You must have a value in the name ban when selecting the name ban checkbox. Необходимо ввести имя игрока при выборе опции бана по имени. - + You must have a value in the ip ban when selecting the ip ban checkbox. Необходимо ввести адрес IP при выборе опции бана по IP. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Необходимо ввести ID клиента при выборе опции бана по ID. @@ -501,22 +636,22 @@ This is only saved for moderators and cannot be seen by the banned person. BetaReleaseChannel - + Beta Бета - + No reply received from the release update server. Нет ответа от сервера обновления версий. - + Invalid reply received from the release update server. Неправильный ответ от сервера обновления версий. - + No reply received from the file update server. Нет ответа от сервера обновления файлов. @@ -524,7 +659,20 @@ This is only saved for moderators and cannot be seen by the banned person. CardArtPreviewWidget - + + Banner preview + + + + + + + + Zoom %1× + + + + No card selected @@ -532,22 +680,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardArtRulesModel - + Card - + ProviderId - + Mode - + Reason @@ -555,65 +703,209 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name Название - + Sets Издания - + Mana cost Мана-стоимость - + Card type Тип - + P/T Сила/Выносливость: - + Color(s) Цвет(а) + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + CardDatabaseView - + Add to Deck - + Добавить в колоду - + Add to Sideboard - + Добавить в сайд - + Select Printing - + + Add to Zone + + + + + Create &new zone... + + + + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards @@ -682,7 +974,7 @@ This is only saved for moderators and cannot be seen by the banned person. Mana Value - Количество маны + Мановая ценность @@ -702,7 +994,7 @@ This is only saved for moderators and cannot be seen by the banned person. Loyalty - Преданность + Верность @@ -712,7 +1004,7 @@ This is only saved for moderators and cannot be seen by the banned person. Main Type - Тип + Основной тип @@ -723,22 +1015,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoFrameWidget - + Image Изображение - + Description Описание - + Both Оба - + View transformation Обратная сторона @@ -746,22 +1038,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards Посмотреть связанные карты - + Add card to deck Добавить карту в колоду - + Mainboard Колода - + Sideboard Сайд @@ -769,27 +1061,27 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoTextWidget - + Name: Название: - + Set: - + Издание: - + Collector Number: - + Related cards: Связанные карты: - + Unknown card: Неизвестная карта: @@ -797,129 +1089,129 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... Показать... - + &All players Всем игрокам - + View related cards Посмотреть связанные карты - + Token: Фишка: - + All tokens Все фишки: - + &Select All Выбрать все - + S&elect Row Выбрать строку - + S&elect Column Выбрать колонку - + &Play Разыграть - + &Hide Спрятать - + Play &Face Down Разыграть лицом вниз - + &Tap / Untap Turn sideways or back again Повернуть / Развернуть - + Skip &untapping - + Пропустить разворот - + T&urn Over Turn face up/face down Перевернуть - + &Peek at card face Посмотреть лицевую сторону - + &Clone Клонировать - + Attac&h to card... Прикрепить к карте... - + Unattac&h Открепить - + &Draw arrow... Нарисовать стрелку - + &Set annotation... Добавить заметку - + Reduce life by power - + Ca&rd counters Жетоны на карте - + &Add counter (%1) &Добавить жетон (%1) - + &Remove counter (%1) &Убрать жетон (%1) - + &Set counters (%1)... &Установить жетоны (%1)... @@ -934,13 +1226,13 @@ This is only saved for moderators and cannot be seen by the banned person. Filesystem - + Файловая система CardSizeWidget - + Card Size Размер карты @@ -1080,6 +1372,24 @@ This is only saved for moderators and cannot be seen by the banned person.кастомной зоны '%2' игрока '%1' + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser @@ -1109,280 +1419,382 @@ This is only saved for moderators and cannot be seen by the banned person. + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + ConnectionController - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown Плановый перерыв в работе сервера - + Failed Login Не удалось подключиться - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - - - - - - - - - - - + + + + + + + + + + - - + + + Error Ошибка - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Активация учетной записи - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Кажется, Вы пытаетесь зарегистрировать новую учетную запись, но этот адрес электронной почты уже используется. Этот сервер ограничивает количество учетных записей, которые можно зарегистрировать на один и тот же адрес электронной почты. Пожалуйста, свяжитесь с оператором сервера для поддержки или для получения сведений об учётной записи. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + Server timeout - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Information - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -1390,113 +1802,113 @@ To update your client, go to Help -> Check for Updates. - - - + + + Reset Password - + Сбросить пароль - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - + Requesting forgotten password to %1 as %2... - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. @@ -1546,7 +1958,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1562,17 +1974,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) Искать по названию карты (или по поисковому запросу) - + Add card to &maindeck Добавить карту в колоду - + Add card to &sideboard Добавить карту в сайд @@ -1580,97 +1992,158 @@ To update your client, go to Help -> Check for Updates. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card Обложка колоды - + Main Type - Тип + Основной тип - + Mana Cost Мана-стоимость - + Colors Цвет - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing Выбрать издание - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck Колода - + Deck &name: Название колоды: - + Banner Card/Tags Visibility Settings Настройки отоборажение обложки и тегов - + Show banner card selection menu Показать меню выбора обложки - + Show tags selection menu Показать меню выбора тегов - + &Comments: Комментарий: - + Group by: Группировать по: - + Format: - + Hash: Хэш-сумма - + &Increment number Увеличить количество - + &Decrement number Уменьшить количество - + &Remove row Удалить строку - + Swap card to/from sideboard Переместить карту в/из сайда @@ -1678,17 +2151,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorFilterDockWidget - + Filters Фильтры - + &Clear all filters Убрать все фильтры - + Delete selected Удалить выбранное @@ -1696,114 +2169,119 @@ To update your client, go to Help -> Check for Updates. DeckEditorMenu - + &Deck Editor Редактор колоды - + &New deck Новая колода - + &Load deck... Загрузить колоду - + Load recent deck... Загрузить недавнюю колоду - + Clear Очистить - + &Save deck Сохранить колоду - + Save deck &as... Сохранить колоду как... - + + Share deck... + + + + Load deck from cl&ipboard... Загрузить колоду из буфера обмена - + Edit deck in clipboard Редактировать колоду из буфера обмена - - + + Annotated Аннотировано - - + + Not Annotated Не аннотировано - + Save deck to clipboard Сохранить колоду в буфер обмена - + Annotated (No set info) Аннотировано (нет информации об издании) - + Not Annotated (No set info) Не аннотировано (нет информации об издании) - + &Print deck... Распечатать колоду... - + Load deck from online service... Загрузить колоду из онлайн-сервиса - + &Send deck to online service Сохранить колоду в онлайн-сервис - + Create decklist (decklist.org) Создать деклист (decklist.org) - + Create decklist (decklist.xyz) Создать деклист (decklist.xyz) - + Analyze deck (deckstats.net) Анализировать колоду (deckstats.net) - + Analyze deck (tappedout.net) Анализировать колоду (tappedout.net) - + &Close Закрыть @@ -1811,7 +2289,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorPrintingSelectorDockWidget - + Printing Selector Выбрать издание @@ -1819,110 +2297,152 @@ To update your client, go to Help -> Check for Updates. DeckEditorSettingsPage - - + + Update Spoilers Обновить спойлеры - + Success Успешно - + Download URLs have been reset. Адреса URL загрузки были сброшены. - + Add URL Добавить URL - - + + URL: URL: - - + + Edit URL Изменить URL: - + Add New URL Добавить новый URL - + Remove URL Удалить URL - + Updating... Обновление... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path Выбрать путь - + URL Download Priority Приоритет загрузки URL - + Spoilers Спойлеры - + Download Spoilers Automatically Скачивать спойлеры автоматически - + Spoiler Location: Расположение спойлеров: - + Last Change Последние изменения - + Spoilers download automatically on launch Спойлеры скачиваются автоматически при запуске - + Press the button to manually update without relaunching Нажмите, чтобы обновить вручную без перезапуска - + Do not close settings until manual update is complete Не закрывать настройки до завершения ручного обновления - + Download card pictures on the fly Синхронная загрузка изображений карт - + How to add a custom URL Как добавить пользовательский URL - + Reset Download URLs Обнулить загруженные URL @@ -1930,32 +2450,32 @@ To update your client, go to Help -> Check for Updates. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1963,27 +2483,27 @@ To update your client, go to Help -> Check for Updates. DeckListModel - + Count Количество - + Set Издание - + Number Номер - + Provider ID Provider ID - + Card Название @@ -2004,17 +2524,22 @@ To update your client, go to Help -> Check for Updates. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match Режим: полное совпадение - + Mode: Includes Режим: частичное совпадение - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) Фильтра совпадения цветов (И/ИЛИ/НЕ фильтр) @@ -2022,7 +2547,7 @@ To update your client, go to Help -> Check for Updates. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... Редактировать теги ... @@ -2030,62 +2555,62 @@ To update your client, go to Help -> Check for Updates. DeckPreviewTagDialog - + Deck Tags Manager Управление тегами колоды - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: Редактируйте теги в колоде. Выберите нужные теги или добавьте новый: - + Add a new tag (e.g., Aggro️) Новый тег (например, Aggro) - + Add Tag Добавить тег - + Filter tags... Фильтр тегов... - + OK ОК - + Edit default tags Редактировать теги по умолчанию - + Cancel Отменить - + Invalid Input Неправильный ввод - + Tag name cannot be empty! Название тега не может быть пустым! - + Duplicate Tag Копировать тег - + This tag already exists. Этот тег уже существует @@ -2093,94 +2618,109 @@ To update your client, go to Help -> Check for Updates. DeckPreviewWidget - + Banner Card Обложка - + Open in deck editor Открыть в редакторе колоды - + + Share deck... + + + + Edit Tags Редактировать теги - + Rename Deck Переименовать колоду - + Save Deck to Clipboard Сохранить колоду в буфер обмена - + Annotated Аннотировано - + Annotated (No set info) Аннотировано (нет информации об издании) - + Not Annotated Не аннотировано - + Not Annotated (No set info) Не аннотировано (нет информации об издании) - + Rename File Переименовать файл - + Delete File Удалить файл - + Set Banner Card Установить обложку колоды - - + + New name: Новое название: - - + + Rename deck + + + + + Rename file + + + + + Error Ошибка - + Rename failed Не удалось переименовать - + Delete file Удалить файл - + Are you sure you want to delete the selected file? Уверены, что хотите удалить выбранный файл? - + Delete failed Не удалось удалить @@ -2188,57 +2728,102 @@ To update your client, go to Help -> Check for Updates. DeckStateManager - + Rename deck to "%1" from "%2" - + Updated comments (was %1 chars, now %2 chars) - + Set banner card to %1 (%2) - + + Set playmat to %1 + + + + Tags changed - + Set format to %1 - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + %1 1 × "%2" (%3) - + Added Добавлено - + Removed Удалено @@ -2260,97 +2845,125 @@ To update your client, go to Help -> Check for Updates. DeckViewContainer - + Load deck... Загрузить колоду... - + Load remote deck... Загрузить колоду удаленно... - + Load from clipboard... Загрузить из буфера обмена... - + Load from website... Загрузить из интернета... - + Unload deck Выгрузить колоду - + Ready to start &Готов - + Force start Принудительный старт - + Sideboard unlocked Сайдборд разблокирован - + Sideboard locked Сайдборд заблокирован - - + + Error Ошибка - + The selected file could not be loaded. Выбранный файл не может быть загружен - + Deck is greater than maximum file size. Колода больше максимально возможного размера файла - + Are you sure you want to force start? This will kick all non-ready players from the game. Уверены что хотите принудительно начать? Это кикнет всех неготовых игроков из игры - + Cockatrice Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion Изменить формат колоды - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? Вы пытаетесь присвоивить тег колоде в формате .txt Теги могут быть добавлены только к колоде в формате .cod -Хотите конвертировать колоду в фомат .cod? +Хотите конвертировать колоду в формат .cod? - + Remember and automatically apply choice in the future Запомнить выбор @@ -2363,143 +2976,143 @@ This will kick all non-ready players from the game. Загрузка... - + Known Hosts Известные Хосты - + Delete the currently selected saved server Удалить текущий выбранный сервер - + Refresh the server list with known public servers Обновить список известных публичных серверов - + New Host Новый хост - + Name: Название: - + &Host: &Хост: - + &Port: &Порт: - + Player &name: &Имя пользователя: - + P&assword: П&ароль: - + &Save password &Сохранить пароль - + A&uto connect Присоединяться автоматически - + Automatically connect to the most recent login when Cockatrice opens Автоматически подключится с последним логином при открытии Кокатрис - + If you have any trouble connecting or registering then contact the server staff for help! Если у вас появилась проблема при присоединении или регистрации - свяжитесь с сотрудниками для помощи! - - + + Webpage Веб-страница - + Reset Password Обнулить пароль - + Forgot password? Забыли пароль? - + &Connect &Подключиться - + Server Сервер - + Login Логин - + Server Contact Соединение с сервером - + Connect to Server Подключиться к серверу - + Server URL URL сервера - + Communication Port Порт связи - + Unique Server Name Уникальное имя сервера - + Connection Warning Предупреждение при соединении - + You need to name your new connection profile. Назовите ваш профиль для соединения. - + Connect Warning Ошибка при соединении - + The player name can't be empty. Необходимо указать имя пользователя. @@ -2507,122 +3120,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings &Запомнить настройки - + &Description: &Описание: - + P&layers: &Игроки: - + General Основные - + Game type Формат игры - + &Password: &Пароль: - + Only &buddies can join Только для &друзей - + Only &registered users can join Только для &зарегистрированных пользователей - + Joining restrictions Ограничения - + &Spectators can watch &Зрители могут наблюдать за игрой - + Spectators &need a password to watch Зрителям &необходим пароль для просмотра игры - + Spectators can &chat Позволить зрителям &комментировать - + Spectators can see &hands Зрители могут видеть &руки игроков - + Create game as spectator Создать игру как зритель - + Spectators Зрители - + Starting life total: Начальное количество жизней: - + Open decklists in lobby Открыть деклист в лобби - + Create game as judge - + Создать игру как судья - + Game setup options Настройки игры - + &Clear &Очистить - + Create game Создать игру - + Game information Информация об игре - + Error Ошибка - + Server error. Ошибка сервера. @@ -2630,97 +3243,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Название: - + Token Фишка - + C&olor: &Цвет: - + white белый - + blue синий - + black черный - + red красный - + green зеленый - + multicolor многоцветный - + colorless бесцветный - + &P/T: &Сила/Выносливость: - + &Annotation: &Примечание: - + &Destroy token when it leaves the table &Уничтожить фишку, когда она покинет поле битвы - + Create face-down (Only hides name) Создать лицом вниз (скроет только название) - + Token data Описание фишки - + Show &all tokens Показать &все фишки - + Show tokens from this &deck Показать фишки из указанной &колоды - + Choose token from list Выбрать фишку из списка - + Create token Создать фишку @@ -2728,53 +3341,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags Редактировать теги - + Add Добавить - + Confirm Подтвердить - + Cancel Отменить - + Enter a tag and press Enter Введите тег и нажмите Enter - - + + ✖ ✖ - + Invalid Input Неправильный ввод - + Tag name cannot be empty! Название тега не может быть пустым! - + Duplicate Tag Копировать тег - + This tag already exists. Этот тег уже существует @@ -2783,7 +3396,7 @@ This will kick all non-ready players from the game. DlgEditAvatar - + No image chosen. Изображение не выбрано. @@ -2795,27 +3408,32 @@ To remove your current avatar, confirm without choosing a new image. Чтобы удалить текущий аватар, нажмите "Сменить аватар", оставив поле выбора изображения пустым. - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... Поиск... - + Change avatar Сменить аватар - + Open Image Открыть изображение - + Image Files (*.png *.jpg *.bmp) Файлы изображений (*.png *.jpg *.bmp) - + Invalid image chosen. Выбрано не подходящее изображение. @@ -2841,38 +3459,38 @@ To remove your current avatar, confirm without choosing a new image. DlgEditPassword - + Old password: Старый пароль: - + New password: Новый пароль: - + Confirm new password: Подтверждение пароля: - + Change password Сменить пароль - - + + Error Ошибка - + Your password is too short. Пароль слишком короткий - + The new passwords don't match. Введенные пароли не совпадают. @@ -2880,93 +3498,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: &Название - + C&olor: &Цвет - + white белый - + blue синий - + black чёрный - + red красный - + green зелёный - + multicolor мультицветный - + colorless бесцветный - + &P/T: &Сила/Выносливость: - + &Annotation: &Примечание: - + Token data Описание фишки - - + + Add token Добавить фишку - + Remove token Удалить фишку - + Edit custom tokens Редактировать пользовательские фишки - + Please enter the name of the token: Введите имя фишки: - + Error Ошибка - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Выбранное имя конфликтует с существующей картой или токеном. @@ -3045,8 +3663,9 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games that have started - Скрыть уже начавшиеся игры + Hide started games + Hide games that have started + @@ -3081,8 +3700,9 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - &Creator name: - Имя &создателя + &Host name: + &Creator name: + @@ -3148,52 +3768,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning Предупреждение при проверке сброса пароля - + A problem has occurred. Please try to request a new password again. Возникла проблема. Попробуй запросить новый пароль еще раз - + Enter the information of the server and the account you'd like to request a new password for. Введите информации о сервере и учетной записи, для которой нужен новый пароль - + &Host: &Хост: - + &Port: &Порт: - + Player &name: &Имя игрока: - + Email: Электронная почта: - + Reset Password Challenge Сбросить пароль - + Reset Password Challenge Error Предупреждение при проверке сброса пароля - + The email address can't be empty. Необходимо указать ваш e-mail @@ -3201,37 +3821,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. Введите информации о сервере и учетной записи, для которой нужен новый пароль - + &Host: &Хост: - + &Port: &Порт: - + Player &name: &Имя игрока: - + Reset Password Request Забыл пароль - + Reset Password Error Ошибка сброса пароля - + The player name can't be empty. Необходимо указать имя игрока. @@ -3239,94 +3859,127 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning Сбросить предупреждение о пароле - + A problem has occurred. Please try to request a new password again. Возникла проблема. Попробуй запросить новый пароль еще раз - + Enter the received token and the new password in order to set your new password. Введите полученный токен и новый пароль - + &Host: &Хост: - + &Port: &Порт: - + Player &name: &Имя игрока: - + Token: Жетон: - - + + New Password: Новый пароль: - + Reset Password Сбросить пароль - + The player name can't be empty. Необходимо указать имя игрока. - - - - + + + + Reset Password Error Ошибка сброса пароля - + The token can't be empty. Необходимо указать код активации. - + The new password can't be empty. Необходимо ввести новый пароль. - + Error Ошибка - + Your password is too short. Пароль слишком короткий - + The passwords do not match. Введенные пароли не совпадают. + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + Пригласить в игру + + + + Search users... + + + + + Invite + Пригласить + + + + Cancel + + + DlgLoadDeck - + Load Deck Загрузить колоду @@ -3418,166 +4071,567 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLocalGameOptions - + Players: Игроки - + General - + Starting life total: - + Game setup options - + Remember settings - + Local game options + Настройки локальной игры + + + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server DlgMoveTopCardsUntil - + Card name (or search expressions): Название карты (или поисковый запрос) - + Number of hits: Количество: - + Auto play hits Автоматически разыгрывать найденные карты - + Put top cards on stack until... Поместить вернюю карту в стек, пока... - + No cards matching the search expression exists in the card database. Proceed anyways? В базе данных карт ничего не найдено. Все равно продолжить? - + Cockatrice Cockatrice - + Invalid filter Неправильный фильтр + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. Введите информацию о себе и о сервере, на котором хотите зарегистрироваться. На электронную почту будет отправлено письмо активации учетной записи. - + &Host: &Хост - + &Port: &Порт: - + Player &name: &Имя пользователя: - + P&assword: Пароль: - + Password (again): Подтверждение пароля: - + Email: Адрес email: - + Email (again): Подтверждение email: - + Country: Страна: - + Undefined Не определено - + Real name: Настоящее имя: - + Register to server Зарегистрироваться на сервере - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning Предкпреждение - + Your password is too short. Пароль слишком короткий - + Your passwords do not match, please try again. Введенные пароли не совпадают, повторите снова. - + Your email addresses do not match, please try again. Введенные адреса email не совпадают, повторите снова. - + The player name can't be empty. Необходимо указать имя пользователя. + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice @@ -3643,40 +4697,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Неизвестная ошибка при загрузке базы карт. - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - База карт содержит ошибки. -Cockatrice не может корректно работать с испорченной базой карт. -Возможно, Вам необходимо обновить базу карт с помощью Oracle. -Вы хотите сменить расположение файла базы карт? - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - Версия вашей базы карт сильно устарела. -Это может привести к проблемам при загрузке информации о картах или изображений. -Возможно, Вам необходимо обновить базу карт с помощью Oracle. -Вы хотите сменить расположение файла базы карт? - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3689,7 +4715,7 @@ Would you like to change your database location setting? Вы хотите изменить путь до локальной базы карт? - + File Error loading your card database. Would you like to change your database location setting? @@ -3697,7 +4723,7 @@ Would you like to change your database location setting? Вы хотите сменить расположение файла базы карт? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3705,7 +4731,7 @@ Would you like to change your database location setting? Вы хотите сменить расположение файла базы карт? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3718,106 +4744,237 @@ Would you like to change your database location setting? Вы хотите изменить путь до локальной базы карт? - - - + + + Error Ошибка - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Ваши колоды отсутствуют в указанной папке. Вернуться и задать правильный путь? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Изображения карт не найдены. Вернуться и задать правильный путь? - + Settings Настройки - + + Search settings... + + + + + OK + + + + General Основные - + Appearance Вид - + User Interface Интерфейс - + Storage - + Card Sources Источник карт - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat Чат - + Sound Звук - + Shortcuts Горячие клавиши + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check База карт: - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - Прошло более %2 после последнего обновления базы карт. + Прошло более %2 дней после последнего обновления базы карт. Выберите способ запуска приложения для обновления базы карт. Вы сможете изменить выбранный способ позднее во кладке настроке "Основные". - + Run in foreground Запустить на переднем плане - + Run in background Запустить в фоне - + Run in background and always from now on - Запустить в фоне сейчас и всегда а будущем + Запустить в фоне сейчас и всегда в будущем - + Don't prompt again and don't run Не спрашивать снова и не запускать - + Don't run this time Не запускать в этот раз @@ -3825,58 +4982,58 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next Следующий - + Previous Предыдущий - + Tip of the Day - Совет дня + Подсказка дня DlgUpdate - + Current release channel Канал актуального обновления - + Reinstall Переустановить - + Cancel Download Отменить загрузку - + Open Download Page Открыть страницу загрузки - + Check for Client Updates Проверить обновления клиента - - - - + + + + Error Ошибка - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. В Cockatrice нет поддержки SSL и вы не можете обновиться автоматически! @@ -3884,125 +5041,135 @@ Please visit the download page to update manually. Пожалуйста, посетите страницу загрузки для получения обновления. - + Downloading update: %1 Скачивание обновлений: %1 - + Checking for updates... Проверка обновлений... - + Finished checking for updates Закончена проверка обновлений - + No Update Available Нет доступных обновлений - + Cockatrice is up to date! Установлена последняя версия Cockatrice! - + You are already running the latest version available in the chosen release channel. Уже установлена последняя версия, доступная в выбранной ветке обновлений. - + Current version Текущая версия - + Selected release channel Выбранная ветка обновлений - - + + Update Available Обновление доступно - - + + A new version of Cockatrice is available! Доступна новая версия Cockatrice! - - + + New version Новая версия - - + + Released Выпущена - - + + Changelog Список изменений - + Do you want to update now? Вы хотите обновить сейчас? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. Автообновление не удалось - подходящая версия не найдена. Скачайте новую версию вручную. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. Проверьте <a href="%1">страницу релиза</a> в нашем репозитории на Github и скачайте сборку для своей системы. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error Ошибка при оновлении - + An error occurred while checking for updates: В процессе проверки обновлений произошла ошибка: - + An error occurred while downloading an update: В процессе загрузки обновления произошла ошибка: - + Installing... Установка... - + Cockatrice is unable to open the installer. Невозможно открыть установщик. - + Try to update manually by closing Cockatrice and running the installer. Попробуйте обновить вручную, закрыв Cockatrice и запустив инсталлятор. - + Download location @@ -4010,17 +5177,17 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing Очистить лог при закрытии - + Copy to clipboard Скопировать в буфер обмена - + Debug Log Лог @@ -4162,7 +5329,7 @@ You may have to manually download the new version. In %1 decks - в %1 колоде + в %1 колодах @@ -4258,15 +5425,15 @@ You may have to manually download the new version. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: - Цена: + Тилт: FilterBuilder - + Type your filter here Укажите свой фильтр @@ -4274,45 +5441,88 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete Подтвердить удаление - + Are you sure you want to delete the filter '%1'? Вы уверены что хотите удалить фильтр '%1'? - + Delete Failed Не удалось удалить - + Failed to delete filter '%1'. Не удалось удалить фильтр '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator кикнут из игры хостом или модератором - + player left the game игрок покинул игру - + player disconnected from server игрок отсоединился от сервера - + reason unknown причина неизвестна @@ -4320,140 +5530,156 @@ You may have to manually download the new version. GameSelector - - - - - - - - - + + + + + + + + + Error Ошибка - + Please join the appropriate room first. Пожалуйста, сперва войдите в соответствующую комнату. - + Wrong password. Неверный пароль. - + Spectators are not allowed in this game. Зрители не допускаются в эту игру. - + The game is already full. Все места уже заняты! - + The game does not exist any more. Эта игра больше не существует. - + This game is only open to registered users. Доступно только для зарегистрированных. - + This game is only open to its creator's buddies. Доступно только для друзей. - + You are being ignored by the creator of this game. Вы добавлены в игнор-лист данного игрока. - + Join Game Присоединиться - + Spectate Game Наблюдать - + Game Information Информация об игре - - Join Game as Judge - + + Cop&y game link + Скопировать ссылку на игру - + + Join Game as Judge + Присоединиться в качестве судьи + + + Spectate Game as Judge - + + Join game Присоединиться - - Password: - Пароль: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. Пожалуйста, сначала присоединитесь к соответствующей комнате. - + &Filter games &Фильтр игр - + C&lear filter О&чистить фильтр - + C&reate &Создать - + &Join &Присоединиться - + Join as judge - + J&oin as spectator Присоединиться как &зритель - + Join as judge spectator - + Games shown: %1 / %2 Показано игр: %1 / %2 - + Games Игры @@ -4494,105 +5720,105 @@ You may have to manually download the new version. GamesModel - + >1 day > 1 дня - + %1%2 hr short age in hours %1%2 час%1%2 часа%1%2 часов%1%2 часов - + new - создать + только что - + %1%2 min short age in minutes %1%2 минута%1%2 минут%1%2 минут%1%2 минут - + password пароль - + buddies only только друзья - + reg. users only только для зарегистрированных пользователей - + open decklists открыть деклист - - + + can chat зрители могут комментировать в чате - + see hands видеть руки игроков - + can see hands - могут видеть руки + могут видеть руки игроков - + not allowed не допускаются - + Room Комната - + Age Возраст - + Description Описание - - Creator - Создатель + + Host + - + Type Тип - + Restrictions Ограничения - + Players Количество игроков - + Spectators Зрители @@ -4600,208 +5826,325 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths Сбросить - + All paths have been reset Все пути были сброшены - - - - - - - + + + + English + Русский (Russian) + + + + + + + + + Choose path Путь - + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> + + + + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + Language settings Выбор языка - + Language: Язык: - - Version settings + + Card text & images language: - + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + + Version settings + Настройки версии + + + Card database - + Startup settings - + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) Путь (в портативном режиме редактирование недоступно) - + Paths Пути расположения - + How to help with translations Как помочь с переводом - + Decks directory: Колоды: - + Filters directory: - Фильтровать директории: + Фильтры: - + Replays directory: Повторы: - + Pictures directory: Изображения карт: - + Card database: База данных карт: - + Custom database directory: Пользовательские сеты: - + Token database: База данных токенов: - + Update channel Обновить канал - + Check for client updates on startup Проверять обновления при запуске - + Check for card database updates on startup Проверять обновления базы карт при запуске - + Don't check Не проверять - + Prompt for update Поиск обновлений - + Always update in the background Всегда обновлять на фоне - + Check for card database updates every Проверять обновления каждые - + days дней - + Notify if a feature supported by the server is missing in my client Предупреждать, если функции, используемые сервером, отсутствуют в этом клиенте. - + Automatically run Oracle when running a new version of Cockatrice Автоматически запускать Oracle в новой версии Cockatrice - + Show tips on startup Показывать советы при запуске клиента - + Last update check on %1 (%2 days ago) - Последнее проверка обновлений была %1 (%2 дней назад) + Последняя проверка обновлений была %1 (%2 дней назад) GraveyardMenu - + &Graveyard Кладбище - + &View graveyard Посмотреть кладбище - + &Move graveyard to... Переместить кладбище в... - + &Top of library Верх библиотеки - + &Bottom of library Низ библиотеки - + &All players Всем игрокам - + &Hand Рука - + &Exile Изгнание - + Reveal random card to... - Показать случайно карту... + Показать случайную карту... @@ -4819,7 +6162,7 @@ You may have to manually download the new version. Sort hand by... - + Сортировать руку по... @@ -4893,59 +6236,353 @@ You may have to manually download the new version. Показать случайную карту... + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck Создать новую колоду - + Browse Decks Посмотреть колоды - + Browse Card Database Посмотреть базу карт - + Browse EDHRec Посмотреть EDHRec - + Browse Archidekt - + Посмотреть Archidekt - + View Replays Посмотреть повтор - + Quit Выход - + Connecting... Подключение... - + Connect Подключиться - + Play Играть + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu @@ -4961,12 +6598,12 @@ You may have to manually download the new version. View &top cards of library... - Посмотреть топдек + Посмотреть верхнюю карту библиотеки View bottom cards of library... - Посмотреть нижнюю карту библиотеки + Посмотреть нижние карты библиотеки @@ -4981,12 +6618,12 @@ You may have to manually download the new version. Reveal &top cards to... - Показать топдек... + Показать верхние карты... &Top of library... - Топдек + Верх библиотеки @@ -5031,47 +6668,47 @@ You may have to manually download the new version. &Play top card - Сыграть топдек + Сыграть верхнюю карту Play top card &face down - Сыграть топдек лицом вниз + Сыграть верхнюю карту лицом вниз Put top card on &bottom - Положить топдек на низ библиотеки + Положить верхнюю карту в низ библиотеки Move top card to grave&yard - Положить топдек в кладбище + Положить верхнюю карту на кладбище Move top card to e&xile - Положить топдек в изгнание + Положить верхнюю карту в изгнание Move top cards to &graveyard... - Положить верхние карты в кладбище + Положить верхние карты на кладбище Move top cards to graveyard face down... - + Положить верхние карты на кладбище лицом вниз... Move top cards to &exile... - Положить верхние карты в кладбище + Положить верхние карты в изгнание Move top cards to exile face down... - + Положить верхние карты в изгнание лицом вниз... @@ -5086,12 +6723,12 @@ You may have to manually download the new version. &Draw bottom card - &Взять нижнюю карту + &Взять нижнюю карту библиотеки D&raw bottom cards... - Взять нижнюю карту библиотеки + Взять нижние карты библиотеки @@ -5106,17 +6743,17 @@ You may have to manually download the new version. Move bottom card to grave&yard - Положить нижнюю карту библиотеки в кладбище + Положить нижнюю карту библиотеки на кладбище Move bottom card to e&xile - Положить нижнюю карту библиотеки в кладбище + Положить нижнюю карту библиотеки в изгнание Move bottom cards to &graveyard... - Положить нижние карты библиотеки в кладбище + Положить нижние карты библиотеки на кладбище @@ -5136,7 +6773,7 @@ You may have to manually download the new version. Put bottom card on &top - Переместить нижнюю карту на топдек + Переместить нижнюю карту на верх библиотеки @@ -5163,411 +6800,432 @@ You may have to manually download the new version. MainWindow - - + + Player %1 Игрок %1 - + Load replay Загрузить повтор - + About Cockatrice О программе - + Version Версия - + Cockatrice Webpage Страница Cockatrice - + Project Manager: Руководитель проекта: - + Past Project Managers: Предыдущие руководители проекта: - + Developers: Разработчики: - + Our Developers Наши разработчики - + Help Develop! Помочь в разработке! - + Translators: Переводчики: - + Our Translators Наши переводчики - + Help Translate! Помочь с переводом! - + Support: Поддержать проект: - + Report an Issue Сообщить о проблеме - + Troubleshooting Устранение неполадок - + F.A.Q. ЧАВо - - + + Error Ошибка - + &Connect... Подключение... &С - + &Disconnect П&рервать подключение - + Start &local game... &Начать локальную игру... - + &Watch replay... &Смотреть повтор... - + &Full screen Полный экран &F - + &Register to server... &Регистрация на сервере... - + &Restore password... - + Восстановить пароль... - + &Settings... &Настройки - + &Exit &Выход - + A&ctions &Действия - + &Cockatrice &Cockatrice - + C&ard Database &База карт - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder Открыть папку с изображениями - + Open custom sets folder Открыть папку с изданиями - + Add custom sets/cards Добавить издания/карты - + Reload card database - + Tabs Быстрый доступ - + &Help &Справка - + &About Cockatrice О про&грамме - + &Tip of the Day - + Подсказка дня - + Check for Client Updates - + Проверить обновления клиента - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder + Открыть папку настроек + + + + Re-run Onboarding Wizard... - + Show/Hide + Показать/скрыть + + + + Sign-in cancelled - + New Version - + Новая версия - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Установка Cockatrice завершена - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + + Are you sure? + Вы уверены? + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database База карт - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" Cockatrice не может загрузить базу карт. Обновить вашу базу карт сейчас? Если Вы не уверены, или впервые запустили программу, нажмите "Да" - - + + Yes Да - - + + No Нет - + Open settings Открыть настройки - + New sets found Найдены новые издания - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + Yes, always enable - + View sets Просмотреть издания - + Welcome Добро пожаловать - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - + Information Информация - + A card database update is already running. Обновление базы карт уже идет. - + Unable to run the card database updater: Не удалось запустить обновление базы карт: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - - - - - + + + + + Load sets/cards Загрузить издания/карты - + Selected file cannot be found. Выбранный файл не найден. - + You can only import XML databases at this time. На данный момент вы только можете предостовлять базы данных в формате XML. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. Новые издания/карты успешно добавлены. Сейчас Cockatrice перезагрузит базу карт. - + Sets/cards failed to import. Не удалось импортировать издания/карты. @@ -5582,17 +7240,17 @@ Cockatrice will now reload the card database. Display type: - + Режим отображения: pie - + круговая диаграмма bar - + гистограмма @@ -5612,7 +7270,7 @@ Cockatrice will now reload the card database. Cancel - + Отменить @@ -5628,7 +7286,7 @@ Cockatrice will now reload the card database. Group By: - + Группировать по: @@ -5735,7 +7393,7 @@ Cockatrice will now reload the card database. Colors: - + Цвета: @@ -5761,7 +7419,7 @@ Cockatrice will now reload the card database. Mana Production + Devotion - + Производство маны + преданность @@ -5772,615 +7430,615 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play из игры - + from their graveyard из их кладбищ - + from exile из изгнания - + from their hand - из их рук + из своей руки - + the top card of %1's library верхняя карта библиотеки %1 - + the top card of their library верхнюю карту своей библиотеки - + from the top of %1's library с верха библиотеки %1 - + from the top of their library с верха своей библиотеки - + the bottom card of %1's library нижняя карта библиотеки %1 - + the bottom card of their library нижнюю карту своей библиотеки - + from the bottom of %1's library Со дна библиотеки %1 - + from the bottom of their library с низа своей библиотеки - + from %1's library из библиотеки %1 - + from their library из своей библиотеки - + from sideboard из сайдборда - + from the stack из стека - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 оставляет верхнюю карту %2 открытой. - + %1 is not revealing the top card %2 any longer. Верхняя карта %2 %1 больше не остается открытой. - + %1 can now look at top card %2 at any time. - + %1 теперь может в любой момент посмотреть верхнюю карту %2. - + %1 no longer can look at top card %2 at any time. - + %1 больше не может в любой момент посмотреть верхнюю карту %2 . - + %1 attaches %2 to %3's %4. %1 прикрепляет %2 к %4 под контролем %3. - + %1 has conceded the game. %1 сдался. - + %1 has unconceded the game. - + %1 has restored connection to the game. %1 восстановил связь с игрой. - + %1 has lost connection to the game. %1 потерял связь с игрой. - + %1 points from their %2 to themselves. %1 указывает с их %2 на себя. - + %1 points from their %2 to %3. %1 указывает с их %2 на %3. - + %1 points from %2's %3 to themselves. %1 указывает с %3 под контролем %2 на себя. - + %1 points from %2's %3 to %4. %1 указывает с %3, контролируемого %2, на %4. - + %1 points from their %2 to their %3. %1 указывает с их %2 на их %3. - + %1 points from their %2 to %3's %4. %1 указывает с их %2 на %4 под контролем %3. - + %1 points from %2's %3 to their own %4. %1 указывает с %3 под контролем %2 на %4, владельцем которого они являются. - + %1 points from %2's %3 to %4's %5. %1 указывает с %3 контролируемого %2 на %5 под контролем %4. - + %1 creates a face down token. - + %1 создает фишку лицом вниз. - + %1 creates token: %2%3. %1 создает фишку: %2%3. - + %1 has loaded a deck (%2). %1 загрузил колоду (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). %1 загрузил колоду с %2 картами из сайдборда (%3). - + %1 destroys %2. %1 уничтожает %2. - + a card карту - + %1 gives %2 control over %3. %1 передает %2 контроль над %3. - - - %1 puts %2 into play%3 face down. - - - - - %1 puts %2 into play%3. - %1 поместил %2 на поле битвы %3. - - - - %1 puts %2%3 into their graveyard face down. - - + %1 puts %2 into play%3 face down. + %1 кладет %2 на поле битвы %3 лицом вниз. + + + + %1 puts %2 into play%3. + %1 кладет %2 на поле битвы %3. + + + + %1 puts %2%3 into their graveyard face down. + %1 кладет %2%3 на своё кладбище лицом вниз. + + + %1 puts %2%3 into their graveyard. %1 кладет %2%3 на своё кладбище. - + %1 exiles %2%3 face down. - + %1 изгоняет %2%3 лицом вниз. - + %1 exiles %2%3. %1 изгоняет %2%3. - + %1 moves %2%3 to their hand. %1 кладет %2%3 в свою руку. - + %1 puts %2%3 into their library. %1 кладет %2%3 в свою библиотеку. - + %1 puts %2%3 onto the bottom of their library. - + %1 кладет %2%3 в низ своей библиотеки. - + %1 puts %2%3 on top of their library. %1 кладет %2%3 на верх своей библиотеки. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 кладет %2%3 в свою библиотеку %4 сверху. - + %1 moves %2%3 to sideboard. %1 поместил %2%3 в сайд. - + %1 plays %2%3 face down. - + %1 разыгрывает %2%3 лицом вниз. - + %1 plays %2%3. %1 разыгрывает %2%3. - + %1 moves %2%3 to custom zone '%4' face down. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 пытается взять карту из пустой библиотеки. - + %1 draws %2 card(s). %1 берет %2 карту.%1 берет %2 карты.%1 берет %2 карт.%1 берет %2 карты. - + %1 is looking at %2. %1 просматривает %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 переворачивает %2 лицом вниз. - + %1 turns %2 face-up. - + %1 переворачивает %2 лицом вверх. - + The game has been closed. Игра закрыта. - + The game has started. Игра началась. - + You are flooding the game. Please wait a couple of seconds. - + Вы слишком быстро отправляете сообщения. Пожалуйста, подождите пару секунд. - + %1 has joined the game. %1 присоединился к игре. - + %1 is now watching the game. - %1 вошел как зритель. + %1 теперь наблюдает за игрой. - + You have been kicked out of the game. Вы были отключены от игры - - - %1 has left the game (%2). - - - %1 is not watching the game any more (%2). - + %1 has left the game (%2). + %1 покинул(а) игру (%2). - + + %1 is not watching the game any more (%2). + %1 больше не наблюдает за игрой (%2). + + + %1 is not ready to start the game any more. %1 пока не готов начать игру. - + %1 shuffles their deck and draws a new hand of %2 card(s). %1 тасует колоду и берет новую руку из %2 карты%1 тасует колоду и берет новую руку из %2 карт%1 тасует колоду и берет новую руку из %2 карт%1 тасует колоду и берет новую руку из %2 карт - + %1 shuffles their deck and draws a new hand. - + %1 тасует колоду и берет новую руку. - + You are watching a replay of game #%1. Вы просматриваете запись игры #%1. - + %1 is ready to start the game. - %1 готов начать игру. + %1 готов(а) начать игру. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. %1 открывает %2 для %3. - + %1 reveals %2. %1 открывает %2. - + %1 randomly reveals %2%3 to %4. %1 случайным образом открывает %2%3 для %4. - + %1 randomly reveals %2%3. %1 случайным образом открывает %2%3. - + %1 peeks at face down card #%2. %1 указывает на перевернутую рубашкой вверх карту #%2. - + %1 peeks at face down card #%2: %3. %1 указывает на перевернутую рубашкой вверх карту #%2: %3. - + %1 reveals %2%3 to %4. %1 открывает %2%3 для %4. - + %1 reveals %2%3. %1 открывает %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads Орел - + Tails Решка - + %1 flipped a coin. It landed as %2. %1 подбросил монету. Выпал(а) %2. - + %1 rolls a %2 with a %3-sided die. %1 выбросил %2 на %3-гранном кубике. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. - + Ход игрока %1. - + %1 sets annotation of %2 to %3. %1 помечает %2 "%3". - + %1 places %2 %3%4 counter(s) on %5 (now %6). - + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + %1 failed to undo their last draw. - + %1 sets counter %2 to %3 (%4%5). %1 устанавливает жетон %2 на %3 (%4%5). - + %1 sets %2 to not untap normally. %2 теперь не разворачивается как обычно (%1). - + %1 sets %2 to untap normally. %2 теперь разворачивается как обычно (%1). - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. %1 заблокировал(а) свой сайдборд. - + %1 has unlocked their sideboard. %1 разблокировал(а) свой сайдборд. - + %1 taps their permanents. %1 поворачивает свои перманенты. - + %1 untaps their permanents. %1 разворачивает свои перманенты. - + %1 taps %2. %1 поворачивает %2. - + %1 untaps %2. %1 разворачивает %2. - + %1 shuffles %2. %1 размешал %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. %1 открепляет %2. - + %1 undoes their last draw. %1 отменяет последние взятия. - + %1 undoes their last draw (%2). %1 отменяет последние взятия (%2). @@ -6388,115 +8046,125 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Слово1 Слово2 Слово3 - + Add New Message - + Edit Message - + Remove Message - + Add message Добавить сообщение - - + + Message: Сообщение: - + Edit message - + Chat settings Настройки чата - + Custom alert words Пользовательские предупреждения - + Enable chat mentions Включить упоминания в чате - + Enable mention completer Включить автозавершение упоминаний - + In-game message macros Макросы сообщений игрового чата - + How to use in-game message macros Как использовать внутриигровые макросы для сообщений - + Ignore chat room messages sent by unregistered users Не уведомлять о сообщениях в чате от незарегистрированных пользователей - + Ignore private messages sent by unregistered users Не уведомлять о личных сообщениях от незарегистрированных пользователей - + Ignore private messages sent by non-buddy users - + Не уведомлять о личных сообщениях от пользователей не из списка друзей - - + + Invert text color Инвертировать цвет текста - + Enable desktop notifications for private messages Включить оповещения о личных сообщениях - + Enable desktop notification for mentions Включить оповещения об упоминаниях - + Enable room message history on join Отображать историю сообщений при присоединении к комнате. - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) (Шестнадцатеричный цвет) - + Separate words with a space, alphanumeric characters only Разделять слова пробелом, только для букв и цифр @@ -6504,42 +8172,42 @@ Cockatrice will now reload the card database. MoveMenu - + Move to - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + T&able - + &Hand - + &Graveyard - + &Exile @@ -6564,7 +8232,7 @@ Cockatrice will now reload the card database. Loyalty - + Верность @@ -6618,148 +8286,134 @@ Cockatrice will now reload the card database. PaletteEditorDialog - - + + Reset - - - Apply - - - - - - Save && Apply - - - - + ▼ Edit Palette - - + + ▶ Edit Palette - + Palette Editor — %1 - + <b>Palette Editor</b> &nbsp;·&nbsp; %1 - - This theme ships no default palette files - - - - - Replace current colours with the theme author's defaults - - - - - Switch between the light and dark palette files + + + Save - Editing: + Delete this scheme's custom palette and revert to the theme default (or the application palette) - Show or hide the per-role colour grid for manual tweaks + Switch between the light and dark palette files + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + ↺ Revert to theme default - + Discard unsaved edits and restore the last saved palette - - Preview this palette without saving to disk + + Cannot save: this theme has no writable directory - + Write palette-%1.toml and reload the theme - - Cannot save: this theme has no directory on disk - - - - + + Save failed - + + Could not write %1 to: %2 - - - No default found - - - - - No default palette file found for the "%1" scheme. - - PaletteGridWidget - + Active - + Disabled - + Inactive - + Normal interactive state - + Widget is disabled / not interactive - + Window is in background / unfocused + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + Phase @@ -6882,30 +8536,21 @@ Cockatrice will now reload the card database. Заключительный шаг - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - ru - - PlayerActions - - - - + + + + grave - - - - + + + + exile @@ -7024,14 +8669,270 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" + Игрок "%1" + + + + &Counters + &Жетоны + + + + PlaymatCollectionDialog + + + Default Playmats - - &Counters - &Жетоны + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + @@ -7054,12 +8955,12 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelector - + Display Navigation Buttons - + Printing Selector @@ -7067,25 +8968,65 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -7116,28 +9057,28 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -7153,95 +9094,103 @@ This setting means you'll only see the default printing for each card, instead o PtMenu - + Power / toughness - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services Сервисы - + Hide %1 Скрыть %1 - + Hide Others Скрыть остальные - + Show All Показать все - + Preferences... Параметры... - + Quit %1 Выйти %1 - + About %1 О %1 @@ -7249,17 +9198,17 @@ This setting means you'll only see the default printing for each card, instead o QObject - + Cockatrice card database (*.xml) База карт Cockatrice (*.xml) - + All files (*.*) Все файлы (*.*) - + Cockatrice replays (*.cor) Записи игр Cockatrice (*.cor) @@ -7279,17 +9228,22 @@ This setting means you'll only see the default printing for each card, instead o Фишки - + + Maybeboard + + + + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? - + Версия данной колоды в формате .cod уже существует. Перезаписать? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7300,7 +9254,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7311,100 +9265,110 @@ Are you sure you would like to disable this feature? - + Confirm Change + + + Art: %1 + + + + + Open deck + Открыть колоду + QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Да - + Yes to &All - + &No - + Нет - + N&o to All - + Abort - + Retry - + Попробовать снова - + Ignore - + Close - + Cancel - + Отменить - + Discard - + Help - + Apply - + Применить - + Reset - + Restore Defaults @@ -7412,121 +9376,189 @@ Are you sure you would like to disable this feature? QuickSetupPanel - + %1% - + <b>Quick Setup</b> - + Generate all palette roles automatically from a single accent colour - + Accent: - + Primary hue. Used directly for highlights and links. At high intensity it also tints buttons and backgrounds. - + Intensity: - + Subtle - + Full colour - + 0–30 Subtle tint — only highlights and links change hue 30–70 Accented — buttons, tooltips, and borders join in 70–100 Full colour — backgrounds, everything - + 70% - - - - - Generate ↓ - - - - - Derive all palette roles from the accent colour above. -Fine-tune individual colours in the grid afterwards. - + 70% RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name Название - + ID ID - + Upload time Дата загрузки + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID ID - + Name Имя - + Players Игроки - + Keep Хранение - + Time started Время начала - + Duration (sec) Длительность (сек) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu @@ -7568,37 +9600,37 @@ Fine-tune individual colours in the grid afterwards. RoomSelector - + Rooms Комнаты - + Joi&n &Присоединиться - + Room Комната - + Description Пометка - + Permissions Разрешения - + Players Игроки - + Games Игры @@ -7606,7 +9638,7 @@ Fine-tune individual colours in the grid afterwards. SayMenu - + S&ay @@ -7614,32 +9646,32 @@ Fine-tune individual colours in the grid afterwards. SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - + Нажмите клавишу/сочетание клавиш, которую Вы хотите назначить для этого действия - + Invalid key - + Shortcut already in use by: - + Clear - + Restore default @@ -7672,56 +9704,92 @@ Fine-tune individual colours in the grid afterwards. Дата релиза + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Действие: - + Shortcut: - + + Shortcut editor + + + + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -7731,7 +9799,7 @@ Fine-tune individual colours in the grid afterwards. Action - + Действие @@ -7775,12 +9843,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -7788,27 +9856,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds Включить &звуки - + Current sounds theme: Звуковая тема: - + Test system sound engine Протестировать системный звук - + Sound settings Настройки звука - + Master volume Громкости @@ -7816,48 +9884,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection Нет подключения к Интернету - + Error - + Ошибка - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -7865,157 +9933,170 @@ Please check your shortcut settings! StableReleaseChannel - + Default По умолчанию - + No reply received from the release update server. Сервер обновлений версии не отвечает. - + Invalid reply received from the release update server. Ошибка отклика сервера обновлений версии. - + No reply received from the tag update server. Сервер обновлений меток не отвечает. - + Invalid reply received from the tag update server. Ошибка отклика сервера обновлений меток. + + StatsTally + + + Total Power + + + + + Total Toughness + + + StorageSettingsPage - - - + + + Success - + Cached card pictures have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + In-memory (currently loaded) card pictures have been reset. - + Card Picture Loader Caching Method: - + Метод кэширования: - + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. - + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. - + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. - + Delete Cached Images - + Delete Saved Images - + Clear In-Memory Images - + Card Picture Loader Cache Method - + Метод кэширования для загрузчика изображений карт - + Network Cache - + Filesystem - + Файловая система - + In-Memory Picture Cache - + Network Cache Size: - + On-disk cache for downloaded pictures - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + In-memory cache for pictures not currently on screen - + Naming scheme: - + Day(s) @@ -8023,17 +10104,33 @@ Please check your shortcut settings! TabAccount - + + + My Reports + + + + Add to Buddy List - + Добавить в список друзей - + Add to Ignore List + Добавить в игнор-лист + + + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. - + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account @@ -8118,7 +10215,7 @@ Please check your shortcut settings! Error - + Ошибка @@ -8154,166 +10251,166 @@ Please check your shortcut settings! TabArchidekt - - - + + + Desc. - - + + AND - - + + Require ALL selected colors - - + + Deck name... - - + + Owner... - - + + Packages - - + + Advanced Filters - + Bracket: - - + + Any - - + + Contains card... - - + + Commander... - - + + Tag... - - + + Deck Size - + Cards: - - + + Asc. - + Sort by: - + Filter by: - + Display Settings - - + + Search - - + + Formats Archidekt: - + Archidekt: TabCardArtRules - + Card: - + ProviderId: - + Mode: - + Reason: - + Type a card name... - + Add rule - + Remove rule - + Refresh @@ -8326,52 +10423,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info Информация о карте - + Deck Колода - + Filters Фильтры - + &View Вид - + Card Database - + Printing - + Visible Видимый - + Floating Плавающий - + Reset layout Сбросить слой - + Deck: %1 Колода: %1 @@ -8379,49 +10476,49 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - + &View - + Printing - + Visible - + Floating - + Reset layout @@ -8429,22 +10526,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -8452,139 +10549,237 @@ Please check your shortcut settings! TabDeckStorage - + Local file system Локальная файловая система - + Server deck storage Хранилище колод на сервере - - + + Open in deck editor Открыть колоду в редакторе - + Rename deck or folder - + Upload deck Загрузить колоду в хранилище - + Download deck Загрузить колоду из хранилища - - - - + + + + New folder Новая папка - - + + Delete Удалить - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + + + + Error - + Ошибка - + Rename failed - - + + + Invalid deck file - + Enter deck name Введите название колоды - + This decklist does not have a name. Please enter a name: У этой колоды нет названия. Пожалуйста, введите имя: - + Unnamed deck Безымянная колода - + Failed to upload deck to server - + Delete local file Удалить файл - + Are you sure you want to delete the selected files? - + Вы уверены, что хотите удалить выбранные файлы? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: Название новой папки: - + Deck Storage @@ -8592,250 +10787,517 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec EDHREC: - + EDHREC: TabEdhRecMain - + &Cards - + Top Commanders - + Tags - + Search for a card ... - + Найти карту... - + Search - + Искать EDHRec: - + EDHRec: TabGame - - - + + + Replay Запись игры - - + + Game Игра - - + + Player List Список игроков - - + + + Invite + + + + + Card Info Информация о карте - - + + Messages Сообщения - - + + Replay Timeline Полоса прокрутки записи - + &Phases &Фазы - + &Game &Игра - + Next &phase Следующая &фаза - + Next phase with &action - + Next &turn Следующий &ход - + Reverse turn order - + &Remove all local arrows &Удалить все указатели - + Rotate View Cl&ockwise Повернуть вид по часовой стрелке - + Rotate View Co&unterclockwise Повернуть вид против часовой стрелки - + Game &information Информация &об игре - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede - - - + + + &Concede &Сдаться... - + &Leave game Покинуть и&гру - + C&lose replay З&акрыть повтор - + &Focus Chat &Установить фокус на чат - + &Say: Ска&зать: - + Selected cards - + &View Вид - + Visible Видимый - + Floating Плавающий - + Reset layout Сбросить слой - + Concede Сдаться - + Are you sure you want to concede this game? Вы точно хотите сдаться? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game Покинуть игру - + Are you sure you want to leave this game? Вы уверены, что хотите уйти? - + A player has joined game #%1 - + Игрок присоединился к игре №%1. - + %1 has joined the game - + %1 присоединился к игре - + You have been kicked out of the game. Вы были отключены от игры @@ -8845,164 +11307,164 @@ Please enter a name: Home - + Меню TabLog - + Logs Логи - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName Время;ИмяОтправителя;IPОтправителя;Сообщение;IDПолучателя;ИмяПолучателя - + Room Logs Логи Комнаты - + Game Logs Логи Игры - + Chat Logs Логи Чата - - + + Error Ошибка - + You must select at least one filter. Вы должны выбрать хотя бы один фильтр - + You have to select a valid number of days to locate. Вы должны указать верное количество дней для размещения. - + Username: Имя пользователя: - + IP Address: IP адрес - + Game Name: Имя игры: - + GameID: ID Игры: - + Message: Сообщение: - + Main Room Основная комната - + Game Room Игровая комната - + Private Chat Приватный чат - + Past X Days: Последние X Дней: - + Today Сегодня - + Last Hour За последний час - + Maximum Results: Максимальный результат: - + At least one filter is required. The more information you put in, the more specific your results will be. Должен быть как минимум один фильтр. Чем больше информации Вы укажете, тем точнее будет результат. - + Get User Logs Получить логи пользователя - + Clear Filters Очистить фильтры - + Filters Фильтры - + Log Locations Размещение логов - + Date Range Интервал дат - + Maximum Results Максимальный результат - - + + Message History История сообщений - + Failed to collect message history information. Не удалось собрать историю сообщений. - + There are no messages for the selected filters. Нет сообщений для указанных фильтров. @@ -9010,218 +11472,558 @@ The more information you put in, the more specific your results will be. TabMessage - + Private &chat Приватный чат - + &Leave &Покинуть - + %1 - Private chat %1 - Приватный чат - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. Это пользователь добавил Вас в игнор-лист и не видит ваши сообщения в главном чате, а Вы не можете присоединяться к созданным им (ею) играм. - + Private message from Личное сообщение от - + %1 has left the server. %1 отключился. - + %1 has joined the server. %1 зашел на сервер. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system Локальная файловая система - + Server replay storage Хранилище записей игр на сервере - - + + Watch replay Посмотреть повтор - + Rename - + Переименовать - - + + New folder - - + + Delete Удалить - + Open replays folder - + Download replay Скачать запись игры - + Toggle expiration lock Переключить блокировку срока - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Новое название: - + Error - + Ошибка - + Rename failed - + Name of new folder: - + Delete local file Удалить локальный файл - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay Удалить запись игры @@ -9231,65 +12033,478 @@ The more information you put in, the more specific your results will be. + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + Готово. + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - - Friends - - - - - Online - - - - - Ignored - - - - + &Say: &Сказать: - + Chat Чат - + &Room &Комната - + &Leave room &Покинуть комнату - + &Clear chat & Обновить чат - + Chat Settings... Установки чата - + mentioned you. упомянул(а) Вас. - + Click to view Нажмите для просмотра - + You are flooding the chat. Please wait a couple of seconds. Кажется, Вы нафлудили. Пожалуйста, подождите пару секунд. @@ -9297,35 +12512,35 @@ The more information you put in, the more specific your results will be. TabServer - + Server Сервер - - - - + + + + Error Ошибка - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -9333,97 +12548,173 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - + EDHRec - + Archidekt - + Archidekt - + Home - + Меню - + &Visual Deck Storage - + Visual Database Display - + Server - + Сервер - + Account - + Учетная запись - + Deck Storage - + Game Replays - + Administration - + Logs - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? Вы уверены? - + There are still open games. Are you sure you want to quit? У вас все еще есть активные игры. Уверены, что хотите выйти? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + Вы заблокировали игрока %1. Чтобы написать ему/ей личное сообщение, разблокируйте его/её. + + + + Game #%1 + Игра №%1 + + + + Game #%1 — %2 + + + + Click to view - + Your buddy %1 has signed on! - + Друг %1 подключился к серверу! - + Unknown Event Неопознанное событие - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -9434,38 +12725,38 @@ To update your client, go to Help -> Check for Updates. Чтобы обновить клиент, перейдите в Справка-> Проверка обновлений. - + Idle Timeout Исчерпано время бездействия - + You are about to be logged out due to inactivity. Вы были отключены из-за отсутствия активности. - + Promotion Повышение - + You have been promoted. Please log out and back in for changes to take effect. - + Warned Предупрежден - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. Вы получили предупреждение по причине %1. Пожалуйста, воздержитесь от подобных действий, иначе в ваш адрес будут применены дальнейшие меры. При возникновении каких-либо вопросов напишите личное сообщение любому модератору. - + You have received the following message from the server. (custom messages like these could be untranslated) Вы получили сообщение от сервера. @@ -9485,6 +12776,34 @@ Please refrain from engaging in this activity or further actions may be taken ag + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -9499,6 +12818,65 @@ Please refrain from engaging in this activity or further actions may be taken ag Невозможно проанализировать колоду + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + Тема: + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -9519,42 +12897,42 @@ Please refrain from engaging in this activity or further actions may be taken ag Life - + Жизнь White - + Белый Blue - + Синий Black - + Черный Red - + Красный Green - + Зеленый Colorless - + Бесцветный Other - + Прочее @@ -9568,274 +12946,319 @@ Please refrain from engaging in this activity or further actions may be taken ag UserCardArtSettingsDialog - + Card Art Settings - + Настройки картинки - + Type a card name... - + Введите название карты... - + Card name: + Название карты: + + + + Printing: + Издание: + + + + Card + Детали + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. - - Card ProviderId: - + + Banner + Баннер - + Left margin (%): - + Отступ слева (%): - + Right margin (%): - + Отступ справа (%): - - Vertical offset: - - - - - Zoom: - - - - - Parameters - - - - - Preview - - - - + Remove Banner Card - + Удалить баннер UserContextMenu - + User &details &Детали пользователя - + Private &chat &Приватный чат - + Show this user's &games Показать &игры этого пользователя - + + View this user's &public decks + + + + Add to &buddy list Добавить в &список друзей - + Remove from &buddy list Удалить из &списка друзей - + Add to &ignore list Добавить в &игнор-лист - + Remove from &ignore list Удалить из &игнор-листа - + Kick from &game Выкинуть из &игры - - - Warn user - Дать предупреждение пользователю - - - - View user's war&n history - Просмотреть &историю предупреждений пользователя - - - - Ban from &server - Забанить на &сервере - - - - View user's &ban history - Просмотреть &историю банов пользователя - - - - &Promote user to moderator - &Повысить пользователя до модератора - - - - Dem&ote user from moderator - &Разжаловать пользователя из модератора - - - - Promote user to &judge - - - Demote user from judge + Report user + Warn user + Дать предупреждение пользователю + + + + View user's war&n history + Просмотреть &историю предупреждений пользователя + + + + Ban from &server + Забанить на &сервере + + + + View user's &ban history + Просмотреть &историю банов пользователя + + + + &Promote user to moderator + &Повысить пользователя до модератора + + + + Dem&ote user from moderator + &Разжаловать пользователя из модератора + + + + Promote user to &judge + + + + + Demote user from judge + + + + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes - - - Error + Investigate user - + + + + Error + Ошибка + + + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Вы добавлены в игнор-лист игроком %1 и не можете видеть его/её игры. - + Could not get %1's games. - + %1's games Игры %1 - - - + + + Ban History История банов - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason ВремяБана;Модератор;ПродолжительностьБана;ПричинаБана;ВидимаяПричина - + User has never been banned. Пользователя никогда не банили. - + Failed to collect ban information. - - - + + + Warning History История предупреждений - + Warning Time;Moderator;User Name;Reason ВремяПредупреждения;Модератор;ИмяПольозователя;Причина - + User has never been warned. Пользователя никогда не предупреждали. - + Failed to collect warning information. - + Failed to get admin notes. - - + + Success Успешно - + Successfully promoted user. Пользователь успешно повышен. - + Successfully demoted user. Пользователь успешно понижен. - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + Kick Player - + Are you sure you want to kick this player from the game? - - - + + + Failed Не удалось - + Failed to promote user. Не удалось повысить пользователя. - + Failed to demote user. Не удалось понизить пользователя. - + Copy hash to clipboard - + Remove this user's messages @@ -9873,361 +13296,370 @@ Please refrain from engaging in this activity or further actions may be taken ag Администратор - + Moderator Модератор - + Registered user Зарегистрированный пользователь - - + + Unregistered user Рандом - + Judge - + Unknown Неизвестный - + The entered password does not match your account. - + Введенный пароль неверен. - - - - + + + + Information Информация - + User information updated. Информация о пользователе обновлена. - - - - - - + + + + + - - - + + + + Error Ошибка User Information - + Информация о пользователе Real Name: - + Настоящее имя: User Level: - + Статус: Edit Banner Card + Сменить баннер + + + + Developer - + %n Year(s), amount of years (only shown if more than 0) - + %n год%n года%n лет%n лет - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + %10%n день %20%10%n дня %20%10%n дней %20%10%n дней %20 - + Enter Password - + Введите пароль - + Password verification is required in order to change your email address - + Для смены почтового ящика необходимо ввести пароль - - - + + + An error occurred while trying to update your user information. - + The selected card is blacklisted on this server or another error occurred. - + Banner card removed. - + Banner card updated. - + This server does not permit you to update your user informations. Этот сервер не позволяет обновить пользовательскую информацию. - + Password changed. Пароль изменен. - + This server does not permit you to change your password. Этот сервер не позволяет Вам сменить пароль. - + The new password is too short. Новый пароль слишком короткий. - + The old password is incorrect. Предыдущий пароль неверен. - + Avatar updated. Аватар изменен. - + This server does not permit you to update your avatar. Этот сервер не позволяет Вам изменить аватар. - + An error occured while trying to update your avatar. - - - This server does not permit you to update your user informations. - An error occured while trying to updater your avatar. - Этот сервер не позволяет обновить пользовательскую информацию. - UserInfoPopup - - + + Games - + Chat - + Open private chat - + Profile - + View user profile - + Show this user's games - + + Invite + + + + + Invite to your game + + + + − Buddy - + Remove from buddy list - + Удалить из списка друзей - + + Buddy - + Add to buddy list - + Добавить в список друзей - + − Ignore - + Remove from ignore list - + + Ignore - + Add to ignore list - + Добавить в игнор-лист - + Ban - + Ban from server - + Warn - + Warn user - + Ban log - + View ban history - + Warn log - + View warning history - + Notes - + View admin notes - + − Mod - + Demote from moderator - + + Mod - + Promote to moderator - + − Judge - + Demote from judge - + + Judge - + Promote to judge - + Join game - + Spectate - - + + Loading games… - + Could not load games. - + No active games. @@ -10235,223 +13667,344 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings Основные настройки интерфейса - + &Double-click cards to play them (instead of single-click) &Двойной клик, чтобы разыграть карту (вместо одинарного) - + &Clicking plays all selected cards (instead of just the clicked card) &Клик для разыгрывания всех выбранных карт (вместо только выбранной) - + &Play all nonlands onto the stack (not the battlefield) by default &Помещать все заклинания в стек при разыгрывании (вместо поля боя) - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed Закрыть окно просмотра карт когда последняя карта удалена из него - + Auto focus search bar when card view window is opened - + Annotate card text on tokens Изменить текст карты на токенах - + Show selection count during drag selection - + Show total selection count - - Show subtype breakdown in selection tally + + + Disabled - + + + Enabled + + + + + + Automatic + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + Use tear-off menus, allowing right click menus to persist on screen Разрешить закрепление контекстных меню на экране - + Keep game chat focused when clicking in game (Note: disables card view search bar) - + Notifications settings - + Настройки оповещений - + Enable notifications in taskbar Включить оповещения в панели задач. - + Notify in the taskbar for game events while you are spectating Уведомлять об игровых событиях в панели задач, когда вы наблюдаете за игрой - + Notify in the taskbar when users in your buddy list connect Уведомлять при заходе в игру друга - + Animation settings Настройки анимации - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation &Анимировать поворот/разворот карты - - Deck editor/storage settings - Редактор колод/Настройки хранилища + + &Arrow draw animation + - + + Life counter flash + + + + + Battlefield flash on damage + + + + + Deck editor/storage settings + Настройки редактора/хранилища колод + + + Open deck in new tab by default Открывать колоду в новой вкладке - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby Использовать графическое представление карт в лобби игры - + Use selection animation for Visual Deck Storage Использовать анимацию при выборе карты в графическом хранилище - + When adding a tag in the visual deck storage to a .txt deck: - - do nothing + + Do nothing - - ask to convert to .cod + + Ask to convert to .cod - - always convert to .cod - всегда конвертировать в .cod + + Always convert to .cod + - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings - + Buffer time for backwards skip via shortcut: Время отката назад при нажатии шортката + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 - + Пользователей на сервере: %1 - + Users in this room: %1 - + Пользователей в комнате: %1 - + Buddies online: %1 / %2 + Друзья онлайн: %1 / %2 + + + + Ignored users online: %1 / %2 + Игнорируемые пользователи онлайн: %1 / %2 + + + + Buddies - - Ignored users online: %1 / %2 + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) UtilityMenu - + Increment all card counters Добавить по одному каждого жетона - + &Untap all permanents - + R&oll die... - + Flip coin - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -10488,22 +14041,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... - + Save Filter - + Save all currently applied filters to a file - + Enter filename... @@ -10511,72 +14064,72 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterToolbarWidget - + Sort by - + Filter by - + Save and load filters - + Filter by exact card name - + Filter by card main-type - + Filter by card sub-type - + Filter by set - + Filter by format legality - + Save/Load - + Name - + Main Type - + Sub Type - + Sets - + Formats @@ -10678,7 +14231,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -10686,19 +14239,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -10744,28 +14297,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - - + + Visual - + Loading database ... - + Clear all filters - + Table @@ -10773,43 +14326,43 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat @@ -10825,12 +14378,12 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -10838,17 +14391,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -10856,7 +14409,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -10864,52 +14417,57 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + Show Color Identity - + Show Tags On Deck Previews - + + Show Upload Time + + + + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -10917,7 +14475,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -10925,40 +14483,45 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Сорт. от А до Я (имя колоды) - + Sort Alphabetically (Filename) - + Сорт. от А до Я (имя файла) - + Sort by Last Modified - + Сорт. по дате посл. изменения - + Sort by Last Loaded - + Сорт. по дате посл. загрузки VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + + Select decks to share + + + + Visual Deck Storage Settings @@ -10966,177 +14529,202 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? Какое предупреждение Вы бы хотели отправить? - + Redact all messages from this user in all rooms - + &OK &ОК - + &Cancel &Отменить - + Warn user for misconduct Дать предупреждение пользователю за проступок - - + + Error Ошибка - + User name to send a warning to can not be blank, please specify a user to warn. Поле не может быть пустым. Пожалуйста, уточните имя пользователя для выдачи предупреждения. - + Warning to use can not be blank, please select a valid warning to send. Поле предупреждения не может быть пустым. Пожалуйста, выберите корректную причину. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top Переместить выбранное издание наверх - + Move selected set up Переместить выбранное издание вверх - + Move selected set down Переместить выбранное издание вниз - + Move selected set to the bottom Переместить выбранное издание в конец. - - Search by set name, code, or type - - - - + Default order - + Restore original art priority order - + Enable all sets Включить все издания - + Disable all sets Отключить все издания - + Enable selected set(s) Включить выбранные издания - + Disable selected set(s) Отключить выбранные издания - + Deck Editor Редактор колод - + Use CTRL+A to select all sets in the view. - + Нажмите CTRL+A для выбора всех изданий в списке - + Only cards in enabled sets will appear in the card list of the deck editor. - + Только карты включенных изданий появятся в редакторе колод - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - + + Search by set name, code, type, or release date + + + + How to use custom card art Как использовать кастомный арт для карты - + Hints - + Подсказки - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -11144,93 +14732,85 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Сгруппировать по мановой ценности - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing Перемешать после просмотра - + pile view просмотр вразнобой - - i18n - - - English - Русский (Russian) - - main - + Connect on startup - + Debug to file @@ -11281,7 +14861,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Move Selected Card - + Положить выбранную карту @@ -11291,12 +14871,12 @@ Please refrain from engaging in this activity or further actions may be taken ag Move Top Card - + Положить верхнюю карту Move Bottom Card - + Положить нижнюю карту @@ -11306,7 +14886,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Drawing - + Взятие карт @@ -11342,7 +14922,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Connect... - + Подключиться... @@ -11362,17 +14942,17 @@ Please refrain from engaging in this activity or further actions may be taken ag Register... - + Регистрация Settings... - + Настройки Start a Local Game... - + Начать локальную игру... @@ -11432,7 +15012,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Add Card - + Добавить карту @@ -11458,7 +15038,7 @@ Please refrain from engaging in this activity or further actions may be taken ag New Deck - + Новая колода @@ -11473,7 +15053,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Delete Card - + Удалить карту @@ -11484,12 +15064,12 @@ Please refrain from engaging in this activity or further actions may be taken ag Save Deck - + Сохранить колоду Save Deck as... - + Сохранить колоду как... @@ -11504,7 +15084,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Save Deck to Clipboard - + Сохранить колоду в буфер обмена @@ -11589,7 +15169,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Force Start - + Принудительный старт @@ -11839,37 +15419,37 @@ Please refrain from engaging in this activity or further actions may be taken ag First Main Phase - + Первая главная фаза Start Combat - + Шаг начала боя Attack - Атака + Шаг объявления атакующих Block - Блок + Шаг объявления блокирующих Damage - Повреждения + Шаг боевых повреждений End Combat - + Шаг конца боя Second Main Phase - + Вторая главная фаза @@ -11879,7 +15459,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Next Phase - + Следующая фаза @@ -11889,7 +15469,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Next Turn - + Следующий ход @@ -11904,7 +15484,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Untap All - + Развернуть все @@ -11950,7 +15530,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Create Token... - + Создать фишку... @@ -12014,6 +15594,16 @@ Please refrain from engaging in this activity or further actions may be taken ag Graveyard Кладбище + + + Report Queue + + + + + Moderation + + @@ -12121,7 +15711,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Leave Game - + Покинуть игру @@ -12131,17 +15721,17 @@ Please refrain from engaging in this activity or further actions may be taken ag Roll Dice... - + Бросить кубик... Flip Coin - + Бросить монетку Shuffle Library - + Перетасовать библиотеку @@ -12181,7 +15771,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Undo Draw - + Отменить взятие карты @@ -12211,12 +15801,12 @@ Please refrain from engaging in this activity or further actions may be taken ag Reveal Hand to All Players - + Показать руку всем игрокам Reveal Random Card to All Players - + Показать случайную карту всем игрокам @@ -12281,7 +15871,7 @@ Please refrain from engaging in this activity or further actions may be taken ag Home - + Меню @@ -12296,12 +15886,12 @@ Please refrain from engaging in this activity or further actions may be taken ag Server - + Сервер Account - + Учетная запись diff --git a/cockatrice/translations/cockatrice_sr.ts b/cockatrice/translations/cockatrice_sr.ts index c04d16f16..9bb962ced 100644 --- a/cockatrice/translations/cockatrice_sr.ts +++ b/cockatrice/translations/cockatrice_sr.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... Podesi brojač... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter Podesi brojač - + New value for counter '%1': Nova vrednost za brojač '%1': @@ -36,64 +36,113 @@ AbstractTabDeckEditor - + Open in new tab Otvori u novoj kartici - + Are you sure? Da li ste sigurni? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. - + There are no cards in your deck to be exported + + AccountSetupPage + + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + AddAnalyticsPanelDialog @@ -105,12 +154,12 @@ Please check that the directory is writable and try again. AdminNotesDialog - + Update Notes - + Admin Notes for %1 @@ -127,172 +176,313 @@ Please check that the directory is writable and try again. Sideboard + + + Tokens + + AppearanceSettingsPage - + seconds - + Error - + Could not create themes directory at '%1'. - + Theme settings Podešavanja teme - + Current theme: Sadašnja tema: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - - Display card name of background in bottom right: - - - - + Menu settings - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering - + Display card names on cards having a picture Pokazuj imena karti na kartama koje imaju sliku - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + + Light + + + + + Dark + + + + + + System + + + + + Show all playmats + + + + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + + Active theme palette: + + + + + Active theme style: + + + + + Qt widget style saved to this theme ("System" lets the application decide) + + + + + Edit theme palette + + + + + Home tab settings + + + + + Display card name of background in bottom right + + + + + Dim the home tab background + + + + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + + Styling settings + + + + + Style user list + + + + + Card printings + + + + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over - + Use rounded card corners - + + Card layout + + + + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Raspored ruke - + Display hand horizontally (wastes space) Prikazuj ruku horizontalno (troši prostor) - + Enable left justification - + Table grid layout - + Invert vertical coordinate - + Minimum player count for multi-column layout: - + Maximum font size for information displayed on cards: @@ -300,12 +490,12 @@ Please check that the directory is writable and try again. ArchidektApiResponseDeckDisplayWidget - + Back to results - + Open Deck in Deck Editor @@ -331,112 +521,112 @@ Please check that the directory is writable and try again. BanDialog - + ban &user name zabrani ime igrača - + ban &IP address zabrani IP adresa - + ban client I&D zabranite ID klijenta - + Ban type Tip zabrane - + &permanent ban stalna zabrana - + &temporary ban privremena zabrana - + &Days: Dani: - + &Hours: Sati: - + &Minutes: Minuti: - + Duration of the ban Dužina trajanja zabrane - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Molimo Vas ukucajte razlog zabrane. Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene osobe. - + Please enter the reason for the ban that will be visible to the banned person. Molimo Vas ukucajte razlog zabrane koji će biti vidljiv zabranjenoj osobi. - + Redact all messages from this user in all rooms - + &OK U redu - + &Cancel Poništi - + Ban user from server Zabranite ulaz korisniku u server - - - - + + + + Error Greška - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. - + You must have a value in the name ban when selecting the name ban checkbox. - + You must have a value in the ip ban when selecting the ip ban checkbox. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. @@ -444,59 +634,280 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene BetaReleaseChannel - + Beta - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the file update server. + + CardArtPreviewWidget + + + Banner preview + + + + + + + + Zoom %1× + + + + + No card selected + + + + + CardArtRulesModel + + + Card + + + + + ProviderId + + + + + Mode + + + + + Reason + + + CardDatabaseModel - + Name Ime - + Sets Setovi - + Mana cost Cena mane - + Card type Tip karte - + P/T - + Color(s) Boja (boje) + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + + + CardDatabaseView + + + Add to Deck + + + + + Add to Sideboard + + + + + Select Printing + + + + + Add to Zone + + + + + Create &new zone... + + + + + Show on EDHRec (Commander) + + + + + Show on EDHRec (Card) + + + + + Show Related cards + + + CardFilter @@ -602,22 +1013,22 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene CardInfoFrameWidget - + Image - + Description - + Both - + View transformation @@ -625,22 +1036,22 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -648,27 +1059,27 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene CardInfoTextWidget - + Name: - + Set: - + Collector Number: - + Related cards: - + Unknown card: @@ -676,132 +1087,150 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + + Reduce life by power + + + + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... + + CardPictureLoaderCacheMethod + + + Network Cache + + + + + Filesystem + + + CardSizeWidget - + Card Size @@ -809,138 +1238,156 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene CardZoneLogic - + their hand nominative - + %1's hand nominative - + their library look at zone - + %1's library look at zone - + of their library top cards of zone, - + of %1's library top cards of zone - + their library reveal zone - + %1's library reveal zone - + their library shuffle - + %1's library shuffle - + their library nominative - + %1's library nominative - + their graveyard nominative - + %1's graveyard nominative - + their exile nominative - + %1's exile nominative - + their sideboard look at zone - + %1's sideboard look at zone - + their sideboard nominative - + %1's sideboard nominative - + their custom zone '%1' nominative - + %1's custom zone '%2' nominative + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser @@ -957,16 +1404,523 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene + + ColorButton + + + Click to pick a color + + + + + Pick colour + + + + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + + + ConnectionController + + + + The server has reached its maximum user capacity, please check back later. + + + + + There are too many concurrent connections from your address. + + + + + Banned by moderator + + + + + Expected end time: %1 + + + + + This ban lasts indefinitely. + + + + + Scheduled server shutdown. + + + + + + Invalid username. + + + + + You have been logged out due to logging in at another location. + + + + + Connection closed + + + + + The server has terminated your connection. +Reason: %1 + + + + + The server is going to be restarted in %n minute(s). +All running games will be lost. +Reason for shutdown: %1 + + + + + Scheduled server shutdown + + + + + Failed Login + + + + + Your client seems to be missing features this server requires for connection. + + + + + To update your client, go to 'Help -> Check for Client Updates'. + + + + + + + + + + + + + + + + + + + + + + Error + + + + + Incorrect username or password. Please check your authentication information and try again. + + + + + There is already an active session using this user name. +Please close that session first and re-login. + + + + + + You are banned until %1. + + + + + + You are banned indefinitely. + + + + + This server requires user registration. Do you want to register now? + + + + + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. +Please close and reopen your client to try again. + + + + + An internal error has occurred, please close and reopen Cockatrice before trying again. +If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. + + + + + Account activation + + + + + Your account has not been activated yet. +You need to provide the activation token received in the activation email. + + + + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + + Server Full + + + + + Unknown login error: %1 + + + + + + +This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. + + + + + + + + + + Registration denied + + + + + Registration is currently disabled on this server + + + + + There is already an existing account with the same user name. + + + + + It's mandatory to specify a valid email address when registering. + + + + + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. + + + + + Password too short. + + + + + Registration failed for a technical problem on the server. + + + + + The connection to the server has been lost. + + + + + Unknown registration error: %1 + + + + + Account activation failed + + + + + Socket error: %1 + + + + + Server timeout + + + + + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. +Local version is %1, remote version is %2. + + + + + Your Cockatrice client is obsolete. Please update your Cockatrice version. +Local version is %1, remote version is %2. + + + + + + Success + + + + + Registration accepted. +Will now login. + + + + + Account activation accepted. +Will now login. + + + + + Information + + + + + This server supports additional features that your client doesn't have. +This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. + +To update your client, go to Help -> Check for Updates. + + + + + + + Reset Password + + + + + Your password has been reset successfully, you can now log in using the new credentials. + + + + + Failed to reset user account password, please contact the server operator to reset your password. + + + + + Activation request received, please check your email for an activation token. + + + + + Connecting to %1... + + + + + Registering to %1 as %2... + + + + + Disconnected + + + + + Connected, logging in at %1 + + + + + Requesting forgotten password to %1 as %2... + + + + + Your username must respect these rules: + + + + + is %1 - %2 characters long + + + + + can %1 contain lowercase characters + + + + + + + + NOT + + + + + can %1 contain uppercase characters + + + + + can %1 contain numeric characters + + + + + can contain the following punctuation: %1 + + + + + first character can %1 be a punctuation mark + + + + + no unacceptable language as specified by these server rules: + note that the following lines will not be translated + + + + + can not contain any of the following words: %1 + + + + + can not match any of the following expressions: %1 + + + + + You may only use A-Z, a-z, 0-9, _, ., and - in your username. + + + CustomZoneMenu - + C&ustom Zones - - + + View custom zone '%1' @@ -974,30 +1928,35 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckAnalyticsWidget - + Add Panel - + Remove Panel - + Save Layout - + Load Layout + + + Include Sideboard + + DeckEditorCardDatabaseDockWidget - + Card Database @@ -1013,47 +1972,17 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - - Add to Deck - - - - - Add to Sideboard - - - - - Select Printing - - - - - Show on EDHRec (Commander) - - - - - Show on EDHRec (Card) - - - - - Show Related cards - - - - + Add card to &maindeck - + Add card to &sideboard @@ -1061,97 +1990,158 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + Format: - + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1159,17 +2149,17 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1177,114 +2167,119 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + + Share deck... + + + + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1292,7 +2287,7 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1300,199 +2295,185 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckEditorSettingsPage - - + + Update Spoilers - - + Success - + Download URLs have been reset. - - Downloaded card pictures have been reset. - - - - - Error - - - - - One or more downloaded card pictures could not be cleared. - - - - + Add URL - - + + URL: - - + + Edit URL - - Network Cache Size: - - - - - Redirect Cache TTL: - - - - - How long cached redirects for urls are valid for. - - - - - Picture Cache Size: - - - - + Add New URL - + Remove URL - - Day(s) - - - - + Updating... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path - + URL Download Priority - + Spoilers - + Download Spoilers Automatically - + Spoiler Location: - + Last Change - + Spoilers download automatically on launch - + Press the button to manually update without relaunching - + Do not close settings until manual update is complete - + Download card pictures on the fly - + How to add a custom URL - - Delete Downloaded Images - - - - + Reset Download URLs - - - On-disk cache for downloaded pictures - - - - - In-memory cache for pictures not currently on screen - - DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1500,27 +2481,27 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckListModel - + Count - + Set - + Number Broj - + Provider ID - + Card Karta @@ -1528,12 +2509,12 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckLoader - + Common deck formats (%1) - + All files (*.*) @@ -1541,17 +2522,22 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -1559,7 +2545,7 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -1567,62 +2553,62 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -1630,94 +2616,109 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckPreviewWidget - + Banner Card - + Open in deck editor - + + Share deck... + + + + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Rename deck + + + + + Rename file + + + + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -1725,57 +2726,102 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckStateManager - + Rename deck to "%1" from "%2" - + Updated comments (was %1 chars, now %2 chars) - + Set banner card to %1 (%2) - + + Set playmat to %1 + + + + Tags changed - + Set format to %1 - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + %1 1 × "%2" (%3) - + Added - + Removed @@ -1797,94 +2843,122 @@ Ovo je samo sačuvano za moderatore i ne može biti viđeno od strane zabranjene DeckViewContainer - + Load deck... Učitaj špil... - + Load remote deck... Učitaj daljinski špil... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start - + Force start - + Sideboard unlocked - + Sideboard locked - - + + Error Greška - + The selected file could not be loaded. Izabrani fajl nije mogao biti učitan. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? - + Remember and automatically apply choice in the future @@ -1897,143 +2971,143 @@ This will kick all non-ready players from the game. - + Known Hosts - + Delete the currently selected saved server - + Refresh the server list with known public servers - + New Host Novi Domaćin - + Name: - + &Host: Domaćin: - + &Port: - + Player &name: Ime igrača: - + P&assword: Lozinka: - + &Save password Sačuvajte lozinku: - + A&uto connect Automatsko povezivanje - + Automatically connect to the most recent login when Cockatrice opens Automatski se poveži na najskoriju prijavu kada se Cockatrice otvori - + If you have any trouble connecting or registering then contact the server staff for help! - - + + Webpage - + Reset Password - + Forgot password? - + &Connect - + Server Server - + Login Prijava - + Server Contact - + Connect to Server - + Server URL - + Communication Port - + Unique Server Name - + Connection Warning - + You need to name your new connection profile. - + Connect Warning Upozorenje o povezivanju - + The player name can't be empty. Ime igrača ne može biti prazno. @@ -2041,122 +3115,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings Za&pamti podešavanja - + &Description: Opis: - + P&layers: Igrači: - + General - + Game type Tip igre - + &Password: Lozinka: - + Only &buddies can join Samo prijatelji mogu da se pridruže - + Only &registered users can join Samo &registrovani korisnici mogu da se pridruže - + Joining restrictions Ograničenja pridruživanja - + &Spectators can watch Posmatrači mogu da gledaju - + Spectators &need a password to watch Posmatračima &je potrebna lozinka da gledaju - + Spectators can &chat Posmatrači mogu &da ćaskaju - + Spectators can see &hands Posmatrači mogu da &vide ruke - + Create game as spectator - + Spectators Posmatrači - + Starting life total: - + Open decklists in lobby - + Create game as judge - + Game setup options - + &Clear Očisti - + Create game Započni igru - + Game information Informacije o igri - + Error Greška - + Server error. Greška servera. @@ -2164,97 +3238,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: Ime: - + Token Žeton - + C&olor: Boja: - + white belo - + blue plavo - + black crno - + red crveno - + green zeleno - + multicolor višebojno - + colorless bezbojno - + &P/T: M/I: - + &Annotation: Anotacija: - + &Destroy token when it leaves the table Uništi token kada napusti sto - + Create face-down (Only hides name) - + Token data Podaci o tokenima - + Show &all tokens Prikaži sve tokene - + Show tokens from this &deck Prikaži tokene iz ovog špila - + Choose token from list Izaberi token iz liste - + Create token Napravi token @@ -2262,53 +3336,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + ✖ - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2317,7 +3391,7 @@ This will kick all non-ready players from the game. DlgEditAvatar - + No image chosen. Slika nije izabrana. @@ -2329,27 +3403,32 @@ To remove your current avatar, confirm without choosing a new image. Da izbrišete vaš sadašnji avatar, potvrdite bez biranja nove slike. - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... Pretraži... - + Change avatar Promeni avatar - + Open Image Otvori sliku - + Image Files (*.png *.jpg *.bmp) Fajlovi Slika (*.png *.jpg *.bmp) - + Invalid image chosen. Neispravna slika izabrana. @@ -2375,38 +3454,38 @@ Da izbrišete vaš sadašnji avatar, potvrdite bez biranja nove slike. DlgEditPassword - + Old password: Stara lozinka: - + New password: Nova lozinka: - + Confirm new password: Potvrdi novu lozinku: - + Change password Promeni lozinku - - + + Error Greška - + Your password is too short. - + The new passwords don't match. Nove lozinke se ne podudaraju @@ -2414,93 +3493,93 @@ Da izbrišete vaš sadašnji avatar, potvrdite bez biranja nove slike. DlgEditTokens - + &Name: Ime: - + C&olor: Boja: - + white belo - + blue plavo - + black crno - + red crveno - + green zeleno - + multicolor višebojno - + colorless bezbojno - + &P/T: M/I: - + &Annotation: Anotacija: - + Token data Podaci o tokenima - - + + Add token Dodaj token - + Remove token Uklonite token - + Edit custom tokens - + Please enter the name of the token: Molim Vas, ukucajte ime tokena: - + Error Greška - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. @@ -2524,12 +3603,12 @@ Make sure to enable the 'Token' set in the "Manage sets" dia Neodređeno - + Real name: Pravo ime: - + Edit user profile Promenite korisnički profil @@ -2567,113 +3646,115 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - + Hide 'buddies only' games - + Hide full games - - Hide games that have started + + Hide started games + Hide games that have started - + Hide password protected games - + Hide 'ignored user' games - + Hide games not created by buddies Hide games not created by buddy - + Hide games with forced open decklists - + &Newer than: - + Game &description: Opis igre: - - &Creator name: - Ime kreatora: + + &Host name: + &Creator name: + - + General - + &Game types Tipovi igara - + at &least: barem: - + at &most: najviše: - + Maximum player count Maksimalni broj igrača - + Restrictions Ograničenja - + Show games only if &spectators can watch - + Show spectator password p&rotected games - + Show only if spectators can ch&at - + Show only if spectators can see &hands - + Spectators - + Filter games Filtriraj igre @@ -2681,52 +3762,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning - + A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. - + &Host: - + &Port: - + Player &name: - + Email: - + Reset Password Challenge - + Reset Password Challenge Error - + The email address can't be empty. @@ -2734,37 +3815,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. - + &Host: - + &Port: - + Player &name: - + Reset Password Request - + Reset Password Error - + The player name can't be empty. @@ -2772,94 +3853,127 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning - + A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. - + &Host: - + &Port: - + Player &name: - + Token: - - + + New Password: - + Reset Password - + The player name can't be empty. - - - - + + + + Reset Password Error - + The token can't be empty. - + The new password can't be empty. - + Error - + Your password is too short. - + The passwords do not match. + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck @@ -2943,180 +4057,581 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLocalGameOptions - + Players: - + General - + Starting life total: - + Game setup options - + Remember settings - + Local game options + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: Domaćin: - + &Port: - + Player &name: Ime igrača: - + P&assword: Lozinka: - + Password (again): Lozinka (ponovo): - + Email: I-mejl: - + Email (again): I-mejl (ponovo): - + Country: Država: - + Undefined Neodređeno - + Real name: Pravo ime: - + Register to server Registruj se na server - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning Upozorenje o registraciji - + Your password is too short. - + Your passwords do not match, please try again. Vaše lozinke se ne podudaraju, molim Vas, pokušajte ponovo - + Your email addresses do not match, please try again. Vaše i-mejl adrese se ne podudaraju, molimo Vas, pokušajte ponovo. - + The player name can't be empty. Ime igrača ne može biti prazno. + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice - + Number of sides: - + Number of dice: - + Roll Dice @@ -3167,34 +4682,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Nepoznata Greška učitavanja baze podataka karte - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3203,14 +4696,14 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3219,7 +4712,7 @@ Would you like to change your database location setting? Da li bi ste želeli da promenite vaše podešavanje lokacije baze podataka? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3228,99 +4721,235 @@ Would you like to change your database location setting? - - - + + + Error Greška - + The path to your deck directory is invalid. Would you like to go back and set the correct path? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? - + Settings Podešavanja - + + Search settings... + + + + + OK + + + + General Opšto - + Appearance Izgled - + User Interface Korisnički interfejs - + + Storage + + + + Card Sources - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat Ćaskanje - + Sound Zvuk - + Shortcuts Prečice + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3328,17 +4957,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next - + Previous - + Tip of the Day @@ -3346,164 +4975,174 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel - + Reinstall - + Cancel Download - + Open Download Page Otvori Stranicu Za Preuzimanje - + Check for Client Updates - - - - + + + + Error Greška - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. - + Downloading update: %1 - + Checking for updates... Provera za ažuriranja... - + Finished checking for updates - + No Update Available - + Cockatrice is up to date! - + You are already running the latest version available in the chosen release channel. - + Current version - + Selected release channel - - + + Update Available - - + + A new version of Cockatrice is available! - - + + New version - - + + Released - - + + Changelog - + Do you want to update now? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error Greška Ažuriranja - + An error occurred while checking for updates: - + An error occurred while downloading an update: - + Installing... Instalacija... - + Cockatrice is unable to open the installer. - + Try to update manually by closing Cockatrice and running the installer. - + Download location @@ -3511,17 +5150,17 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing - + Copy to clipboard - + Debug Log @@ -3593,67 +5232,67 @@ You may have to manually download the new version. DrawProbabilityWidget - + Draw Probability - + Probability of drawing - + Card Name - + Type - + Subtype - + Mana Value - + At least - + Exactly - + card(s) having drawn at least - + cards - + Category - + Qty - + Odds (%) @@ -3759,7 +5398,7 @@ You may have to manually download the new version. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -3767,7 +5406,7 @@ You may have to manually download the new version. FilterBuilder - + Type your filter here @@ -3775,45 +5414,88 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -3821,140 +5503,156 @@ You may have to manually download the new version. GameSelector - - - - - - - - - + + + + + + + + + Error Greška - + Please join the appropriate room first. - + Wrong password. Pogrešna lozinka. - + Spectators are not allowed in this game. Posmatrači nisu dozvoljeni u ovoj igri. - + The game is already full. Igra je već puna. - + The game does not exist any more. Ova igra više ne postoji. - + This game is only open to registered users. Ova igra je samo otvorena za registrovane korisnike. - + This game is only open to its creator's buddies. Ova igra je samo otvorena za prijatelje njenog kreatora. - + You are being ignored by the creator of this game. Vi ste ignorisani od strane kreatore ove igre. - + Join Game - + Spectate Game - + Game Information - + + Cop&y game link + + + + Join Game as Judge - + Spectate Game as Judge - + + Join game Pridruži se igri - - Password: - Lozinka: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. - + &Filter games Filtriraj igre - + C&lear filter Očisti filter - + C&reate Kreiraj - + &Join Pridruži se - + Join as judge - + J&oin as spectator Pridruži se kao posmatrač - + Join as judge spectator - + Games shown: %1 / %2 - + Games Igre @@ -3962,32 +5660,32 @@ You may have to manually download the new version. GameSelectorQuickFilterToolBar - + All types - + Filter by game name... - + Filter by game type/format - + Hide games not created by buddies - + Hide full games - + Hide started games @@ -3995,105 +5693,105 @@ You may have to manually download the new version. GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password lozinka - + buddies only samo prijatelji - + reg. users only samo reg. korisnici - + open decklists - - + + can chat može da ćaska - + see hands vidi ruke - + can see hands može da vidi ruke - + not allowed nije dozvoljeno - + Room Soba - + Age Starost - + Description Opis - - Creator - Kreator + + Host + - + Type Tip - + Restrictions Ograničenja - + Players Igrači - + Spectators Posmatrači @@ -4101,143 +5799,275 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + English + Srpski (Serbian) + + + + + + + + + Choose path Izaberi put - - Personal settings - Lična podešavanja + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> + - + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + + Language settings + + + + Language: Jezik: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + + Version settings + + + + + Card database + + + + + Startup settings + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) - + Paths Putevi - + How to help with translations - + Decks directory: - + Filters directory: - + Replays directory: - + Pictures directory: - + Card database: Baza podataka karata: - + Custom database directory: - + Token database: Baza podataka tokena: - + Update channel - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup - + Last update check on %1 (%2 days ago) @@ -4245,47 +6075,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4293,355 +6123,649 @@ You may have to manually download the new version. HandMenu - + &Hand - + &View hand - + Sort hand by... - + Name - + Type - + Mana Value - + Take &mulligan (Choose hand size) - + Take mulligan (Same hand size) - + Take mulligan (Hand size - 1) - + &Move hand to... - + &Top of library - + &Bottom of library - + &Graveyard - + &Exile - + &Reveal hand to... - - + + All players - + Reveal r&andom card to... + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu - + &Library - + &View library - + View &top cards of library... - + View bottom cards of library... - + Reveal &library to... - + Lend library to... - + Reveal &top cards to... - + &Top of library... - + &Bottom of library... - + &Always reveal top card - + &Always look at top card - + &Open deck in deck editor - + &Draw card - + D&raw cards... - + &Undo last draw - + Shuffle - + &Play top card - + Play top card &face down - + Put top card on &bottom - + Move top card to grave&yard - + Move top card to e&xile - + Move top cards to &graveyard... - + Move top cards to graveyard face down... - + Move top cards to &exile... - + Move top cards to exile face down... - + Put top cards on stack &until... - + Shuffle top cards... - + &Draw bottom card - + D&raw bottom cards... - + &Play bottom card - + Play bottom card &face down - + Move bottom card to grave&yard - + Move bottom card to e&xile - + Move bottom cards to &graveyard... - + Move bottom cards to graveyard face down... - + Move bottom cards to &exile... - + Move bottom cards to exile face down... - + Put bottom card on &top - + Shuffle bottom cards... - - + + &All players - + Reveal top cards of library - + Number of cards: (max. %1) @@ -4649,831 +6773,474 @@ You may have to manually download the new version. MainWindow - - - The server has reached its maximum user capacity, please check back later. - - - - - There are too many concurrent connections from your address. - - - - - Banned by moderator - Zabranjen od strane moderatora - - - - Expected end time: %1 - - - - - This ban lasts indefinitely. - - - - - Scheduled server shutdown. - - - - - - Invalid username. - Nevažeće korisničko ime. - - - - You have been logged out due to logging in at another location. - - - - - Connection closed - Povezanost prekinuta - - - - The server has terminated your connection. -Reason: %1 - - - - - The server is going to be restarted in %n minute(s). -All running games will be lost. -Reason for shutdown: %1 - - - - - Scheduled server shutdown - - - - - - Success - Uspeh - - - - Registration accepted. -Will now login. - - - - - Account activation accepted. -Will now login. - - - - - + + Player %1 - + Load replay - + About Cockatrice - + Version - + Cockatrice Webpage - + Project Manager: - + Past Project Managers: - + Developers: - + Our Developers - + Help Develop! - + Translators: - + Our Translators - + Help Translate! - + Support: - + Report an Issue - + Troubleshooting - + F.A.Q. - - - - - - - - - - - - - - - - - - - - + + Error - - Server timeout - - - - - Failed Login - - - - - Your client seems to be missing features this server requires for connection. - - - - - To update your client, go to 'Help -> Check for Client Updates'. - - - - - Incorrect username or password. Please check your authentication information and try again. - - - - - There is already an active session using this user name. -Please close that session first and re-login. - - - - - - You are banned until %1. - - - - - - You are banned indefinitely. - - - - - This server requires user registration. Do you want to register now? - - - - - This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. -Please close and reopen your client to try again. - - - - - An internal error has occurred, please close and reopen Cockatrice before trying again. -If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - - - - - Account activation - - - - - Your account has not been activated yet. -You need to provide the activation token received in the activation email. - - - - - Server Full - - - - - Unknown login error: %1 - - - - - - -This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - - Your username must respect these rules: - - - - - is %1 - %2 characters long - - - - - can %1 contain lowercase characters - - - - - - - - NOT - - - - - can %1 contain uppercase characters - - - - - can %1 contain numeric characters - - - - - can contain the following punctuation: %1 - - - - - first character can %1 be a punctuation mark - - - - - no unacceptable language as specified by these server rules: - note that the following lines will not be translated - - - - - can not contain any of the following words: %1 - - - - - can not match any of the following expressions: %1 - - - - - You may only use A-Z, a-z, 0-9, _, ., and - in your username. - - - - - - - - - - Registration denied - - - - - Registration is currently disabled on this server - - - - - There is already an existing account with the same user name. - - - - - It's mandatory to specify a valid email address when registering. - - - - - It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - - - - - Password too short. - - - - - Registration failed for a technical problem on the server. - - - - - The connection to the server has been lost. - - - - - Unknown registration error: %1 - - - - - Account activation failed - - - - - Socket error: %1 - - - - - You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. -Local version is %1, remote version is %2. - - - - - Your Cockatrice client is obsolete. Please update your Cockatrice version. -Local version is %1, remote version is %2. - - - - - Connecting to %1... - - - - - Registering to %1 as %2... - - - - - Disconnected - - - - - Connected, logging in at %1 - - - - - - - Requesting forgotten password to %1 as %2... - - - - + &Connect... - + &Disconnect - + Start &local game... - + &Watch replay... - + &Full screen - + &Register to server... - + &Restore password... - + &Settings... - + &Exit - + A&ctions - + &Cockatrice - + C&ard Database - + &Manage sets... - + Edit custom &tokens... - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + Reload card database - + Tabs - + &Help - + &About Cockatrice - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + + Re-run Onboarding Wizard... + + + + Show/Hide - + + Sign-in cancelled + + + + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + + Are you sure? + + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + + Yes, always enable + + + + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - - + Information - + A card database update is already running. - + Unable to run the card database updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - - This server supports additional features that your client doesn't have. -This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. - -To update your client, go to Help -> Check for Updates. - - - - - - - - + + + + + Load sets/cards - + Selected file cannot be found. - + You can only import XML databases at this time. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. - - - - - Reset Password - - - - - Your password has been reset successfully, you can now log in using the new credentials. - - - - - Failed to reset user account password, please contact the server operator to reset your password. - - - - - Activation request received, please check your email for an activation token. - - ManaBaseConfigDialog - + Mana Base Configuration - + Display type: - + pie - + bar - + combinedBar - + Filter Colors (optional): - + OK - + Cancel @@ -5545,27 +7312,27 @@ Cockatrice will now reload the card database. ManaDevotionConfigDialog - + Display type: - + pie - + bar - + combinedBar - + Filter Colors (optional): @@ -5635,610 +7402,615 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play - + from their graveyard - + from exile - + from their hand - + the top card of %1's library - + the top card of their library - + from the top of %1's library - + from the top of their library - + the bottom card of %1's library - + the bottom card of their library - + from the bottom of %1's library - + from the bottom of their library - + from %1's library - + from their library - + from sideboard - + from the stack - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. - + %1 is not revealing the top card %2 any longer. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. - + %1 has conceded the game. - + %1 has unconceded the game. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. - + %1 has loaded a deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). - + %1 destroys %2. - + a card - + %1 gives %2 control over %3. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. - + %1 puts %2%3 into their graveyard face down. - + %1 puts %2%3 into their graveyard. - + %1 exiles %2%3 face down. - + %1 exiles %2%3. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. - + %1 moves %2%3 to sideboard. - + %1 plays %2%3 face down. - + %1 plays %2%3. - + %1 moves %2%3 to custom zone '%4' face down. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. - + %1 turns %2 face-up. - + The game has been closed. - + The game has started. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. - + %1 is now watching the game. - + You have been kicked out of the game. - + %1 has left the game (%2). - + %1 is not watching the game any more (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. - + %1 sets annotation of %2 to %3. - - %1 places %2 "%3" counter(s) on %4 (now %5). + + %1 places %2 %3%4 counter(s) on %5 (now %6). - - %1 removes %2 "%3" counter(s) from %4 (now %5). + + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + + %1 failed to undo their last draw. + + + + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. - + %1 untaps their permanents. - + %1 taps %2. - + %1 untaps %2. - + %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -6246,110 +8018,125 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message - - + + Message: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - - + + Ignore private messages sent by non-buddy users + + + + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -6357,42 +8144,42 @@ Cockatrice will now reload the card database. MoveMenu - + Move to - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + T&able - + &Hand - + &Graveyard - + &Exile @@ -6405,47 +8192,47 @@ Cockatrice will now reload the card database. - + Mana Value - + Color(s) - + Loyalty - + Main Card Type - + Mana Cost - + P/T - + Side - + Layout - + Color Identity @@ -6468,6 +8255,138 @@ Cockatrice will now reload the card database. + + PaletteEditorDialog + + + + Reset + + + + + ▼ Edit Palette + + + + + + ▶ Edit Palette + + + + + Palette Editor — %1 + + + + + <b>Palette Editor</b> &nbsp;·&nbsp; %1 + + + + + + Save + + + + + Delete this scheme's custom palette and revert to the theme default (or the application palette) + + + + + Switch between the light and dark palette files + + + + + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + + ↺ Revert to theme default + + + + + Discard unsaved edits and restore the last saved palette + + + + + Cannot save: this theme has no writable directory + + + + + Write palette-%1.toml and reload the theme + + + + + + Save failed + + + + + + Could not write %1 to: +%2 + + + + + PaletteGridWidget + + + Active + + + + + Disabled + + + + + Inactive + + + + + Normal interactive state + + + + + Widget is disabled / not interactive + + + + + Window is in background / unfocused + + + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + + Phase @@ -6534,222 +8453,460 @@ Cockatrice will now reload the card database. PhasesToolbar - + Untap step - + Upkeep step - + Draw step - + First main phase - + Beginning of combat step - + Declare attackers step - + Declare blockers step - + Combat damage step - + End of combat step - + Second main phase - + End of turn step - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - PlayerActions - - View top cards of library - - - - - - - - - - - - - Number of cards: (max. %1) - - - - - View bottom cards of library - - - - - Shuffle top cards of library - - - - - Shuffle bottom cards of library - - - - - Draw hand - - - - - 0 and lower are in comparison to current hand size - - - - - Draw cards - - - - - - + + + grave - - - + + + exile + + + PlayerDialogs - + + View top cards of library + + + + + + + + + + + + + Number of cards: (max. %1) + + + + + View bottom cards of library + + + + + Shuffle top cards of library + + + + + Shuffle bottom cards of library + + + + + Draw hand + + + + + 0 and lower are in comparison to current hand size + + + + + Draw cards + + + + Move top cards to %1 - + Move bottom cards to %1 - + Draw bottom cards - - - C&reate another %1 token - - - - + Create tokens - - + Number: - + Place card X cards from top of library - + Which position should this card be placed: - + (max. %1) - + Change power/toughness - + Change stats to: - + Set annotation - + Please enter the new annotation: - - - Set counters - - PlayerMenu - + Player "%1" - + &Counters + + PlaymatCollectionDialog + + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + PrintingDisabledInfoWidget @@ -6770,12 +8927,12 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelector - + Display Navigation Buttons - + Printing Selector @@ -6783,25 +8940,65 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -6832,28 +9029,28 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -6869,95 +9066,103 @@ This setting means you'll only see the default printing for each card, instead o PtMenu - + Power / toughness - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -6965,17 +9170,17 @@ This setting means you'll only see the default printing for each card, instead o QObject - + Cockatrice card database (*.xml) - + All files (*.*) - + Cockatrice replays (*.cor) @@ -6995,17 +9200,22 @@ This setting means you'll only see the default printing for each card, instead o - + + Maybeboard + + + + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7016,7 +9226,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7027,189 +9237,334 @@ Are you sure you would like to disable this feature? - + Confirm Change + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults + + QuickSetupPanel + + + %1% + + + + + <b>Quick Setup</b> + + + + + Generate all palette roles automatically from a single accent colour + + + + + Accent: + + + + + Primary hue. Used directly for highlights and links. +At high intensity it also tints buttons and backgrounds. + + + + + Intensity: + + + + + Subtle + + + + + Full colour + + + + + 0–30 Subtle tint — only highlights and links change hue +30–70 Accented — buttons, tooltips, and borders join in +70–100 Full colour — backgrounds, everything + + + + + 70% + + + RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name - + ID - + Upload time + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID - + Name - + Players - + Keep - + Time started - + Duration (sec) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu - + &Exile - + &View exile - + &Move exile to... - + &Top of library - + &Bottom of library - + &Hand - + &Graveyard @@ -7217,37 +9572,37 @@ Are you sure you would like to disable this feature? RoomSelector - + Rooms - + Joi&n - + Room - + Description - + Permissions - + Players - + Games @@ -7255,7 +9610,7 @@ Are you sure you would like to disable this feature? SayMenu - + S&ay @@ -7263,32 +9618,32 @@ Are you sure you would like to disable this feature? SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - + Invalid key - + Shortcut already in use by: - + Clear - + Restore default @@ -7296,81 +9651,117 @@ Are you sure you would like to disable this feature? SetsModel - + Enabled - + Set type - + Set code - + Long name - + Release date + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + + Shortcut editor + + + + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -7424,12 +9815,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -7437,27 +9828,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -7465,48 +9856,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -7514,45 +9905,204 @@ Please check your shortcut settings! StableReleaseChannel - + Default - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. + + StatsTally + + + Total Power + + + + + Total Toughness + + + + + StorageSettingsPage + + + + + Success + + + + + Cached card pictures have been reset. + + + + + Downloaded card pictures have been reset. + + + + + Error + + + + + One or more downloaded card pictures could not be cleared. + + + + + In-memory (currently loaded) card pictures have been reset. + + + + + Card Picture Loader Caching Method: + + + + + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. + + + + + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. + + + + + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. + + + + + Delete Cached Images + + + + + Delete Saved Images + + + + + Clear In-Memory Images + + + + + Card Picture Loader Cache Method + + + + + Network Cache + + + + + Filesystem + + + + + In-Memory Picture Cache + + + + + Network Cache Size: + + + + + On-disk cache for downloaded pictures + + + + + Redirect Cache TTL: + + + + + How long cached redirects for urls are valid for. + + + + + Picture Cache Size: + + + + + In-memory cache for pictures not currently on screen + + + + + Naming scheme: + + + + + Day(s) + + + TabAccount - + + + My Reports + + + + Add to Buddy List - + Add to Ignore List - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account @@ -7673,118 +10223,118 @@ Please check your shortcut settings! TabArchidekt - - - + + + Desc. - - + + AND - - + + Require ALL selected colors - - + + Deck name... - - + + Owner... - - + + Packages - - + + Advanced Filters - + Bracket: - - + + Any - - + + Contains card... - - + + Commander... - - + + Tag... - - + + Deck Size - + Cards: - - + + Asc. - + Sort by: - + Filter by: - + Display Settings - - + + Search - - + + Formats @@ -7794,55 +10344,103 @@ Please check your shortcut settings! + + TabCardArtRules + + + Card: + + + + + ProviderId: + + + + + Mode: + + + + + Reason: + + + + + Type a card name... + + + + + Add rule + + + + + Remove rule + + + + + Refresh + + + + + Card Art Rules + + + TabDeckEditor - + Card Info - + Deck - + Filters - + &View - + Card Database - + Printing - + Visible - + Floating - + Reset layout - + Deck: %1 @@ -7850,55 +10448,49 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - - - Filters - - - - + &View - + Printing - + Visible - + Floating - + Reset layout @@ -7906,22 +10498,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -7929,138 +10521,236 @@ Please check your shortcut settings! TabDeckStorage - + Local file system - + Server deck storage - - + + Open in deck editor - + Rename deck or folder - + Upload deck - + Download deck - - - - + + + + New folder - - + + Delete - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + + + + Error - + Rename failed - - + + + Invalid deck file - + Enter deck name - + This decklist does not have a name. Please enter a name: - + Unnamed deck - + Failed to upload deck to server - + Delete local file - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: - + Deck Storage @@ -8068,20 +10758,271 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -8094,27 +11035,27 @@ Please enter a name: TabEdhRecMain - + &Cards - + Top Commanders - + Tags - + Search for a card ... - + Search @@ -8127,191 +11068,207 @@ Please enter a name: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + + Invite + + + + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases - + &Game - + Next &phase - + Next phase with &action - + Next &turn - + Reverse turn order - + &Remove all local arrows - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede - - - + + + &Concede - + &Leave game - + C&lose replay - + &Focus Chat - + &Say: - + Selected cards - + &View - + Visible - + Floating - + Reset layout - + Concede - + Are you sure you want to concede this game? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game - + Are you sure you want to leave this game? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. @@ -8327,157 +11284,157 @@ Please enter a name: TabLog - + Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: - + IP Address: - + Game Name: - + GameID: - + Message: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -8485,218 +11442,558 @@ The more information you put in, the more specific your results will be. TabMessage - + Private &chat - + &Leave - + %1 - Private chat - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from - + %1 has left the server. - + %1 has joined the server. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system - + Server replay storage - - + + Watch replay - + Rename - - + + New folder - - + + Delete - + Open replays folder - + Download replay - + Toggle expiration lock - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay @@ -8706,50 +12003,478 @@ The more information you put in, the more specific your results will be. + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - + &Say: - + Chat - + &Room - + &Leave room - + &Clear chat - + Chat Settings... - + mentioned you. - + Click to view - + You are flooding the chat. Please wait a couple of seconds. @@ -8757,35 +12482,35 @@ The more information you put in, the more specific your results will be. TabServer - + Server - - - - + + + + Error - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -8793,97 +12518,173 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - + Archidekt - + Home - + &Visual Deck Storage - + Visual Database Display - + Server - + Account - + Deck Storage - + Game Replays - + Administration - + Logs - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? - + There are still open games. Are you sure you want to quit? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -8891,38 +12692,38 @@ To update your client, go to Help -> Check for Updates. - + Idle Timeout - + You are about to be logged out due to inactivity. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) @@ -8931,16 +12732,44 @@ Please refrain from engaging in this activity or further actions may be taken ag TabVisualDatabaseDisplay - + Database Display - + Visual Database Display + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -8955,6 +12784,65 @@ Please refrain from engaging in this activity or further actions may be taken ag + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -8973,42 +12861,42 @@ Please refrain from engaging in this activity or further actions may be taken ag TranslateCounterName - + Life - + White - + Blue - + Black - + Red - + Green - + Colorless - + Other @@ -9016,214 +12904,327 @@ Please refrain from engaging in this activity or further actions may be taken ag UpdateDownloader - + Could not open the file for reading. + + UserCardArtSettingsDialog + + + Card Art Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Remove Banner Card + + + UserContextMenu - + User &details - + Private &chat - + Show this user's &games - - - Add to &buddy list - - - - - Remove from &buddy list - - - - - Add to &ignore list - - - - - Remove from &ignore list - - - - - Kick from &game - - - - - Warn user - - - View user's war&n history + View this user's &public decks - Ban from &server + Add to &buddy list - View user's &ban history + Remove from &buddy list - &Promote user to moderator + Add to &ignore list - Dem&ote user from moderator + Remove from &ignore list - Promote user to &judge + Kick from &game - Demote user from judge + Report user + Warn user + + + + + View user's war&n history + + + + + Ban from &server + + + + + View user's &ban history + + + + + &Promote user to moderator + + + + + Dem&ote user from moderator + + + + + Promote user to &judge + + + + + Demote user from judge + + + + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes - - + Investigate user + + + + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - + Failed to get admin notes. - - + + Success - + Successfully promoted user. - + Successfully demoted user. - - - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + + Kick Player + + + + + Are you sure you want to kick this player from the game? + + + + + + Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -9231,48 +13232,48 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInfoBox - + Location: - + Account Age: - + Edit - + Change password - + Change avatar - + Administrator - + Moderator - + Registered user - - + + Unregistered user @@ -9282,334 +13283,702 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Unknown - + The entered password does not match your account. - - - + + + + Information - + User information updated. - - - - - - - - - - + + + + + + + + + + + Error - + User Information - + Real Name: - + User Level: + + + Edit Banner Card + + + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + + The selected card is blacklisted on this server or another error occurred. + + + + + Banner card removed. + + + + + Banner card updated. + + + + This server does not permit you to update your user informations. - + Password changed. - + This server does not permit you to change your password. - + The new password is too short. - + The old password is incorrect. - + Avatar updated. - + This server does not permit you to update your avatar. - - An error occured while trying to updater your avatar. + + An error occured while trying to update your avatar. + + + + + UserInfoPopup + + + + Games + + + + + Chat + + + + + Open private chat + + + + + Profile + + + + + View user profile + + + + + Show this user's games + + + + + Invite + + + + + Invite to your game + + + + + − Buddy + + + + + Remove from buddy list + + + + + + Buddy + + + + + Add to buddy list + + + + + − Ignore + + + + + Remove from ignore list + + + + + + Ignore + + + + + Add to ignore list + + + + + Ban + + + + + Ban from server + + + + + Warn + + + + + Warn user + + + + + Ban log + + + + + View ban history + + + + + Warn log + + + + + View warning history + + + + + Notes + + + + + View admin notes + + + + + − Mod + + + + + Demote from moderator + + + + + + Mod + + + + + Promote to moderator + + + + + − Judge + + + + + Demote from judge + + + + + + Judge + + + + + Promote to judge + + + + + Join game + + + + + Spectate + + + + + + Loading games… + + + + + Could not load games. + + + + + No active games. UserInterfaceSettingsPage - + General interface settings - + &Double-click cards to play them (instead of single-click) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens - - Show selection counter during drag selection + + Show selection count during drag selection - - Show total selection counter + + Show total selection count - + + + Disabled + + + + + + Enabled + + + + + + Automatic + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + Use tear-off menus, allowing right click menus to persist on screen - + + Keep game chat focused when clicking in game (Note: disables card view search bar) + + + + Notifications settings - + Enable notifications in taskbar - + Notify in the taskbar for game events while you are spectating - + Notify in the taskbar when users in your buddy list connect - + Animation settings - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings - + Open deck in new tab by default - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - - do nothing + + Do nothing - - ask to convert to .cod + + Ask to convert to .cod - - always convert to .cod + + Always convert to .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings - + Buffer time for backwards skip via shortcut: + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + + Flip coin + + + + &Create token... - + C&reate another token - + Cr&eate predefined token + + + C&reate another %1 token + + VisualDatabaseDisplayColorFilterWidget @@ -9638,22 +14007,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... - + Save Filter - + Save all currently applied filters to a file - + Enter filename... @@ -9734,22 +14103,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFormatLegalityFilterWidget - + Show formats with at least: - + cards - + Do not display formats with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) @@ -9767,32 +14136,32 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayMainTypeFilterWidget - + Show main types with at least: - + cards - + Do not display card main-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9800,27 +14169,27 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayNameFilterWidget - + Filter by name... (Exact match) - + Load from Deck - + Apply all card names in currently loaded deck as exact match name filters - + Load from Clipboard - + Apply all card names in clipboard as exact match name filters @@ -9828,7 +14197,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -9836,19 +14205,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -9856,37 +14225,37 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySubTypeFilterWidget - + Search subtypes... - + Show sub types with at least: - + cards - + Do not display card sub-types with less than this amount of cards in the database - + Filter mode (AND/OR/NOT conjunctions of filters) - + Mode: Exact Match - + Mode: Includes @@ -9894,28 +14263,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - - + + Visual - + Loading database ... - + Clear all filters - + Table @@ -9923,43 +14292,43 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat @@ -9975,12 +14344,12 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -9988,17 +14357,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -10006,7 +14375,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -10014,52 +14383,57 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + Show Color Identity - + Show Tags On Deck Previews - + + Show Upload Time + + + + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -10067,7 +14441,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -10075,22 +14449,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -10098,17 +14472,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + + Select decks to share + + + + Visual Deck Storage Settings @@ -10116,177 +14495,202 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK U redu - + &Cancel - + Warn user for misconduct - - + + Error - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - - Search by set name, code, or type - - - - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - + + Search by set name, code, type, or release date + + + + How to use custom card art - + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -10294,93 +14698,85 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing - + pile view - - i18n - - - English - Srpski (Serbian) - - main - + Connect on startup - + Debug to file @@ -10394,7 +14790,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Deck Editor @@ -10475,7 +14871,7 @@ Please refrain from engaging in this activity or further actions may be taken ag - + Replays @@ -10590,129 +14986,129 @@ Please refrain from engaging in this activity or further actions may be taken ag - - + + Load Deck from Clipboard... - + Edit Deck in Clipboard, Annotated - + Edit Deck in Clipboard - + New Deck - + Open Custom Pictures Folder - + Print Deck... - + Delete Card - - + + Reset Layout - + Save Deck - + Save Deck as... - + Save Deck to Clipboard, Annotated - + Save Deck to Clipboard, Annotated (No Set Info) - + Save Deck to Clipboard - + Save Deck to Clipboard (No Set Info) - + Load Local Deck... - + Load Remote Deck... - + Set Ready to Start - + Toggle Sideboard Lock - + Add Green Counter - + Remove Green Counter - + Set Green Counters... - + Add Red Counter - + Remove Red Counter - + Set Red Counters... - + Add Life Counter @@ -10722,724 +15118,754 @@ Please refrain from engaging in this activity or further actions may be taken ag - + + Load deck from online service... + + + + + Load from website... + + + + Unload Deck - + Force Start - + Add Card Counter (F) - + Remove Card Counter (F) - + Set Card Counters (F)... - + Add Card Counter (E) - + Remove Card Counter (E) - + Set Card Counters (E)... - + Add Card Counter(D) - + Remove Card Counter (D) - + Set Card Counters (D)... - + Add Card Counter (C) - + Remove Card Counter (C) - + Set Card Counters (C)... - + Add Card Counter (B) - + Remove Card Counter (B) - + Set Card Counters (B)... - + Add Card Counter (A) - + Remove Card Counter (A) - + Set Card Counters (A)... - + Remove Life Counter - + Set Life Counters... - + Add White Counter - + Remove White Counter - + Set White Counters... - + Add Blue Counter - + Remove Blue Counter - + Set Blue Counters... - + Add Black Counter - + Remove Black Counter - + Set Black Counters... - + Add Colorless Counter - + Remove Colorless Counter - + Set Colorless Counters... - + Add Other Counter - + Remove Other Counter - + Set Other Counters... - + Increment all card counters - + Add Power (+1/+0) - + Remove Power (-1/-0) - + Move Toughness to Power (+1/-1) - + Add Toughness (+0/+1) - + Remove Toughness (-0/-1) - + Move Power to Toughness (-1/+1) - + Add Power and Toughness (+1/+1) - + Remove Power and Toughness (-1/-1) - + Set Power and Toughness... - + Reset Power and Toughness - + Untap - + Upkeep - + Draw Izvlačenje - + First Main Phase - + Start Combat - + Attack Napad - + Block - + Damage - + End Combat - + Second Main Phase - + End Kraj - + Next Phase - + Next Phase Action - + Next Turn - + Hide Card in Reveal Window - + Tap / Untap Card - + Untap All - + Toggle Skip Untapping Toggle Untap - + Turn Card Over - + Peek Card - + Play Card - + Play Card, Face Down - + Attach Card... - + Unattach Card - + Clone Card - + Create Token... - + Create All Related Tokens - + Create Another Token - + Set Annotation... - + + Reduce Life by Power + + + + Select All Cards in Zone - + Select All Cards in Row - + Select All Cards in Column - + Reveal Selected Cards to All Players - - + + Bottom of Library - - - - + + + + Exile Izgnanstvo - - - - + + + + Graveyard Groblje + + + Report Queue + + + + + Moderation + + - - + + Hand Ruka - - + + Top of Library - - + + Battlefield, Face Down - + Battlefield - + Library Špil - + Sideboard - + Top Cards of Library - + Bottom Cards of Library - + Close Recent View - - + + Stack - - + + Graveyard (Multiple) - - + + Graveyard (Multiple), Face Down - - + + Exile (Multiple) - - + + Exile (Multiple), Face Down - + Stack Until Found - + Draw Bottom Card - + Draw Multiple Cards from Bottom... - + Draw Arrow... - + Remove Local Arrows - + Leave Game - + Concede - + Roll Dice... - + + Flip Coin + + + + Shuffle Library - + Shuffle Top Cards of Library - + Shuffle Bottom Cards of Library - + Mulligan - + Mulligan (Same hand size) - + Mulligan (Hand size - 1) - + Draw a Card - + Draw Multiple Cards... - + Undo Draw - + Always Reveal Top Card - + Always Look At Top Card - + Sort Hand by Name - + Sort Hand by Type - + Sort Hand by Mana Value - + Reveal Hand to All Players - + Reveal Random Card to All Players - + Rotate View Clockwise - + Rotate View Counterclockwise - + Unfocus Text Box - + Focus Chat - + Clear Chat - + Refresh - + Skip Forward - + Skip Backward - + Skip Forward by a lot - + Skip Backward by a lot - + Play/Pause - + Toggle Fast Forward - + Home - + Visual Deck Storage - + Deck Storage - + Server - + Account - + Administration - + Logs diff --git a/cockatrice/translations/cockatrice_sv.ts b/cockatrice/translations/cockatrice_sv.ts index 2f9eb911b..727f43588 100644 --- a/cockatrice/translations/cockatrice_sv.ts +++ b/cockatrice/translations/cockatrice_sv.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &Placera polett... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter - + New value for counter '%1': @@ -36,64 +36,113 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. - + There are no cards in your deck to be exported + + AccountSetupPage + + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + AddAnalyticsPanelDialog @@ -105,12 +154,12 @@ Please check that the directory is writable and try again. AdminNotesDialog - + Update Notes - + Admin Notes for %1 @@ -136,218 +185,304 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error - + Could not create themes directory at '%1'. - + Theme settings Temainställningar - + Current theme: Aktuellt tema: - + Open themes folder - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering Kortrendering - + Display card names on cards having a picture Visa kortnamn på kort som har bilder - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Light - + Dark - + + System - + + Show all playmats + + + + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + Active theme palette: - + + Active theme style: + + + + + Qt widget style saved to this theme ("System" lets the application decide) + + + + Edit theme palette - + Home tab settings - + Display card name of background in bottom right - + + Dim the home tab background + + + + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + Styling settings - + Style user list - + Card printings - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over Skala kort på musen över - + Use rounded card corners - + Card layout - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout Handlayout - + Display hand horizontally (wastes space) Visa hand horisontellt (slösar plats) - + Enable left justification Aktivera vänsterjustering - + Table grid layout Bordets rutnätlayout - + Invert vertical coordinate Invertera vertikal koordinat - + Minimum player count for multi-column layout: Minst antal spelare för multi-kolumnlayout: - + Maximum font size for information displayed on cards: Maximal teckenstorlek för information som visas på kort: @@ -355,12 +490,12 @@ Please check that the directory is writable and try again. ArchidektApiResponseDeckDisplayWidget - + Back to results - + Open Deck in Deck Editor @@ -386,112 +521,112 @@ Please check that the directory is writable and try again. BanDialog - + ban &user name bannlys användar&namn - + ban &IP address bannlys &IP address - + ban client I&D förbjuda klienten I & D - + Ban type Typ av bannlysning - + &permanent ban &permanent bannlysning - + &temporary ban &temporär bannlysning - + &Days: &Dagar: - + &Hours: &Timmar: - + &Minutes: &Minuter: - + Duration of the ban Bannlysningens längd - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. Vänligen ange anledningen till bannlysningen. Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. - + Please enter the reason for the ban that will be visible to the banned person. Vänligen ange en anledning för bannlysningen som kommer att visas för den bannlysta personen. - + Redact all messages from this user in all rooms - + &OK &OK - + &Cancel &Avbryt - + Ban user from server Bannlys användaren från servern - - - - + + + + Error Fel - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. Du måste välja en namnbaserad, IP-baserad, clientId-baserad eller en kombination av de tre för att lägga ett förbud. - + You must have a value in the name ban when selecting the name ban checkbox. Du måste ha ett värde i namnförbudet när du markerar kryssrutan för namnförbud. - + You must have a value in the ip ban when selecting the ip ban checkbox. Du måste ha ett värde i ip-förbudet när du markerar kryssrutan ip-banan. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. Du måste ha ett värde i clientidförbudet när du markerar kryssrutan för klientidförbud. @@ -499,22 +634,22 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. BetaReleaseChannel - + Beta - + No reply received from the release update server. Inget svar mottaget från uppdateringsservern. - + Invalid reply received from the release update server. Ogiltigt svar mottaget från uppdateringsserveren. - + No reply received from the file update server. Inget svar mottaget från filuppdateringsservern. @@ -522,7 +657,20 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardArtPreviewWidget - + + Banner preview + + + + + + + + Zoom %1× + + + + No card selected @@ -530,22 +678,22 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardArtRulesModel - + Card - + ProviderId - + Mode - + Reason @@ -553,65 +701,209 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardDatabaseModel - + Name Namn - + Sets Utgåvor - + Mana cost Manakostnad - + Card type Korttyp - + P/T P/T - + Color(s) Färg(er) + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + CardDatabaseView - + Add to Deck - + Add to Sideboard - + Select Printing - + + Add to Zone + + + + + Create &new zone... + + + + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards @@ -721,22 +1013,22 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardInfoFrameWidget - + Image - + Description - + Both - + View transformation @@ -744,22 +1036,22 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -767,27 +1059,27 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardInfoTextWidget - + Name: - + Set: - + Collector Number: - + Related cards: - + Unknown card: @@ -795,129 +1087,129 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Reduce life by power - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -938,7 +1230,7 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. CardSizeWidget - + Card Size @@ -1078,6 +1370,24 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser @@ -1107,280 +1417,382 @@ Detta sparas endast för moderatorer och kan inte ses av den bannlysta personen. + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + ConnectionController - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - - - - - - - - - - - + + + + + + + + + + - - + + + Error - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + Server timeout - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Information - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -1388,113 +1800,113 @@ To update your client, go to Help -> Check for Updates. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - + Requesting forgotten password to %1 as %2... - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. @@ -1544,7 +1956,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1560,17 +1972,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add card to &maindeck - + Add card to &sideboard @@ -1578,97 +1990,158 @@ To update your client, go to Help -> Check for Updates. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + Format: - + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1676,17 +2149,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1694,114 +2167,119 @@ To update your client, go to Help -> Check for Updates. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + + Share deck... + + + + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1809,7 +2287,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1817,110 +2295,152 @@ To update your client, go to Help -> Check for Updates. DeckEditorSettingsPage - - + + Update Spoilers Uppdatera Spoilers - + Success - + Download URLs have been reset. - + Add URL Lägg till webbadress - - + + URL: - - + + Edit URL - + Add New URL - + Remove URL - + Updating... Uppdaterar ... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path Välj sökväg - + URL Download Priority - + Spoilers Spoilers - + Download Spoilers Automatically Ladda ner Spoilers automatiskt - + Spoiler Location: Spoiler Plats: - + Last Change Senaste ändring - + Spoilers download automatically on launch Spoilers laddas ner automatiskt vid lanseringen - + Press the button to manually update without relaunching Tryck på knappen för att manuellt uppdatera utan omstart - + Do not close settings until manual update is complete - + Download card pictures on the fly Ladda enkelt ned bilder på kort - + How to add a custom URL Hur man lägger till en valfri webbadress - + Reset Download URLs Återställ nedladdnings-webbadresser @@ -1928,32 +2448,32 @@ To update your client, go to Help -> Check for Updates. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1961,27 +2481,27 @@ To update your client, go to Help -> Check for Updates. DeckListModel - + Count - + Set - + Number Nummer - + Provider ID - + Card Kort @@ -2002,17 +2522,22 @@ To update your client, go to Help -> Check for Updates. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -2020,7 +2545,7 @@ To update your client, go to Help -> Check for Updates. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -2028,62 +2553,62 @@ To update your client, go to Help -> Check for Updates. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2091,94 +2616,109 @@ To update your client, go to Help -> Check for Updates. DeckPreviewWidget - + Banner Card - + Open in deck editor - + + Share deck... + + + + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Rename deck + + + + + Rename file + + + + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -2186,57 +2726,102 @@ To update your client, go to Help -> Check for Updates. DeckStateManager - + Rename deck to "%1" from "%2" - + Updated comments (was %1 chars, now %2 chars) - + Set banner card to %1 (%2) - + + Set playmat to %1 + + + + Tags changed - + Set format to %1 - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + %1 1 × "%2" (%3) - + Added - + Removed @@ -2258,94 +2843,122 @@ To update your client, go to Help -> Check for Updates. DeckViewContainer - + Load deck... Ladda lek... - + Load remote deck... Ladda remote lek... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start - + Force start - + Sideboard unlocked - + Sideboard locked - - + + Error Fel - + The selected file could not be loaded. Den valda filen kunde ej laddas. - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? - + Remember and automatically apply choice in the future @@ -2358,143 +2971,143 @@ This will kick all non-ready players from the game. Hämtar ... - + Known Hosts Kända värdar - + Delete the currently selected saved server - + Refresh the server list with known public servers Uppdatera servern listan med kända offentliga servrar - + New Host Ny värd - + Name: Namn: - + &Host: &Värd: - + &Port: &Port: - + Player &name: Spelar&namn: - + P&assword: &Lösenord: - + &Save password &Spara lösenord - + A&uto connect Anslut automatiskt - + Automatically connect to the most recent login when Cockatrice opens Koppla automatiskt till den senaste inloggningen när Cockatrice öppnas - + If you have any trouble connecting or registering then contact the server staff for help! Om du har problem med att ansluta eller registrera kontaktar du serverns personal för hjälp! - - + + Webpage Webbsida - + Reset Password - + Forgot password? - + &Connect &Ansluta - + Server Server - + Login Logga in - + Server Contact Serverkontakt - + Connect to Server Anslut till servern - + Server URL Server webbadress - + Communication Port - + Unique Server Name Unikt servernamn - + Connection Warning Anslutningsvarning - + You need to name your new connection profile. Du måste ange din nya anslutningsprofil. - + Connect Warning Anslutningsvarning - + The player name can't be empty. Fältet för spelarnamn kan inte lämnas tomt. @@ -2502,122 +3115,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings Kom ihåg inställningar - + &Description: &Beskrivning: - + P&layers: &Spelare: - + General Allmän - + Game type Speltyp - + &Password: &Lösenord: - + Only &buddies can join Endast &vänner kan ansluta - + Only &registered users can join Endast &registerade användare kan ansluta - + Joining restrictions Anslutingsbegränsningar - + &Spectators can watch Åskådare tillåtna - + Spectators &need a password to watch Åskådare behöver lösenord - + Spectators can &chat Åskådare kan &chatta - + Spectators can see &hands Åskådare kan se spelares händer - + Create game as spectator - + Spectators Åskådare - + Starting life total: - + Open decklists in lobby - + Create game as judge - + Game setup options - + &Clear Tydlig - + Create game Skapa spel - + Game information Spelinformation - + Error Fel - + Server error. Serverfel. @@ -2625,97 +3238,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &Namn: - + Token Jetong - + C&olor: &Färg: - + white vit - + blue blå - + black svart - + red röd - + green grön - + multicolor multifärgad - + colorless färglös - + &P/T: &P/T: - + &Annotation: &Annotering: - + &Destroy token when it leaves the table &Förstör jetong när den lämnar bordet - + Create face-down (Only hides name) - + Token data Jetongdata - + Show &all tokens Visa alla &jetonger - + Show tokens from this &deck Visa jetonger från denna &lek - + Choose token from list Välj jetong från lista - + Create token Skapa jetong @@ -2723,53 +3336,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + ✖ - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2778,7 +3391,7 @@ This will kick all non-ready players from the game. DlgEditAvatar - + No image chosen. Ingen bild vald. @@ -2790,27 +3403,32 @@ To remove your current avatar, confirm without choosing a new image. För att radera din nuvarande avatar, klicka "bekräfta" utan att välja en ny bild. - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... Leta... - + Change avatar Ändra avatar - + Open Image Öppna bild - + Image Files (*.png *.jpg *.bmp) Bildfiler (*.png *.jpg *.bmp) - + Invalid image chosen. Ogiltigt val av bild. @@ -2836,38 +3454,38 @@ För att radera din nuvarande avatar, klicka "bekräfta" utan att väl DlgEditPassword - + Old password: Ditt gamla lösenord: - + New password: Nytt lösenord: - + Confirm new password: Bekräfta ditt nya lösenord: - + Change password Ändra lösenord - - + + Error Fel - + Your password is too short. - + The new passwords don't match. Dina nya lösenord matchar inte. @@ -2875,93 +3493,93 @@ För att radera din nuvarande avatar, klicka "bekräfta" utan att väl DlgEditTokens - + &Name: &Namn: - + C&olor: &Färg: - + white vit - + blue blå - + black svart - + red röd - + green grön - + multicolor multifärgad - + colorless färglös - + &P/T: &P/T: - + &Annotation: &Annotering: - + Token data Jetongdata - - + + Add token Lägg till jetong - + Remove token Ta bort jetong - + Edit custom tokens - + Please enter the name of the token: Vänligen ange namnet på jetongen: - + Error Fel - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. Det valda namnet står i konflikt med ett befintligt kort eller token. @@ -3040,7 +3658,8 @@ Se till att du aktiverar "Token" i dialogrutan "Hantera uppsättn - Hide games that have started + Hide started games + Hide games that have started @@ -3076,8 +3695,9 @@ Se till att du aktiverar "Token" i dialogrutan "Hantera uppsättn - &Creator name: - S&kaparens namn: + &Host name: + &Creator name: + @@ -3143,52 +3763,52 @@ Se till att du aktiverar "Token" i dialogrutan "Hantera uppsättn DlgForgotPasswordChallenge - + Reset Password Challenge Warning - + A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. - + &Host: &Värd: - + &Port: &Hamn: - + Player &name: - + Email: E-post: - + Reset Password Challenge - + Reset Password Challenge Error - + The email address can't be empty. E-postadressen kan inte vara tom. @@ -3196,37 +3816,37 @@ Se till att du aktiverar "Token" i dialogrutan "Hantera uppsättn DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. - + &Host: &Värd: - + &Port: &Hamn: - + Player &name: - + Reset Password Request - + Reset Password Error - + The player name can't be empty. Spelarens namn kan inte vara tomt. @@ -3234,94 +3854,127 @@ Se till att du aktiverar "Token" i dialogrutan "Hantera uppsättn DlgForgotPasswordReset - + Reset Password Warning - + A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. - + &Host: &Värd: - + &Port: &Hamn: - + Player &name: - + Token: Tecken: - - + + New Password: Nytt lösenord: - + Reset Password - + The player name can't be empty. Spelarens namn kan inte vara tomt. - - - - + + + + Reset Password Error - + The token can't be empty. Token kan inte vara tomt. - + The new password can't be empty. Det nya lösenordet kan inte vara tomt. - + Error - + Your password is too short. - + The passwords do not match. Lösenorden stämmer inte överens. + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck @@ -3405,165 +4058,566 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLocalGameOptions - + Players: - + General - + Starting life total: - + Game setup options - + Remember settings - + Local game options + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: Värd: - + &Port: Port: - + Player &name: Spelarnamn: - + P&assword: Lösenord: - + Password (again): Lösenord (igen): - + Email: E-post: - + Email (again): E-post (igen): - + Country: Land: - + Undefined Odefinierad - + Real name: Riktiga namn: - + Register to server Registrera på server - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning Registreringsvarning - + Your password is too short. - + Your passwords do not match, please try again. Dina lösenord matchar inte, var god försök igen. - + Your email addresses do not match, please try again. Dina e-postadresser matchar inte, var god försök igen. - + The player name can't be empty. Fältet för spelarnamn kan inte lämnas tomt. + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice @@ -3629,46 +4683,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database Ett okänt fel har inträffat vid laddande av kortdatabasen - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - Din kortdatabs är ogiltig. - -Cockatrice kan möjligen funger dåligt eller fel med en ogiltig databas. - -Möjligen kan du behöva starta om oracle för att uppdatera din kortdatabas. - -Vill du ändra dina databaslokaliseringsinställningar? - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - Din kortdatabas är för gammal. - -Detta kan skapa problem med laddandet av kortinformation eller bilder - -Detta kan oftast fixas genom att starta om oracle för att uppdatera din kortdatabas. - -Vill du ändra dina kortdatabaslokaliseringsinställningar? - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3677,7 +4697,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3686,7 +4706,7 @@ Would you like to change your database location setting? Vill du ändra dina kortdatabaslokaliseringsinställningar? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3695,7 +4715,7 @@ Would you like to change your database location setting? Vill du ändra dina kortdatabaslokaliseringsinställningar? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3704,104 +4724,235 @@ Would you like to change your database location setting? - - - + + + Error Fel - + The path to your deck directory is invalid. Would you like to go back and set the correct path? Sökvägen till din lekkatalog är ogiltig. Vill du gå tillbaka och ange den korrekta sökvägen? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? Sökvägen till din kortbildsdatabas är ogiltig. Vill du gå tillbaka och ange den korrekta sökvägen? - + Settings Inställningar - + + Search settings... + + + + + OK + + + + General Allmänt - + Appearance Utseende - + User Interface Gränssnitt - + Storage - + Card Sources - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat Chatt - + Sound Ljud - + Shortcuts Genvägar + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3809,17 +4960,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next Nästa - + Previous Tidigare - + Tip of the Day Dagens Tips @@ -3827,165 +4978,175 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel Nuvarande släppkanal - + Reinstall Installera - + Cancel Download Avbryt Ladda ner - + Open Download Page Öppna nedladdningssidan - + Check for Client Updates - - - - + + + + Error Fel - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. Cockatrice byggdes inte med SSL-stöd, därför kan du inte ladda ner uppdateringar automatiskt! Besök sidan för nedladdning för att uppdatera manuellt. - + Downloading update: %1 - + Checking for updates... Letar efter nya uppdateringar... - + Finished checking for updates Slutfört efter uppdateringar - + No Update Available Ingen uppdatering tillgänglig - + Cockatrice is up to date! Cockatris är aktuell! - + You are already running the latest version available in the chosen release channel. Du kör redan den senaste versionen i den valda utgåvan. - + Current version Aktuell version - + Selected release channel Vald utgivningskanal - - + + Update Available Uppdatering tillgänglig - - + + A new version of Cockatrice is available! En ny version av Cockatrice är tillgänglig! - - + + New version Ny version - - + + Released Släppte - - + + Changelog Ändringslogg - + Do you want to update now? Vill du uppdatera nu? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error Uppdateringsfel - + An error occurred while checking for updates: Ett fel uppstod när du letade efter uppdateringar: - + An error occurred while downloading an update: Ett fel uppstod när du hämtade en uppdatering: - + Installing... Installerar... - + Cockatrice is unable to open the installer. Cockatrice kan inte öppna installatören. - + Try to update manually by closing Cockatrice and running the installer. Försök att uppdatera manuellt genom att stänga Cockatrice och köra installationsprogrammet. - + Download location Hämta plats @@ -3993,17 +5154,17 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing Tydlig logg vid stängning - + Copy to clipboard - + Debug Log Felsökningslogg @@ -4241,7 +5402,7 @@ You may have to manually download the new version. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -4249,7 +5410,7 @@ You may have to manually download the new version. FilterBuilder - + Type your filter here Skriv ditt filter här @@ -4257,45 +5418,88 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -4303,140 +5507,156 @@ You may have to manually download the new version. GameSelector - - - - - - - - - + + + + + + + + + Error Fel - + Please join the appropriate room first. Vänligen anslut till det lämpliga rummet först. - + Wrong password. Fel lösenord. - + Spectators are not allowed in this game. Åskådare är ej tillåtna i detta spelet. - + The game is already full. Spelet är redan fullt. - + The game does not exist any more. Det här spelet finns inte längre. - + This game is only open to registered users. Det här spelet är bara öppet för registrerade användare. - + This game is only open to its creator's buddies. Det här spelet är bara öppet för skaparens vänner. - + You are being ignored by the creator of this game. Spelets skapare ignorerar dig. - + Join Game - + Spectate Game - + Game Information - + + Cop&y game link + + + + Join Game as Judge - + Spectate Game as Judge - + + Join game Anslut till spel - - Password: - Lösenord: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. Vänligen anslut till respektive rum först. - + &Filter games Specificera speltyp - + C&lear filter Rensa filter - + C&reate Skapa - + &Join Anslut - + Join as judge - + J&oin as spectator Anslut som &åskådare - + Join as judge spectator - + Games shown: %1 / %2 - + Games Spel @@ -4477,105 +5697,105 @@ You may have to manually download the new version. GamesModel - + >1 day - + %1%2 hr short age in hours - + new - + %1%2 min short age in minutes - + password lösenord - + buddies only endast vänner - + reg. users only endast reg. användare - + open decklists - - + + can chat kan chatta - + see hands visa händer - + can see hands Tillåts se händer - + not allowed ej tillåtna - + Room Rum - + Age Ålder - + Description Beskrivning - - Creator - Skapare + + Host + - + Type Typ - + Restrictions Begränsningar - + Players Spelare - + Spectators Åskådare @@ -4583,158 +5803,275 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + English + Svenska (Swedish) + + + + + + + + + Choose path Välj sökväg - + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> + + + + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + Language settings - + Language: Språk: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + Version settings - + Card database - + Startup settings - + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) Sökvägar (redigering inaktiverad i bärbart läge) - + Paths Sökvägar - + How to help with translations - + Decks directory: Lekkatalog: - + Filters directory: - + Replays directory: Repriskatalog: - + Pictures directory: Bildkatalog: - + Card database: Kortdatabas: - + Custom database directory: - + Token database: Token-databas: - + Update channel Uppdatera kanal - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client Meddela om en funktion som stöds av servern saknas i min klient - + Automatically run Oracle when running a new version of Cockatrice - + Show tips on startup Visa tips vid uppstart - + Last update check on %1 (%2 days ago) @@ -4742,47 +6079,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4876,59 +6213,353 @@ You may have to manually download the new version. + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu @@ -5146,410 +6777,431 @@ You may have to manually download the new version. MainWindow - - + + Player %1 Spelare %1 - + Load replay Ladda repris - + About Cockatrice Om Cockatrice - + Version - + Cockatrice Webpage - + Project Manager: Projektledare: - + Past Project Managers: Tidigare projektledare: - + Developers: utvecklare: - + Our Developers Våra utvecklare - + Help Develop! - + Translators: Översättare: - + Our Translators - + Help Translate! - + Support: - + Report an Issue - + Troubleshooting - + F.A.Q. - - + + Error Fel - + &Connect... &Anslut... - + &Disconnect &Frånkoppla - + Start &local game... Starta &lokalt spel... - + &Watch replay... &Titta på repris... - + &Full screen &Fullskärmsläge - + &Register to server... - + &Restore password... - + &Settings... &Inställningar... - + &Exit A&vsluta - + A&ctions - + &Cockatrice &Cockatrice - + C&ard Database - + &Manage sets... &Hantera utgåvor... - + Edit custom &tokens... Redigera egna &tokens... - + Open custom image folder - + Open custom sets folder - + Add custom sets/cards - + Reload card database - + Tabs - + &Help &Hjälp - + &About Cockatrice &Om Cockatrice - + &Tip of the Day - + Check for Client Updates - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + + Re-run Onboarding Wizard... + + + + Show/Hide - + + Sign-in cancelled + + + + New Version - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. - + Cockatrice installed - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. - + + Are you sure? + Är du säker? + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" - - + + Yes - - + + No - + Open settings - + New sets found - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + Yes, always enable - + View sets - + Welcome - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. - + Information - + A card database update is already running. - + Unable to run the card database updater: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - - - - - + + + + + Load sets/cards Ladda utgåvor/kort - + Selected file cannot be found. Den valda filen kunde inte laddas. - + You can only import XML databases at this time. För tillfället går det bara att importera XML-databaser. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. - + Sets/cards failed to import. Det gick inte att importera utgåvor/kort. @@ -5754,615 +7406,615 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play - + from their graveyard - + from exile från exil - + from their hand - + the top card of %1's library det översta kortet av %1's lek - + the top card of their library - + from the top of %1's library från toppen av %1's lek - + from the top of their library - + the bottom card of %1's library det sista kortet i %1's lek - + the bottom card of their library - + from the bottom of %1's library från botten av %1's lek - + from the bottom of their library - + from %1's library från %1'a lek - + from their library - + from sideboard från sidbrädan - + from the stack från stapeln - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1 håller nu det översta kortet %2 avslöjat. - + %1 is not revealing the top card %2 any longer. %1 håller inte längre det översta kortet %2 avslöjat. - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. %1 fäster %2 på %3s %4. - + %1 has conceded the game. - + %1 has unconceded the game. %1 har ångrat att ge upp spelet. - + %1 has restored connection to the game. - + %1 has lost connection to the game. - + %1 points from their %2 to themselves. - + %1 points from their %2 to %3. - + %1 points from %2's %3 to themselves. - + %1 points from %2's %3 to %4. - + %1 points from their %2 to their %3. - + %1 points from their %2 to %3's %4. - + %1 points from %2's %3 to their own %4. - + %1 points from %2's %3 to %4's %5. - + %1 creates a face down token. - + %1 creates token: %2%3. - + %1 has loaded a deck (%2). - + %1 has loaded a deck with %2 sideboard cards (%3). - + %1 destroys %2. - + a card ett kort - + %1 gives %2 control over %3. %1 ger kontroll över %3 till %2. - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1 sätter %2 i spel%3. - + %1 puts %2%3 into their graveyard face down. - + %1 puts %2%3 into their graveyard. - + %1 exiles %2%3 face down. - + %1 exiles %2%3. %1 sätter %2%3 i exil. - + %1 moves %2%3 to their hand. - + %1 puts %2%3 into their library. - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. - + %1 puts %2%3 into their library %4 cards from the top. %1 lägger %2%3 i sitt bibliotek %4 kort från toppen. - + %1 moves %2%3 to sideboard. %1 flyttar %2%3 till sidbrädan. - + %1 plays %2%3 face down. - + %1 plays %2%3. %1 spelar %2%3. - + %1 moves %2%3 to custom zone '%4' face down. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. %1 vänder %2 nedåt. - + %1 turns %2 face-up. %1 vänder %2 uppåt. - + The game has been closed. Spelet har stängts. - + The game has started. Spelet har börjat. - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. - + %1 is now watching the game. %1 kollar nu på spelet. - + You have been kicked out of the game. Du har blivit utsparkad från spelet. - + %1 has left the game (%2). %1 har lämnat spelet (%2) - + %1 is not watching the game any more (%2). %1 tittar inte längre på spelet (%2). - + %1 is not ready to start the game any more. - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. %1 blandar sin lek och drar en ny hand. - + You are watching a replay of game #%1. - + %1 is ready to start the game. - + cards an unknown amount of cards kort - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. - + %1 reveals %2. - + %1 randomly reveals %2%3 to %4. - + %1 randomly reveals %2%3. - + %1 peeks at face down card #%2. - + %1 peeks at face down card #%2: %3. - + %1 reveals %2%3 to %4. - + %1 reveals %2%3. - + %1 reversed turn order, now it's %2. - + reversed - + normal - + Heads - + Tails - + %1 flipped a coin. It landed as %2. - + %1 rolls a %2 with a %3-sided die. - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. %1's tur - + %1 sets annotation of %2 to %3. - + %1 places %2 %3%4 counter(s) on %5 (now %6). - + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + %1 failed to undo their last draw. - + %1 sets counter %2 to %3 (%4%5). - + %1 sets %2 to not untap normally. - + %1 sets %2 to untap normally. - + %1 removes the PT of %2. - + %1 changes the PT of %2 from nothing to %4. - + %1 changes the PT of %2 from %3 to %4. - + %1 has locked their sideboard. - + %1 has unlocked their sideboard. - + %1 taps their permanents. - + %1 untaps their permanents. - + %1 taps %2. - + %1 untaps %2. - + %1 shuffles %2. - + %1 shuffles the bottom %3 cards of %2. - + %1 shuffles the top %3 cards of %2. - + %1 shuffles cards %3 - %4 of %2. - + %1 unattaches %2. - + %1 undoes their last draw. - + %1 undoes their last draw (%2). @@ -6370,115 +8022,125 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 - + Add New Message - + Edit Message - + Remove Message - + Add message Lägg till meddelande - - + + Message: Meddelande: - + Edit message - + Chat settings - + Custom alert words - + Enable chat mentions - + Enable mention completer - + In-game message macros - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users - + Ignore private messages sent by unregistered users - + Ignore private messages sent by non-buddy users - - + + Invert text color - + Enable desktop notifications for private messages - + Enable desktop notification for mentions - + Enable room message history on join - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) - + Separate words with a space, alphanumeric characters only @@ -6486,42 +8148,42 @@ Cockatrice will now reload the card database. MoveMenu - + Move to - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + T&able - + &Hand - + &Graveyard - + &Exile @@ -6600,148 +8262,134 @@ Cockatrice will now reload the card database. PaletteEditorDialog - - + + Reset - - - Apply - - - - - - Save && Apply - - - - + ▼ Edit Palette - - + + ▶ Edit Palette - + Palette Editor — %1 - + <b>Palette Editor</b> &nbsp;·&nbsp; %1 - - This theme ships no default palette files - - - - - Replace current colours with the theme author's defaults - - - - - Switch between the light and dark palette files + + + Save - Editing: + Delete this scheme's custom palette and revert to the theme default (or the application palette) - Show or hide the per-role colour grid for manual tweaks + Switch between the light and dark palette files + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + ↺ Revert to theme default - + Discard unsaved edits and restore the last saved palette - - Preview this palette without saving to disk + + Cannot save: this theme has no writable directory - + Write palette-%1.toml and reload the theme - - Cannot save: this theme has no directory on disk - - - - + + Save failed - + + Could not write %1 to: %2 - - - No default found - - - - - No default palette file found for the "%1" scheme. - - PaletteGridWidget - + Active - + Disabled - + Inactive - + Normal interactive state - + Widget is disabled / not interactive - + Window is in background / unfocused + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + Phase @@ -6864,30 +8512,21 @@ Cockatrice will now reload the card database. Slutsteget - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - PlayerActions - - - - + + + + grave - - - - + + + + exile @@ -7006,16 +8645,272 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters + + PlaymatCollectionDialog + + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + PrintingDisabledInfoWidget @@ -7036,12 +8931,12 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelector - + Display Navigation Buttons - + Printing Selector @@ -7049,25 +8944,65 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -7098,28 +9033,28 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -7135,95 +9070,103 @@ This setting means you'll only see the default printing for each card, instead o PtMenu - + Power / toughness - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services - + Hide %1 - + Hide Others - + Show All - + Preferences... - + Quit %1 - + About %1 @@ -7231,17 +9174,17 @@ This setting means you'll only see the default printing for each card, instead o QObject - + Cockatrice card database (*.xml) - + All files (*.*) Alla filer (*.*) - + Cockatrice replays (*.cor) Cockatricerepriser (*.cor) @@ -7261,17 +9204,22 @@ This setting means you'll only see the default printing for each card, instead o - + + Maybeboard + + + + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7282,7 +9230,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7293,100 +9241,110 @@ Are you sure you would like to disable this feature? - + Confirm Change + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK - + Save - + Save All - + Open - + &Yes - + Yes to &All - + &No - + N&o to All - + Abort - + Retry - + Ignore - + Close - + Cancel - + Discard - + Help - + Apply - + Reset - + Restore Defaults @@ -7394,121 +9352,189 @@ Are you sure you would like to disable this feature? QuickSetupPanel - + %1% - + <b>Quick Setup</b> - + Generate all palette roles automatically from a single accent colour - + Accent: - + Primary hue. Used directly for highlights and links. At high intensity it also tints buttons and backgrounds. - + Intensity: - + Subtle - + Full colour - + 0–30 Subtle tint — only highlights and links change hue 30–70 Accented — buttons, tooltips, and borders join in 70–100 Full colour — backgrounds, everything - + 70% - - - Generate ↓ - - - - - Derive all palette roles from the accent colour above. -Fine-tune individual colours in the grid afterwards. - - RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name Namn - + ID ID - + Upload time Uppladdningstidpunkt + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID ID - + Name Namn - + Players Spelare - + Keep Behåll - + Time started Tid startad - + Duration (sec) Varaktighet (sek) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu @@ -7550,37 +9576,37 @@ Fine-tune individual colours in the grid afterwards. RoomSelector - + Rooms Rum - + Joi&n &Anslut - + Room Rum - + Description Beskrivning - + Permissions - + Players Spelare - + Games Spel @@ -7588,7 +9614,7 @@ Fine-tune individual colours in the grid afterwards. SayMenu - + S&ay @@ -7596,32 +9622,32 @@ Fine-tune individual colours in the grid afterwards. SequenceEdit - + Choose an action from the table - + Hit the key/combination of keys you want to set for this action - + Invalid key - + Shortcut already in use by: - + Clear - + Restore default @@ -7654,56 +9680,92 @@ Fine-tune individual colours in the grid afterwards. + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts - + Do you really want to restore all default shortcuts? - + Clear all default shortcuts - + Do you really want to clear all shortcuts? - + Section: - + Action: - + Shortcut: - + + Shortcut editor + + + + How to set custom shortcuts - + Clear all shortcuts - + Search by shortcut name @@ -7757,12 +9819,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -7770,27 +9832,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds - + Current sounds theme: - + Test system sound engine - + Sound settings - + Master volume @@ -7798,48 +9860,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended - + Deleting spoiler.xml. Please run Oracle - - + + Spoilers download failed - + No internet connection - + Error - + Spoilers already up to date - + No new spoilers added - + Spoilers have been updated! - + Last change: @@ -7847,157 +9909,170 @@ Please check your shortcut settings! StableReleaseChannel - + Default - + No reply received from the release update server. - + Invalid reply received from the release update server. - + No reply received from the tag update server. - + Invalid reply received from the tag update server. + + StatsTally + + + Total Power + + + + + Total Toughness + + + StorageSettingsPage - - - + + + Success - + Cached card pictures have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + In-memory (currently loaded) card pictures have been reset. - + Card Picture Loader Caching Method: - + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. - + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. - + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. - + Delete Cached Images - + Delete Saved Images - + Clear In-Memory Images - + Card Picture Loader Cache Method - + Network Cache - + Filesystem - + In-Memory Picture Cache - + Network Cache Size: - + On-disk cache for downloaded pictures - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + In-memory cache for pictures not currently on screen - + Naming scheme: - + Day(s) @@ -8005,17 +10080,33 @@ Please check your shortcut settings! TabAccount - + + + My Reports + + + + Add to Buddy List - + Add to Ignore List - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account @@ -8136,118 +10227,118 @@ Please check your shortcut settings! TabArchidekt - - - + + + Desc. - - + + AND - - + + Require ALL selected colors - - + + Deck name... - - + + Owner... - - + + Packages - - + + Advanced Filters - + Bracket: - - + + Any - - + + Contains card... - - + + Commander... - - + + Tag... - - + + Deck Size - + Cards: - - + + Asc. - + Sort by: - + Filter by: - + Display Settings - - + + Search - - + + Formats @@ -8260,42 +10351,42 @@ Please check your shortcut settings! TabCardArtRules - + Card: - + ProviderId: - + Mode: - + Reason: - + Type a card name... - + Add rule - + Remove rule - + Refresh @@ -8308,52 +10399,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info - + Deck - + Filters - + &View - + Card Database - + Printing - + Visible - + Floating - + Reset layout - + Deck: %1 Lek: %1 @@ -8361,49 +10452,49 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - + &View - + Printing - + Visible - + Floating - + Reset layout @@ -8411,22 +10502,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -8434,138 +10525,236 @@ Please check your shortcut settings! TabDeckStorage - + Local file system Lokalt filsystem - + Server deck storage Serverns leklagring - - + + Open in deck editor Öppna i lekredigeraren - + Rename deck or folder - + Upload deck Ladda upp lek - + Download deck Ladda ner lek - - - - + + + + New folder Ny mapp - - + + Delete Radera - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + + + + Error - + Rename failed - - + + + Invalid deck file - + Enter deck name Ange leknamn - + This decklist does not have a name. Please enter a name: Denna leklista har inget namn. Vänligen ange ett namn: - + Unnamed deck Namnlös lek - + Failed to upload deck to server - + Delete local file Radera lokal fil - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: Namn på den nya mappen: - + Deck Storage @@ -8573,20 +10762,271 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -8599,27 +11039,27 @@ Please enter a name: TabEdhRecMain - + &Cards - + Top Commanders - + Tags - + Search for a card ... - + Search @@ -8632,191 +11072,207 @@ Please enter a name: TabGame - - - + + + Replay - - + + Game - - + + Player List - - + + + Invite + + + + + Card Info - - + + Messages - - + + Replay Timeline - + &Phases &Faser - + &Game &Spel - + Next &phase Nästa &fas - + Next phase with &action - + Next &turn Nästa &tur - + Reverse turn order - + &Remove all local arrows Ta &bort alla lokala pilar - + Rotate View Cl&ockwise - + Rotate View Co&unterclockwise - + Game &information Spel&information - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede - - - + + + &Concede &Ge upp - + &Leave game &Lämna spel - + C&lose replay S&täng repris - + &Focus Chat - + &Say: S&äg: - + Selected cards - + &View - + Visible - + Floating - + Reset layout - + Concede Ge upp - + Are you sure you want to concede this game? Är du säker på att du vill ge upp detta spel? - + Unconcede - + You have already conceded. Do you want to return to this game? - + Leave game Lämna spel - + Are you sure you want to leave this game? Är du säker på att du vill lämna detta spel? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. @@ -8832,157 +11288,157 @@ Please enter a name: TabLog - + Logs - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName - + Room Logs - + Game Logs - + Chat Logs - - + + Error - + You must select at least one filter. - + You have to select a valid number of days to locate. - + Username: - + IP Address: - + Game Name: - + GameID: - + Message: - + Main Room - + Game Room - + Private Chat - + Past X Days: - + Today - + Last Hour - + Maximum Results: - + At least one filter is required. The more information you put in, the more specific your results will be. - + Get User Logs - + Clear Filters - + Filters - + Log Locations - + Date Range - + Maximum Results - - + + Message History - + Failed to collect message history information. - + There are no messages for the selected filters. @@ -8990,218 +11446,558 @@ The more information you put in, the more specific your results will be. TabMessage - + Private &chat - + &Leave &Lämna - + %1 - Private chat - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. - + Private message from - + %1 has left the server. %1 har lämnat servern. - + %1 has joined the server. %1 har anslutit sig till servern. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system Lokalt filsystem - + Server replay storage Serverns reprislagring - - + + Watch replay Titta på repris - + Rename - - + + New folder - - + + Delete Radera - + Open replays folder - + Download replay Ladda ner repris - + Toggle expiration lock Växla utgångslås - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file Radera lokal fil - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay Radera avlägsen repris @@ -9211,65 +12007,478 @@ The more information you put in, the more specific your results will be. + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - - Friends - - - - - Online - - - - - Ignored - - - - + &Say: &Säg: - + Chat Chatt - + &Room &Rum - + &Leave room &Lämna rum - + &Clear chat - + Chat Settings... - + mentioned you. - + Click to view - + You are flooding the chat. Please wait a couple of seconds. Du översvämmar chatten. Var vänlig vänta ett par sekunder. @@ -9277,35 +12486,35 @@ The more information you put in, the more specific your results will be. TabServer - + Server - - - - + + + + Error - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -9313,97 +12522,173 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - + Archidekt - + Home - + &Visual Deck Storage - + Visual Database Display - + Server - + Account - + Deck Storage - + Game Replays - + Administration - + Logs - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? - + There are still open games. Are you sure you want to quit? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view - + Your buddy %1 has signed on! - + Unknown Event - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -9411,38 +12696,38 @@ To update your client, go to Help -> Check for Updates. Servern har skickat dig ett meddelande som din klient inte förstår. Det här meddelandet kan betyda att det finns en ny version av Cockatrice tillgänglig eller så kör den här servern en användarinställd eller förhandsutgiven version. För att uppdatera din klient, gå till Hjälp -> Leta efter uppdateringar. - + Idle Timeout - + You are about to be logged out due to inactivity. Du kommer snart loggas ut på grund av inaktivitet. - + Promotion - + You have been promoted. Please log out and back in for changes to take effect. - + Warned Varnad - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. - + You have received the following message from the server. (custom messages like these could be untranslated) @@ -9461,6 +12746,34 @@ Please refrain from engaging in this activity or further actions may be taken ag + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -9475,6 +12788,65 @@ Please refrain from engaging in this activity or further actions may be taken ag + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -9544,57 +12916,52 @@ Please refrain from engaging in this activity or further actions may be taken ag UserCardArtSettingsDialog - + Card Art Settings - + Type a card name... - + Card name: - - Card ProviderId: + + Printing: - + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + Left margin (%): - + Right margin (%): - - Vertical offset: - - - - - Zoom: - - - - - Parameters - - - - - Preview - - - - + Remove Banner Card @@ -9602,216 +12969,266 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details Användar&detaljer - + Private &chat - + Show this user's &games Visa denna användarens &spel - - - Add to &buddy list - Lägg till som &vän - - - - Remove from &buddy list - Ta bort som &vän - - - - Add to &ignore list - &Ignorera - - - - Remove from &ignore list - Sluta &ignorera - - - - Kick from &game - &Sparka från spelet - - - - Warn user - - - View user's war&n history + View this user's &public decks - Ban from &server - &Bannlys från servern + Add to &buddy list + Lägg till som &vän - View user's &ban history - + Remove from &buddy list + Ta bort som &vän - &Promote user to moderator - + Add to &ignore list + &Ignorera - Dem&ote user from moderator - + Remove from &ignore list + Sluta &ignorera - Promote user to &judge - + Kick from &game + &Sparka från spelet - Demote user from judge + Report user + Warn user + + + + + View user's war&n history + + + + + Ban from &server + &Bannlys från servern + + + + View user's &ban history + + + + + &Promote user to moderator + + + + + Dem&ote user from moderator + + + + + Promote user to &judge + + + + + Demote user from judge + + + + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes - - + Investigate user + + + + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games %1's spel - - - + + + Ban History - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason - + User has never been banned. - + Failed to collect ban information. - - - + + + Warning History - + Warning Time;Moderator;User Name;Reason - + User has never been warned. - + Failed to collect warning information. - + Failed to get admin notes. - - + + Success - + Successfully promoted user. - + Successfully demoted user. - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + Kick Player - + Are you sure you want to kick this player from the game? - - - + + + Failed - + Failed to promote user. - + Failed to demote user. - + Copy hash to clipboard - + Remove this user's messages @@ -9849,61 +13266,61 @@ Please refrain from engaging in this activity or further actions may be taken ag Administratör - + Moderator Moderator - + Registered user Registrerad användare - - + + Unregistered user Oregistrerad användare - + Judge - + Unknown - + The entered password does not match your account. - - - - + + + + Information - + User information updated. - - - - - - + + + + + - - - + + + + Error @@ -9927,283 +13344,292 @@ Please refrain from engaging in this activity or further actions may be taken ag Edit Banner Card + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + The selected card is blacklisted on this server or another error occurred. - + Banner card removed. - + Banner card updated. - + This server does not permit you to update your user informations. - - - Password changed. - - - This server does not permit you to change your password. + Password changed. - The new password is too short. + This server does not permit you to change your password. + The new password is too short. + + + + The old password is incorrect. - + Avatar updated. - + This server does not permit you to update your avatar. - + An error occured while trying to update your avatar. - - - This server does not permit you to update your user informations. - An error occured while trying to updater your avatar. - - UserInfoPopup - - + + Games - + Chat - + Open private chat - + Profile - + View user profile - + Show this user's games - + + Invite + + + + + Invite to your game + + + + − Buddy - + Remove from buddy list - + + Buddy - + Add to buddy list - + − Ignore - + Remove from ignore list - + + Ignore - + Add to ignore list - + Ban - + Ban from server - + Warn - + Warn user - + Ban log - + View ban history - + Warn log - + View warning history - + Notes - + View admin notes - + − Mod - + Demote from moderator - + + Mod - + Promote to moderator - + − Judge - + Demote from judge - + + Judge - + Promote to judge - + Join game - + Spectate - - + + Loading games… - + Could not load games. - + No active games. @@ -10211,223 +13637,344 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings Allmänna gränssnittsinställningar - + &Double-click cards to play them (instead of single-click) &Dubbelklicka på kort för att spela dem (istället för enkelklick) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens - + Show selection count during drag selection - + Show total selection count - - Show subtype breakdown in selection tally + + + Disabled - - Use tear-off menus, allowing right click menus to persist on screen + + + Enabled - - Keep game chat focused when clicking in game (Note: disables card view search bar) - - - - - Notifications settings - - - - - Enable notifications in taskbar - - - - - Notify in the taskbar for game events while you are spectating + + + Automatic + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Use tear-off menus, allowing right click menus to persist on screen + + + + + Keep game chat focused when clicking in game (Note: disables card view search bar) + + + + + Notifications settings + + + + + Enable notifications in taskbar + + + + + Notify in the taskbar for game events while you are spectating + + + + Notify in the taskbar when users in your buddy list connect - + Animation settings Animationsinställningar - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation &Tappnings/Upptappningsanimation - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings - + Open deck in new tab by default - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - - do nothing + + Do nothing - - ask to convert to .cod + + Ask to convert to .cod - - always convert to .cod + + Always convert to .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings - + Buffer time for backwards skip via shortcut: + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + Flip coin - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -10464,22 +14011,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... - + Save Filter - + Save all currently applied filters to a file - + Enter filename... @@ -10487,72 +14034,72 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterToolbarWidget - + Sort by - + Filter by - + Save and load filters - + Filter by exact card name - + Filter by card main-type - + Filter by card sub-type - + Filter by set - + Filter by format legality - + Save/Load - + Name - + Main Type - + Sub Type - + Sets - + Formats @@ -10654,7 +14201,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -10662,19 +14209,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -10720,28 +14267,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - - + + Visual - + Loading database ... - + Clear all filters - + Table @@ -10749,43 +14296,43 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat @@ -10801,12 +14348,12 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -10814,17 +14361,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -10832,7 +14379,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -10840,52 +14387,57 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + Show Color Identity - + Show Tags On Deck Previews - + + Show Upload Time + + + + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -10893,7 +14445,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -10901,22 +14453,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -10924,17 +14476,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + + Select decks to share + + + + Visual Deck Storage Settings @@ -10942,177 +14499,202 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? - + Redact all messages from this user in all rooms - + &OK - + &Cancel - + Warn user for misconduct - - + + Error - + User name to send a warning to can not be blank, please specify a user to warn. - + Warning to use can not be blank, please select a valid warning to send. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top - + Move selected set up - + Move selected set down - + Move selected set to the bottom - - Search by set name, code, or type - - - - + Default order - + Restore original art priority order - + Enable all sets - + Disable all sets - + Enable selected set(s) - + Disable selected set(s) - + Deck Editor - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art - - How to use custom card art + + Search by set name, code, type, or release date + How to use custom card art + + + + Hints - + Note - + Sorting by column allows you to find a set while not changing set priority. - + To enable ordering again, click the column header until this message disappears. - + Use the current sorting as the set priority instead - + Sorts the set priority using the same column - + Manage sets @@ -11120,93 +14702,85 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing blanda när du stänger - + pile view - - i18n - - - English - Svenska (Swedish) - - main - + Connect on startup - + Debug to file @@ -11990,6 +15564,16 @@ Please refrain from engaging in this activity or further actions may be taken ag Graveyard Kyrkogård + + + Report Queue + + + + + Moderation + + diff --git a/cockatrice/translations/cockatrice_yue.ts b/cockatrice/translations/cockatrice_yue.ts index f20b9ea1d..d5769e206 100644 --- a/cockatrice/translations/cockatrice_yue.ts +++ b/cockatrice/translations/cockatrice_yue.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &設置指示物 @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter 設置指示物 - + New value for counter '%1': 數值物新價值為 '%1' @@ -36,64 +36,113 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? 你確定嗎? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error 出錯 - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. - + There are no cards in your deck to be exported + + AccountSetupPage + + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + AddAnalyticsPanelDialog @@ -105,12 +154,12 @@ Please check that the directory is writable and try again. AdminNotesDialog - + Update Notes 更新後筆記 - + Admin Notes for %1 %1 的管理筆記 @@ -136,218 +185,304 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error 出現錯誤 - + Could not create themes directory at '%1'. 不能在'%1'創造主題目錄 - + Theme settings 主題設定 - + Current theme: 現在主題 - + Open themes folder 打開主題文件夾 - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings 選單設定 - + Show keyboard shortcuts in right-click menus 在右擊選單中顯示鍵盤快速鍵 - + Show game filter toolbar above list in room tab - + Card rendering 卡面算繪 - + Display card names on cards having a picture 最有圖片的牌上顯示牌名 - + Auto-Rotate cards with sideways layout 將打橫佈局卡片自動向上旋轉 - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Light - + Dark - + + System - + + Show all playmats + + + + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + Active theme palette: - + + Active theme style: + + + + + Qt widget style saved to this theme ("System" lets the application decide) + + + + Edit theme palette - + Home tab settings - + Display card name of background in bottom right - + + Dim the home tab background + + + + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + Styling settings - + Style user list - + Card printings - + Bump sets that the deck contains cards from to the top in the printing selector 將牌庫中包含的牌組推到藝術選擇視窗之頂 - + Scale cards on mouse over 卡牌隨遊標縮放 - + Use rounded card corners - + Card layout - + Minimum overlap percentage of cards on the stack and in vertical hand 在疊區中和直行手牌中,盡量減少交重疊百分點 - + Maximum initial height for card view window: 卡望框最高開頭高度 - - + + rows 橫列 - + Maximum expanded height for card view window: - + Card counters 牌指示物 - + Counter %1 指示物 %1 - + Hand layout 手牌佈局 - + Display hand horizontally (wastes space) 打棟顯示手牌(浪費空間) - + Enable left justification 啟用向左對齊 - + Table grid layout 表格布局 - + Invert vertical coordinate 反轉垂直坐標 - + Minimum player count for multi-column layout: 界面佈局之內能夠容納的最少玩家數量: - + Maximum font size for information displayed on cards: 卡牌上顯示的最大字體大小 @@ -355,12 +490,12 @@ Please check that the directory is writable and try again. ArchidektApiResponseDeckDisplayWidget - + Back to results 返去結果 - + Open Deck in Deck Editor 在套牌編輯&打開牌庫檔案 @@ -386,112 +521,112 @@ Please check that the directory is writable and try again. BanDialog - + ban &user name 禁用 &用家名字 - + ban &IP address 禁用&IP地址 - + ban client I&D 禁用客户ID - + Ban type 禁用類型 - + &permanent ban &永久禁用 - + &temporary ban &暫時禁用 - + &Days: &日 - + &Hours: &小時 - + &Minutes: &分鐘 - + Duration of the ban 禁用時間 - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. 請記載禁用原因 這項只保留為版主, 被禁用戶不能看到. - + Please enter the reason for the ban that will be visible to the banned person. 請記載被禁用戶能看到的被禁原因. - + Redact all messages from this user in all rooms 將這用戶所有室中打出的訊息完全移除 - + &OK &OK - + &Cancel &取消 - + Ban user from server 把用戶從本伺服器禁用 - - - - + + + + Error 出現錯誤 - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. 若要對用戶實施禁用,請你選擇以下三種的拼合:名字,IP,或用戶id. - + You must have a value in the name ban when selecting the name ban checkbox. 若用用戶名字施行禁用,必須要填寫姓名. - + You must have a value in the ip ban when selecting the ip ban checkbox. 若用ip地址施行禁用,必須要寫下IP地址. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. 若用用戶ID施行禁用,必須要寫下用戶ID. @@ -499,22 +634,22 @@ This is only saved for moderators and cannot be seen by the banned person. BetaReleaseChannel - + Beta 測試版 - + No reply received from the release update server. 從發佈更新伺服器收不到回覆. - + Invalid reply received from the release update server. 從發佈更新伺服器收不到有效的回覆. - + No reply received from the file update server. 從檔案更新伺服器收不到有效的回覆. @@ -522,7 +657,20 @@ This is only saved for moderators and cannot be seen by the banned person. CardArtPreviewWidget - + + Banner preview + + + + + + + + Zoom %1× + + + + No card selected @@ -530,22 +678,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardArtRulesModel - + Card - + ProviderId - + Mode - + Reason @@ -553,65 +701,209 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name 名字 - + Sets 牌組 - + Mana cost 法力費用 - + Card type 卡牌類別 - + P/T 力量/防御力 - + Color(s) 顏色 + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + CardDatabaseView - + Add to Deck - + Add to Sideboard - + Select Printing - + + Add to Zone + + + + + Create &new zone... + + + + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards @@ -721,22 +1013,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoFrameWidget - + Image 卡圖 - + Description 描述 - + Both 兩個都顯示 - + View transformation 查看轉化 @@ -744,22 +1036,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards 查看關聯卡牌 - + Add card to deck 添加卡牌到牌庫 - + Mainboard 主牌庫 - + Sideboard 副牌庫 @@ -767,27 +1059,27 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoTextWidget - + Name: 名稱: - + Set: 牌組: - + Collector Number: 收藏號碼: - + Related cards: 有關牌名: - + Unknown card: 不知名的牌 @@ -795,129 +1087,129 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... &展示给... - + &All players &所有玩家 - + View related cards 查看關聯卡牌 - + Token: 令牌: - + All tokens 所有令牌 - + &Select All &全選 - + S&elect Row &選擇橫行 - + S&elect Column &選擇直行 - + &Play &使出 - + &Hide &隱藏 - + Play &Face Down &面朝下使出 - + &Tap / Untap Turn sideways or back again &横置/重置 - + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Reduce life by power - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -938,7 +1230,7 @@ This is only saved for moderators and cannot be seen by the banned person. CardSizeWidget - + Card Size 卡牌大小 @@ -1078,6 +1370,24 @@ This is only saved for moderators and cannot be seen by the banned person.%1的自定區域'%2' + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser @@ -1107,280 +1417,382 @@ This is only saved for moderators and cannot be seen by the banned person. + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + ConnectionController - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - - - - - - - - - - - + + + + + + + + + + - - + + + Error - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + Server timeout - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Information - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -1388,113 +1800,113 @@ To update your client, go to Help -> Check for Updates. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - + Requesting forgotten password to %1 as %2... - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. @@ -1544,7 +1956,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1560,17 +1972,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add card to &maindeck - + Add card to &sideboard @@ -1578,97 +1990,158 @@ To update your client, go to Help -> Check for Updates. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + Format: - + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1676,17 +2149,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1694,114 +2167,119 @@ To update your client, go to Help -> Check for Updates. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + + Share deck... + + + + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1809,7 +2287,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1817,110 +2295,152 @@ To update your client, go to Help -> Check for Updates. DeckEditorSettingsPage - - + + Update Spoilers 更新預覽卡牌 - + Success 成功 - + Download URLs have been reset. 下載URL已重置。 - + Add URL 添加URL - - + + URL: URL: - - + + Edit URL 更改URL - + Add New URL 添置新 URL - + Remove URL 移除 URL. - + Updating... 更新中... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path 選擇路徑 - + URL Download Priority URL下載優先次序 - + Spoilers 預覽 - + Download Spoilers Automatically 自動下載預覽 - + Spoiler Location: 預覽位置: - + Last Change 最後變更 - + Spoilers download automatically on launch 開啟時自動下載預覽 - + Press the button to manually update without relaunching 免重新啟動人手按制更新 - + Do not close settings until manual update is complete 人手更新完成前,不能關掉设置 - + Download card pictures on the fly 即時下載卡牌圖片 - + How to add a custom URL 如何添加自定URL - + Reset Download URLs 重置下載URL @@ -1928,32 +2448,32 @@ To update your client, go to Help -> Check for Updates. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1961,27 +2481,27 @@ To update your client, go to Help -> Check for Updates. DeckListModel - + Count 數目 - + Set 牌組 - + Number 號碼 - + Provider ID 提供者 id - + Card 卡牌 @@ -2002,17 +2522,22 @@ To update your client, go to Help -> Check for Updates. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -2020,7 +2545,7 @@ To update your client, go to Help -> Check for Updates. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -2028,62 +2553,62 @@ To update your client, go to Help -> Check for Updates. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2091,94 +2616,109 @@ To update your client, go to Help -> Check for Updates. DeckPreviewWidget - + Banner Card - + Open in deck editor - + + Share deck... + + + + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Rename deck + + + + + Rename file + + + + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -2186,57 +2726,102 @@ To update your client, go to Help -> Check for Updates. DeckStateManager - + Rename deck to "%1" from "%2" - + Updated comments (was %1 chars, now %2 chars) - + Set banner card to %1 (%2) - + + Set playmat to %1 + + + + Tags changed - + Set format to %1 - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + %1 1 × "%2" (%3) - + Added - + Removed @@ -2258,94 +2843,122 @@ To update your client, go to Help -> Check for Updates. DeckViewContainer - + Load deck... 載入牌庫... - + Load remote deck... 載入伺服器上的牌庫... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start 準備好 - + Force start 強迫開始 - + Sideboard unlocked 備牌解鎖 - + Sideboard locked 解鎖鎖上 - - + + Error 出現錯誤 - + The selected file could not be loaded. 選擇的檔案不能再入 - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? - + Remember and automatically apply choice in the future @@ -2358,143 +2971,143 @@ This will kick all non-ready players from the game. 下載中... - + Known Hosts 已知主辦機 - + Delete the currently selected saved server 刪除現選儲藏伺服器. - + Refresh the server list with known public servers 從已知的伺服器更新伺服器列表 - + New Host 新主辦 - + Name: 名稱: - + &Host: &主辦機: - + &Port: &端口: - + Player &name: 玩家 &名字: - + P&assword: &密碼: - + &Save password &儲存密碼 - + A&uto connect &自動連接 - + Automatically connect to the most recent login when Cockatrice opens 當Cockatrice啓動時自動連接到最近的登錄 - + If you have any trouble connecting or registering then contact the server staff for help! 如在連接或註冊誰中遇到問題,請聯絡伺服器職員尋求援助! - - + + Webpage 網頁 - + Reset Password 重設密碼 - + Forgot password? 密碼忘記了? - + &Connect 連接 - + Server 伺服器 - + Login 登錄 - + Server Contact 伺服器聯絡人 - + Connect to Server 連接伺服器 - + Server URL 伺服器URL - + Communication Port 通信端口 - + Unique Server Name 獨特伺服器名字 - + Connection Warning 連接警告 - + You need to name your new connection profile. 你必須將你的連接簡介命名 - + Connect Warning 連接警告 - + The player name can't be empty. 玩家名稱不能空置 @@ -2502,122 +3115,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings &記住設定 - + &Description: &描述: - + P&layers: 玩家: - + General 一般 - + Game type 遊戲類型 - + &Password: &密碼: - + Only &buddies can join 只容許&好友加入 - + Only &registered users can join 只容許&註冊用戶加入 - + Joining restrictions 加入條件 - + &Spectators can watch 允許觀看者 - + Spectators &need a password to watch 觀看者&需要密碼 - + Spectators can &chat 觀看者可以&聊天 - + Spectators can see &hands 觀看者可見&手牌 - + Create game as spectator 以旁觀者的身份創建遊戲 - + Spectators 觀看者 - + Starting life total: 開始生命總數 - + Open decklists in lobby - + Create game as judge - + Game setup options 遊戲設定選擇 - + &Clear &清除 - + Create game 創建遊戲 - + Game information 遊戲資訊 - + Error 出現錯誤 - + Server error. 伺服器出現錯誤. @@ -2625,97 +3238,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &名稱: - + Token 令牌 - + C&olor: &顏色: - + white 白色 - + blue 藍色 - + black 黑色 - + red 紅色 - + green 綠色 - + multicolor 多色 - + colorless 無色 - + &P/T: &力量/防御力: - + &Annotation: &註釋: - + &Destroy token when it leaves the table &此令牌離場時會被摧毀 - + Create face-down (Only hides name) - + Token data 令牌資料 - + Show &all tokens 顯示&所有令牌 - + Show tokens from this &deck 顯示本牌庫所有令牌 - + Choose token from list 從清單中選擇令牌 - + Create token 派出令牌 @@ -2723,53 +3336,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + ✖ - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2778,7 +3391,7 @@ This will kick all non-ready players from the game. DlgEditAvatar - + No image chosen. 未選擇圖像 @@ -2790,27 +3403,32 @@ To remove your current avatar, confirm without choosing a new image. 若要移除現有頭像, 請不要選擇新的圖片,直接確認. - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... 瀏覽... - + Change avatar 更改頭像 - + Open Image 打開圖片 - + Image Files (*.png *.jpg *.bmp) 圖片文件(*.png *.jpg *.bmp格式) - + Invalid image chosen. 所選圖片不能使用。 @@ -2836,38 +3454,38 @@ To remove your current avatar, confirm without choosing a new image. DlgEditPassword - + Old password: 舊密碼: - + New password: 新密碼: - + Confirm new password: 確認新密碼: - + Change password 更改密碼 - - + + Error 出現錯誤 - + Your password is too short. 你的密碼太短。 - + The new passwords don't match. 新舊密碼不一致 @@ -2875,93 +3493,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: &名稱: - + C&olor: &顏色: - + white 白色 - + blue 藍色 - + black 黑色 - + red 红色 - + green 綠色 - + multicolor 多色 - + colorless 無色 - + &P/T: &力量/防御力: - + &Annotation: &註釋: - + Token data 令牌資料 - - + + Add token 添加令牌 - + Remove token 移除令牌 - + Edit custom tokens 編輯自定令牌 - + Please enter the name of the token: 請輸入令牌名稱: - + Error 出現錯誤 - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. 現時選擇的名字跟現有的卡牌或令牌出現衝突.請從"牌組管理"中啟動'令牌牌組',保持正確顯示. @@ -3039,7 +3657,8 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games that have started + Hide started games + Hide games that have started @@ -3075,8 +3694,9 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - &Creator name: - &創建者名稱: + &Host name: + &Creator name: + @@ -3142,52 +3762,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning 重設密碼驗證警告 - + A problem has occurred. Please try to request a new password again. 程序出現故障,請再要求新的密碼. - + Enter the information of the server and the account you'd like to request a new password for. 請輸入需要新密碼的伺服器和相關戶口資料. - + &Host: &主辦機: - + &Port: &端口: - + Player &name: 玩家 &名字: - + Email: 電郵: - + Reset Password Challenge 重設密碼驗證 - + Reset Password Challenge Error 重設密碼驗證錯誤 - + The email address can't be empty. 電郵不能空置. @@ -3195,37 +3815,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. 請輸入需要新密碼的伺服器資料. - + &Host: &主辦機: - + &Port: &端口: - + Player &name: 玩家 &名字: - + Reset Password Request 重設密碼要求 - + Reset Password Error 重設密碼錯誤 - + The player name can't be empty. 玩家名稱不能空置 @@ -3233,94 +3853,127 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning 重設密碼警告 - + A problem has occurred. Please try to request a new password again. 程序出現故障,請再要求新的密碼. - + Enter the received token and the new password in order to set your new password. 請輸入收到驗證碼和新的密碼,以便定立新的密碼. - + &Host: &主辦機: - + &Port: &端口: - + Player &name: 玩家 &名字: - + Token: 驗證碼 - - + + New Password: 新密碼: - + Reset Password 重設密碼 - + The player name can't be empty. 玩家名稱不能空置 - - - - + + + + Reset Password Error 重設密碼錯誤 - + The token can't be empty. 驗證碼不能為空白 - + The new password can't be empty. 新密碼不能為空白 - + Error 出現錯誤 - + Your password is too short. 你的新密碼太短。 - + The passwords do not match. 新舊密碼不一致. + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck 載入牌庫 @@ -3404,78 +4057,258 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLocalGameOptions - + Players: - + General - + Starting life total: - + Game setup options - + Remember settings - + Local game options + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): 卡牌名稱(或搜尋表達式) - + Number of hits: 因為cockatrice原生沒有ssl支援, 所以你不能自動下載更新. 請瀏覽到我們的下載網頁,進行人手更新. - + Auto play hits 開關副牌上鎖 - + Put top cards on stack until... 把頂牌放在重疊區, 直到... - + No cards matching the search expression exists in the card database. Proceed anyways? 在卡牌資料庫中找不到符合搜尋表達式的卡牌.你想繼續嗎? - + Cockatrice Cockatrice - + Invalid filter 篩選無效 + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. 輸入你, 和你想登記的伺服器的資料. @@ -3483,88 +4316,309 @@ Your email will be used to verify your account. 我們會使用你的電郵來核實你的戶口. - + &Host: &主辦機: - + &Port: &端口: - + Player &name: 玩家 &名字: - + P&assword: &密碼: - + Password (again): (再次)輸入密碼: - + Email: 電郵: - + Email (again): (再次)輸入電郵: - + Country: 國家: - + Undefined 未定義 - + Real name: 實名: - + Register to server 註冊加入伺服器 - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning 注册警告 - + Your password is too short. 你的密碼太短。 - + Your passwords do not match, please try again. 兩次輸入的密碼不一致,請重新輸入, - + Your email addresses do not match, please try again. 兩次輸入的電子郵箱不一致,請重新輸入。 - + The player name can't be empty. 玩家名稱不能空置 + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice @@ -3630,46 +4684,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database 讀取卡牌資料庫時出現不知名錯誤 - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - 你的卡牌資料庫無效。 - -資料庫無效可能會導致Cockatrice無法正確運行。 - -你應該重新啓動Oracle,更新卡牌資料庫。 - -你想要重新設置卡牌資料庫路徑嗎? - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - 你的卡牌資料庫過舊。 - -這可能會導致卡牌圖片或資料出現問題。 - -你應該重新啓動Oracle,更新卡牌資料庫。 - -你想要重新設置卡牌資料庫路徑嗎? - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3682,7 +4702,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + File Error loading your card database. Would you like to change your database location setting? @@ -3690,7 +4710,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3699,7 +4719,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3715,104 +4735,235 @@ https://github.com/Cockatrice/Cockatrice/issues 你想不想更改你的資料庫位置設定? - - - + + + Error 出現錯誤 - + The path to your deck directory is invalid. Would you like to go back and set the correct path? 你的牌庫目錄路徑現在無效. 你想要重新設置套牌路徑嗎? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? 你的卡牌圖片目錄路徑現在無效. 你想要重新設置卡牌圖片資料庫路徑嗎? - + Settings 設定 - + + Search settings... + + + + + OK + + + + General 一般 - + Appearance 外觀 - + User Interface 用戶界面 - + Storage - + Card Sources 卡牌來源 - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat 聊天 - + Sound 聲音 - + Shortcuts 快捷键 + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3820,17 +4971,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next 下一個 - + Previous 上一個 - + Tip of the Day 每日提示 @@ -3838,165 +4989,175 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel 現在發行渠道 - + Reinstall 重新安裝 - + Cancel Download 取消下載 - + Open Download Page 打開下載頁面 - + Check for Client Updates 檢查客戶端更新 - - - - + + + + Error 出現錯誤 - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. 因為Cockatrice不設立ssl支援,所以你不能自動下載更新! 請你瀏覽到我們的下載網頁進行人手更新. - + Downloading update: %1 - + Checking for updates... 正在檢查更新... - + Finished checking for updates 更新檢查已完成 - + No Update Available 沒有更新可用 - + Cockatrice is up to date! Cockatrice 已經達到最新版本! - + You are already running the latest version available in the chosen release channel. 你運行的已經是最新的發行版本了。 - + Current version 現行版本 - + Selected release channel 現選發行渠道 - - + + Update Available 現有更新版本 - - + + A new version of Cockatrice is available! Cockatrice有新版本可用! - - + + New version 新版本 - - + + Released 已發行 - - + + Changelog 變更記載 - + Do you want to update now? 你打算現在更新嗎? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error 更新出現錯誤 - + An error occurred while checking for updates: 檢測更新時出現錯誤: - + An error occurred while downloading an update: 下載更新時出現錯誤 - + Installing... 安裝中 - + Cockatrice is unable to open the installer. Cockatrice 不能啟動安裝程式 - + Try to update manually by closing Cockatrice and running the installer. 請你嘗試人手更新, 關掉cockatrice, 然後重開安裝程式. - + Download location 下載位置 @@ -4004,17 +5165,17 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing 關閉時清除紀錄 - + Copy to clipboard - + Debug Log 偵錯紀錄 @@ -4252,7 +5413,7 @@ You may have to manually download the new version. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -4260,7 +5421,7 @@ You may have to manually download the new version. FilterBuilder - + Type your filter here 輸入你的篩選類型 @@ -4268,45 +5429,88 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -4314,140 +5518,156 @@ You may have to manually download the new version. GameSelector - - - - - - - - - + + + + + + + + + Error 出現錯誤 - + Please join the appropriate room first. 請先參加合適房間 - + Wrong password. 密碼錯誤. - + Spectators are not allowed in this game. 這個遊戲不允許觀眾加入. - + The game is already full. 這個遊戲已經滿了. - + The game does not exist any more. 這個遊戲已經不存在了。 - + This game is only open to registered users. 這個遊戲只開放給註冊用戶. - + This game is only open to its creator's buddies. 這個遊戲只開放給房間主辦的好友。 - + You are being ignored by the creator of this game. 你被這個房間的主辦屏蔽了. - + Join Game - + Spectate Game - + Game Information - + + Cop&y game link + + + + Join Game as Judge - + Spectate Game as Judge - + + Join game 加入遊戲 - - Password: - 密碼: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. 請先參加相關的房間. - + &Filter games &篩選遊戲 - + C&lear filter &清除篩選項目 - + C&reate &創建 - + &Join &加入 - + Join as judge - + J&oin as spectator &作為觀看者加入 - + Join as judge spectator - + Games shown: %1 / %2 顯示遊戲:%1/%2 - + Games 遊戲 @@ -4488,105 +5708,105 @@ You may have to manually download the new version. GamesModel - + >1 day >1日 - + %1%2 hr short age in hours %1%2 小時 - + new 新建 - + %1%2 min short age in minutes %1%2 分鐘 - + password 密碼 - + buddies only 僅限好友 - + reg. users only 僅限註冊用户 - + open decklists - - + + can chat 允許聊天 - + see hands 查看手牌 - + can see hands 允許查看手牌 - + not allowed 不允許 - + Room 房間 - + Age 時長 - + Description 描述: - - Creator - 創建者 + + Host + - + Type 類型 - + Restrictions 限制 - + Players 玩家 - + Spectators 觀看者 @@ -4594,158 +5814,275 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths 重置所有路徑 - + All paths have been reset 重置所有路徑 - - - - - - - + + + + English + 粵語 (Cantonese) + + + + + + + + + Choose path 選擇路徑 - + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> + + + + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + Language settings - + Language: 語言: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + Version settings - + Card database - + Startup settings - + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) 路徑(在便攜模式下不能編輯) - + Paths 路徑 - + How to help with translations - + Decks directory: 牌庫路徑: - + Filters directory: - + Replays directory: 遊戲錄像目錄: - + Pictures directory: 圖片目錄: - + Card database: 卡牌資料庫: - + Custom database directory: 自主資料庫目錄 - + Token database: 令牌資料庫: - + Update channel 更新渠道 - + Check for client updates on startup 啟動時檢查客戶更新 - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client 如果客戶端缺少伺服器支持的更功能時,請提示我 - + Automatically run Oracle when running a new version of Cockatrice 啟動新版本Cockatrice時,自動運行Oracle. - + Show tips on startup 顯示啓動時提示 - + Last update check on %1 (%2 days ago) @@ -4753,47 +6090,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4887,59 +6224,353 @@ You may have to manually download the new version. + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu @@ -5157,273 +6788,294 @@ You may have to manually download the new version. MainWindow - - + + Player %1 玩家 %1 - + Load replay 載入遊戲錄像 - + About Cockatrice 關於Cockatrice - + Version 版本 - + Cockatrice Webpage Cockatrice 網頁 - + Project Manager: 項目經理: - + Past Project Managers: 前項目經理: - + Developers: 開發者: - + Our Developers 我們的開發者: - + Help Develop! 協助開發! - + Translators: 翻譯者: - + Our Translators 翻譯者名單 - + Help Translate! 協助翻譯! - + Support: 支援: - + Report an Issue 報告問題 - + Troubleshooting 排除故障 - + F.A.Q. 問答 - - + + Error 出現錯誤 - + &Connect... &連線... - + &Disconnect &斷線 - + Start &local game... 開始&離線遊戲 - + &Watch replay... &觀看遊戲錄像 - + &Full screen &全屏 - + &Register to server... &註冊加入伺服器 - + &Restore password... &弄回密碼 - + &Settings... 設置⋯ - + &Exit &退出 - + A&ctions &動作 - + &Cockatrice &Cockatrice - + C&ard Database &卡牌資料庫 - + &Manage sets... &管理牌組 - + Edit custom &tokens... 編輯自定 &令牌... - + Open custom image folder 打開自定圖片文件夾 - + Open custom sets folder 打開自定牌组文件夾 - + Add custom sets/cards 添加自定卡组/卡牌 - + Reload card database 刷新卡牌資料庫 - + Tabs - + &Help &求助 - + &About Cockatrice &關於Cockatrice - + &Tip of the Day &每日提示 - + Check for Client Updates 檢查客戶端更新 - + Check for Card Updates... 檢查卡牌更新 - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log 查看&偵錯紀錄 - + Open Settings Folder 打開設置文件夾 - + + Re-run Onboarding Wizard... + + + + Show/Hide 顯示/隱藏 - + + Sign-in cancelled + + + + New Version 新版本 - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. 恭喜你將Cockatrice升級為%1. 現在會幫你啟動oracle, 更新你的卡牌資料庫. - + Cockatrice installed Cockatrice 已被安裝 - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. 恭喜你成功安裝Cockatrice%1. 現在會幫你啟動oracle, 安裝你的卡牌資料庫. - + + Are you sure? + + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database 卡牌資料庫 - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5432,29 +7084,29 @@ If unsure or first time user, choose "Yes" 如果不確定, 或者是第一次使用,請選擇“是/Yes” - - + + Yes 是/Yes - - + + No 否/No - + Open settings 打開設置 - + New sets found 發現新卡组 - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5463,22 +7115,22 @@ Do you want to enable it/them? 要啟用它們嗎? - + Yes, always enable - + View sets 查看卡组 - + Welcome 歡迎 - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5487,88 +7139,88 @@ Read more about changing the set order or disabling specific sets and consequent 請閱讀,了解更多在“卡组设置”對話框裡.更改卡组顺序或者禁用某些卡组的功能. - + Information 資訊 - + A card database update is already running. 資料庫更新現已進行. - + Unable to run the card database updater: 不能運行卡牌資料附更新程式: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - - - - - + + + + + Load sets/cards 载入卡组/卡牌 - + Selected file cannot be found. 找不到選擇的文件。 - + You can only import XML databases at this time. 你暫時只能導入xml資料庫. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. 已經成功添加新的卡組/卡牌. Cockatrice會重新載入卡牌資料庫. - + Sets/cards failed to import. 卡組/卡牌導入失敗. @@ -5773,615 +7425,615 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play 從戰場上 - + from their graveyard 從它們的墳場 - + from exile 從放逐區 - + from their hand 從它們的手牌 - + the top card of %1's library %1牌庫的頂牌 - + the top card of their library 它們的頂牌 - + from the top of %1's library 從%1牌庫的頂 - + from the top of their library 從它們1牌庫頂 - + the bottom card of %1's library %1牌庫的底牌 - + the bottom card of their library 它們牌庫的底牌 - + from the bottom of %1's library 從%1的牌庫底 - + from the bottom of their library 從它們的牌庫底 - + from %1's library 從%1的牌庫 - + from their library 從它們的牌庫 - + from sideboard 從備牌中 - + from the stack 從疊區中 - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1現在保持%2頂牌顯現. - + %1 is not revealing the top card %2 any longer. %1不再展示牌庫頂牌%2。 - + %1 can now look at top card %2 at any time. %1可以隨時查看牌庫頂牌 %2. - + %1 no longer can look at top card %2 at any time. %1再不可以隨時查看牌庫頂牌 %2. - + %1 attaches %2 to %3's %4. %1 結附 %2 指向 %3的 %4. - + %1 has conceded the game. %1 在此遊戲宣佈降服. - + %1 has unconceded the game. %1 收回在此遊戲降服. - + %1 has restored connection to the game. %1已恢復連接。 - + %1 has lost connection to the game. %1 已失去連接。 - + %1 points from their %2 to themselves. %1 從它們的%2 指向自己。 - + %1 points from their %2 to %3. %1 從它們的%2指向%3。 - + %1 points from %2's %3 to themselves. %1 從%2的%3 指向自己。 - + %1 points from %2's %3 to %4. %1 從%2的%3 指向%4。 - + %1 points from their %2 to their %3. %1 從它們的%2指向它們的%3。 - + %1 points from their %2 to %3's %4. %1 從它們的%2指向%3的%4。 - + %1 points from %2's %3 to their own %4. %1 從%2的%3指向自己的%4。 - + %1 points from %2's %3 to %4's %5. %1 從%2的%3指向%4的%5。 - + %1 creates a face down token. - + %1 creates token: %2%3. %1 派出令牌: %2%3 - + %1 has loaded a deck (%2). %1 已载入牌庫(%2) - + %1 has loaded a deck with %2 sideboard cards (%3). %1已載入含有 %2張備牌的牌庫(%3). - + %1 destroys %2. %1销毁了%2。 - + a card 一張牌 - + %1 gives %2 control over %3. %1將%3的控制轉移給%2. - + %1 puts %2 into play%3 face down. %1將%2%3牌面朝下地放進戰場。 - + %1 puts %2 into play%3. %1將%2放進戰場%3。 - + %1 puts %2%3 into their graveyard face down. - + %1 puts %2%3 into their graveyard. %1將%2%3置入它們的的墳場。 - + %1 exiles %2%3 face down. - + %1 exiles %2%3. %1 放逐%2%3. - + %1 moves %2%3 to their hand. %1將%2%3移到它們的的手牌中。 - + %1 puts %2%3 into their library. %1將%2%3放入它們的牌庫。 - + %1 puts %2%3 onto the bottom of their library. %1將%2%3放在它們的牌庫底部。 - + %1 puts %2%3 on top of their library. %1將%2%3放在它們的牌庫頂部。 - + %1 puts %2%3 into their library %4 cards from the top. %1將%2%3放在它們的牌庫頂部%4張牌以下。 - + %1 moves %2%3 to sideboard. %1 將%2%3移到副牌庫. - + %1 plays %2%3 face down. - + %1 plays %2%3. %1使出 %2%3。 - + %1 moves %2%3 to custom zone '%4' face down. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1嘗試從空牌庫抽牌 - + %1 draws %2 card(s). %1抽%2張牌。 - + %1 is looking at %2. %1 正在查看%2。 - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 正在查看%2的 %4 %3張卡牌. - + bottom 底部 - + top 頂部 - + %1 turns %2 face-down. %1把%2翻為面朝下。 - + %1 turns %2 face-up. %1把%2翻為面朝上。 - + The game has been closed. 遊戲已經關閉。 - + The game has started. 遊戲已開始。 - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 已經加入遊戲。 - + %1 is now watching the game. %1 正在旁觀遊戲。 - + You have been kicked out of the game. 你已被踢出遊戲。 - + %1 has left the game (%2). %1 已經離開遊戲(%2)。 - + %1 is not watching the game any more (%2). %1 已經不再旁觀遊戲(%2)。 - + %1 is not ready to start the game any more. %1 還未準備好開始遊戲。 - + %1 shuffles their deck and draws a new hand of %2 card(s). %1將其牌庫洗牌,然後抽%2張牌作為新的起手。 - + %1 shuffles their deck and draws a new hand. %1將其牌庫洗牌,然後抽起新的手牌。 - + You are watching a replay of game #%1. 你正在觀看遊戲#%1的錄像。 - + %1 is ready to start the game. %1已準備好開始遊戲。 - + cards an unknown amount of cards 卡牌(眾)  - + %1 card(s) a card for singular, %1 cards for plural %1 卡牌(眾)  - + %1 lends %2 to %3. %1把&2借給%3. - + %1 reveals %2 to %3. %1把%2展示給%3. - + %1 reveals %2. %1 展示 %2。 - + %1 randomly reveals %2%3 to %4. %1隨機展示%2%3給%4。 - + %1 randomly reveals %2%3. %1隨機展示%2%3。 - + %1 peeks at face down card #%2. %1查看面朝下的卡牌#%2。 - + %1 peeks at face down card #%2: %3. %1查看面朝下的卡牌#%2:%3。 - + %1 reveals %2%3 to %4. %1展示%2%3給%4。 - + %1 reveals %2%3. %1展示%2%3。 - + %1 reversed turn order, now it's %2. %1 逆轉回合次序,現在輪到%2。 - + reversed 逆轉 - + normal 正常 - + Heads 正面 - + Tails 翻面 - + %1 flipped a coin. It landed as %2. %1擲了硬幣。結果為%2。 - + %1 rolls a %2 with a %3-sided die. %1擲%3面骰子,結果為%2。 - + %1 flips %2 coins. There are %3 heads and %4 tails. %1擲了%2個硬幣。結果為%3個正面和%4個反面。 - + %1 rolls a %2-sided dice %3 times: %4. %1把%2面骰子擲了%3次:%4 - + %1's turn. 輪到 %1. - + %1 sets annotation of %2 to %3. %1給%2添加了註釋成%3。 - + %1 places %2 %3%4 counter(s) on %5 (now %6). - + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + %1 failed to undo their last draw. - + %1 sets counter %2 to %3 (%4%5). %1 將%2指示物設置為 %3 (%4%5)。 - + %1 sets %2 to not untap normally. %1 將 %2 設置為不被重置。 - + %1 sets %2 to untap normally. %1 將 %2 設置為照常重置。 - + %1 removes the PT of %2. %1 移除%2的力量/防禦. - + %1 changes the PT of %2 from nothing to %4. %1把%2的力量/防禦從無數值轉為%4. - + %1 changes the PT of %2 from %3 to %4. %1把%2的力量/防禦從%3轉為%4. - + %1 has locked their sideboard. %1已經鎖定它們的的副牌庫。 - + %1 has unlocked their sideboard. %1已經把它們的的副牌庫解鎖。 - + %1 taps their permanents. %1横置它們的永久卡牌 - + %1 untaps their permanents. %1重置它們的永久卡牌. - + %1 taps %2. %1 横置 %2. - + %1 untaps %2. %1 重置 %2。 - + %1 shuffles %2. % 1 對 %2 洗牌 - + %1 shuffles the bottom %3 cards of %2. %1 洗 %2 底的 %3 張卡牌. - + %1 shuffles the top %3 cards of %2. %1 洗 %2 頂的 %3 張卡牌. - + %1 shuffles cards %3 - %4 of %2. %1到%2.洗%3 - %4 - + %1 unattaches %2. %1 取消了%2的結附。 - + %1 undoes their last draw. %1 放回它們最後一張抽到的卡牌. - + %1 undoes their last draw (%2). %1 放回它們最後一張抽到的卡牌(%2). @@ -6389,115 +8041,125 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 詞1 詞2 詞3 - + Add New Message 添加新信息 - + Edit Message 更改信息 - + Remove Message 移除信息 - + Add message 添加信息 - - + + Message: 信息: - + Edit message 更改信息 - + Chat settings 聊天設定 - + Custom alert words 自定通知觸發詞語 - + Enable chat mentions 允許聊天中提到某人 - + Enable mention completer 允許自動完成提名 - + In-game message macros 遊戲內短信速鍵 - + How to use in-game message macros 如何使用遊戲內短信速鍵 - + Ignore chat room messages sent by unregistered users 不要顯示未註冊用户的聊天消息。 - + Ignore private messages sent by unregistered users 不要顯示未註冊用户的私人消息. - + Ignore private messages sent by non-buddy users - - + + Invert text color 反轉文本顏色 - + Enable desktop notifications for private messages 容許桌面提醒提及私人消息 - + Enable desktop notification for mentions 容許桌面提醒提及聊天提名 - + Enable room message history on join 加入聊天室時開啓消息歷史 - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) (顏色為16進制) - + Separate words with a space, alphanumeric characters only 將單詞以空格區分,僅支持字母. @@ -6505,42 +8167,42 @@ Cockatrice will now reload the card database. MoveMenu - + Move to - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + T&able - + &Hand - + &Graveyard - + &Exile @@ -6619,148 +8281,134 @@ Cockatrice will now reload the card database. PaletteEditorDialog - - + + Reset - - - Apply - - - - - - Save && Apply - - - - + ▼ Edit Palette - - + + ▶ Edit Palette - + Palette Editor — %1 - + <b>Palette Editor</b> &nbsp;·&nbsp; %1 - - This theme ships no default palette files - - - - - Replace current colours with the theme author's defaults - - - - - Switch between the light and dark palette files + + + Save - Editing: + Delete this scheme's custom palette and revert to the theme default (or the application palette) - Show or hide the per-role colour grid for manual tweaks + Switch between the light and dark palette files + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + ↺ Revert to theme default - + Discard unsaved edits and restore the last saved palette - - Preview this palette without saving to disk + + Cannot save: this theme has no writable directory - + Write palette-%1.toml and reload the theme - - Cannot save: this theme has no directory on disk - - - - + + Save failed - + + Could not write %1 to: %2 - - - No default found - - - - - No default palette file found for the "%1" scheme. - - PaletteGridWidget - + Active - + Disabled - + Inactive - + Normal interactive state - + Widget is disabled / not interactive - + Window is in background / unfocused + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + Phase @@ -6883,30 +8531,21 @@ Cockatrice will now reload the card database. 回合完結部 - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - PlayerActions - - - - + + + + grave - - - - + + + + exile @@ -7025,16 +8664,272 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters + + PlaymatCollectionDialog + + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + PrintingDisabledInfoWidget @@ -7055,12 +8950,12 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelector - + Display Navigation Buttons - + Printing Selector @@ -7068,25 +8963,65 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardOverlayWidget - + Preference 喜好 - + Pin Printing 版本上別針 - + Unpin Printing 版本脫別針 - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards 顯示關聯卡牌 + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -7117,28 +9052,28 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardSortingWidget - + Alphabetical 英文字母次序 - + Preference 喜好 - + Release Date 發行日期 - - + + Descending 由後到先 - + Ascending 由先倒後 @@ -7154,95 +9089,103 @@ This setting means you'll only see the default printing for each card, instead o PtMenu - + Power / toughness - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services 服務 - + Hide %1 隐藏 %1 - + Hide Others 隱藏其他 - + Show All 全部展示 - + Preferences... 喜好... - + Quit %1 退出%1 - + About %1 關於 %1 @@ -7250,17 +9193,17 @@ This setting means you'll only see the default printing for each card, instead o QObject - + Cockatrice card database (*.xml) Cockatrice卡牌資料庫 (*.xml) - + All files (*.*) 所有檔案格式 (*.*) - + Cockatrice replays (*.cor) Cockatrice錄像文件 (*.cor) @@ -7280,17 +9223,22 @@ This setting means you'll only see the default printing for each card, instead o 眾令牌 - + + Maybeboard + + + + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7301,7 +9249,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7312,100 +9260,110 @@ Are you sure you would like to disable this feature? - + Confirm Change + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK OK/確認 - + Save 保存 - + Save All 全部保存 - + Open 打開 - + &Yes &是/Yes - + Yes to &All &全選是/Yes - + &No &否/No - + N&o to All &全選否/No - + Abort 退出 - + Retry 重試 - + Ignore 不理 - + Close 關閉 - + Cancel 取消 - + Discard 棄牌 - + Help 協助 - + Apply 應用 - + Reset 重設 - + Restore Defaults 恢復常用認值 @@ -7413,121 +9371,189 @@ Are you sure you would like to disable this feature? QuickSetupPanel - + %1% - + <b>Quick Setup</b> - + Generate all palette roles automatically from a single accent colour - + Accent: - + Primary hue. Used directly for highlights and links. At high intensity it also tints buttons and backgrounds. - + Intensity: - + Subtle - + Full colour - + 0–30 Subtle tint — only highlights and links change hue 30–70 Accented — buttons, tooltips, and borders join in 70–100 Full colour — backgrounds, everything - + 70% - - - Generate ↓ - - - - - Derive all palette roles from the accent colour above. -Fine-tune individual colours in the grid afterwards. - - RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name 名字 - + ID ID - + Upload time 上載時間 + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID ID - + Name 名字 - + Players 玩家(眾) - + Keep 選用 - + Time started 開始時間 - + Duration (sec) 維持(秒) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu @@ -7569,37 +9595,37 @@ Fine-tune individual colours in the grid afterwards. RoomSelector - + Rooms 房間(眾)  - + Joi&n &加入 - + Room 房間 - + Description 描述 - + Permissions 許可 - + Players 玩家(眾) - + Games 遊戲(眾) @@ -7607,7 +9633,7 @@ Fine-tune individual colours in the grid afterwards. SayMenu - + S&ay @@ -7615,32 +9641,32 @@ Fine-tune individual colours in the grid afterwards. SequenceEdit - + Choose an action from the table 從列表中選擇行動 - + Hit the key/combination of keys you want to set for this action 按下以此行動設置的單鍵或組鍵 - + Invalid key 設置的單鍵或組鍵無效 - + Shortcut already in use by: 捷鍵已被:&使用 - + Clear 清除 - + Restore default 恢復常用認值 @@ -7673,56 +9699,92 @@ Fine-tune individual colours in the grid afterwards. 發行日期 + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts 恢復所有常用快鍵 - + Do you really want to restore all default shortcuts? 確定要恢復所有常用快鍵嗎? - + Clear all default shortcuts 清除所有常用快鍵 - + Do you really want to clear all shortcuts? 確認清除所有快鍵嗎? - + Section: 部分: - + Action: 行動: - + Shortcut: 快鍵: - + + Shortcut editor + + + + How to set custom shortcuts 如何設置自定快鍵 - + Clear all shortcuts 清除所有快键 - + Search by shortcut name 按快鍵名稱搜尋 @@ -7778,12 +9840,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -7791,27 +9853,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds 啓用&聲效 - + Current sounds theme: 現前聲效主題: - + Test system sound engine 測試系統聲效引擎 - + Sound settings 聲效設置 - + Master volume 主音量 @@ -7819,48 +9881,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended 預覽季節已結束 - + Deleting spoiler.xml. Please run Oracle 現在正刪除spoiler.xml。請啟動Oracle - - + + Spoilers download failed 預覽下載出錯 - + No internet connection 沒有網絡連接 - + Error 出現錯誤 - + Spoilers already up to date 預覽已是最新 - + No new spoilers added 沒有添加新預覽 - + Spoilers have been updated! 預覽已被更新 - + Last change: 最後變更: @@ -7868,157 +9930,170 @@ Please check your shortcut settings! StableReleaseChannel - + Default 常用 - + No reply received from the release update server. 從發佈更新伺服器收不到有效的回覆. - + Invalid reply received from the release update server. 從發佈更新伺服器收不到有效的回覆. - + No reply received from the tag update server. 未收到標籤更新伺服器的回應. - + Invalid reply received from the tag update server. 從標籤更新伺服器收不到有效的回覆. + + StatsTally + + + Total Power + + + + + Total Toughness + + + StorageSettingsPage - - - + + + Success - + Cached card pictures have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + In-memory (currently loaded) card pictures have been reset. - + Card Picture Loader Caching Method: - + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. - + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. - + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. - + Delete Cached Images - + Delete Saved Images - + Clear In-Memory Images - + Card Picture Loader Cache Method - + Network Cache - + Filesystem - + In-Memory Picture Cache - + Network Cache Size: - + On-disk cache for downloaded pictures - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + In-memory cache for pictures not currently on screen - + Naming scheme: - + Day(s) @@ -8026,17 +10101,33 @@ Please check your shortcut settings! TabAccount - + + + My Reports + + + + Add to Buddy List - + Add to Ignore List - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account @@ -8157,118 +10248,118 @@ Please check your shortcut settings! TabArchidekt - - - + + + Desc. - - + + AND - - + + Require ALL selected colors - - + + Deck name... - - + + Owner... - - + + Packages - - + + Advanced Filters - + Bracket: - - + + Any - - + + Contains card... - - + + Commander... - - + + Tag... - - + + Deck Size - + Cards: - - + + Asc. - + Sort by: - + Filter by: - + Display Settings - - + + Search - - + + Formats @@ -8281,42 +10372,42 @@ Please check your shortcut settings! TabCardArtRules - + Card: - + ProviderId: - + Mode: - + Reason: - + Type a card name... - + Add rule - + Remove rule - + Refresh @@ -8329,52 +10420,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info 卡牌資訊 - + Deck 牌庫 - + Filters 篩選項目 - + &View &視圖 - + Card Database - + Printing 印刷版本 - + Visible 可見 - + Floating 浮動 - + Reset layout 重置布局 - + Deck: %1 牌庫:%1 @@ -8382,49 +10473,49 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - + &View - + Printing - + Visible - + Floating - + Reset layout @@ -8432,22 +10523,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -8455,138 +10546,236 @@ Please check your shortcut settings! TabDeckStorage - + Local file system 本地檔案系統 - + Server deck storage 伺服器牌庫儲藏 - - + + Open in deck editor 在套牌編輯打開 - + Rename deck or folder - + Upload deck 上載牌庫 - + Download deck 下载套牌 - - - - + + + + New folder 新文件夾 - - + + Delete 删除 - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder 打開牌庫文件夾 - + Rename local folder - + Rename local file - + New name: - - - - + + + + + + + Error 出現錯誤 - + Rename failed - - + + + Invalid deck file 無效牌庫檔案 - + Enter deck name 輸入牌庫名稱 - + This decklist does not have a name. Please enter a name: 本牌庫列表未有名稱.請輸入名稱: - + Unnamed deck 未命名牌庫 - + Failed to upload deck to server 牌庫上載於伺服器時失敗 - + Delete local file 删除本地檔案 - + Are you sure you want to delete the selected files? 你確定要刪除被選檔案(眾)嗎 ? - + Delete remote decks 刪除伺服器上的牌庫 - + Are you sure you want to delete the selected decks? 你確定要剷除所有被選的牌庫嗎? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: 新建文件夾名稱: - + Deck Storage @@ -8594,20 +10783,271 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -8620,27 +11060,27 @@ Please enter a name: TabEdhRecMain - + &Cards - + Top Commanders - + Tags - + Search for a card ... - + Search @@ -8653,191 +11093,207 @@ Please enter a name: TabGame - - - + + + Replay 遊戲錄像重溫 - - + + Game 遊戲 - - + + Player List 玩家列表 - - + + + Invite + + + + + Card Info 卡牌資訊 - - + + Messages 信息 - - + + Replay Timeline 錄像時間線 - + &Phases 階段 - + &Game 遊戲 - + Next &phase 下個&階段 - + Next phase with &action 下個階段&行動 - + Next &turn 下個&回合 - + Reverse turn order 逆轉回合次序 - + &Remove all local arrows &移除所有自設箭頭 - + Rotate View Cl&ockwise &順時針旋轉視角 - + Rotate View Co&unterclockwise &逆時針旋轉視角 - + Game &information 遊戲&資訊 - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede 撤銷&投降 - - - + + + &Concede &投降 - + &Leave game &離開遊戲 - + C&lose replay &關閉遊戲錄像 - + &Focus Chat &聚焦聊天 - + &Say: &説: - + Selected cards - + &View &視圖 - + Visible 可見 - + Floating 浮動 - + Reset layout 重置布局 - + Concede 投降 - + Are you sure you want to concede this game? 你確定要宣佈投降嗎? - + Unconcede 撤銷投降 - + You have already conceded. Do you want to return to this game? 你已經宣佈投降,你想回到這場遊戲嗎? - + Leave game 離開遊戲 - + Are you sure you want to leave this game? 你確定要離開這個遊戲嗎? - + A player has joined game #%1 有玩家加入遊戲 #%1 - + %1 has joined the game %1 已經加入遊戲。 - + You have been kicked out of the game. 你已被踢出遊戲。 @@ -8853,158 +11309,158 @@ Please enter a name: TabLog - + Logs 記錄 - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName 時間;發送者姓名;發送者IP;信息;目標ID;目標姓名 - + Room Logs 房間記錄 - + Game Logs 遊戲記錄 - + Chat Logs 聊天記錄 - - + + Error 出現錯誤 - + You must select at least one filter. 您必須選擇至少一個過濾項. - + You have to select a valid number of days to locate. 您必須選擇一個可用的日數來作為定位. - + Username: 用戶名稱: - + IP Address: IP地址 - + Game Name: 遊戲名稱: - + GameID: 遊戲ID: - + Message: 信息: - + Main Room 主房 - + Game Room 遊戲房間 - + Private Chat 私人聊天 - + Past X Days: 過去 X 天: - + Today 今天 - + Last Hour 1小時前 - + Maximum Results: 最多結果: - + At least one filter is required. The more information you put in, the more specific your results will be. 至少需要1個篩選項。 您輸入的信息越多,找到結果越準確。 - + Get User Logs 獲取用戶記錄 - + Clear Filters 清除篩選項目 - + Filters 篩選項目 - + Log Locations 記錄位置 - + Date Range 日期範圍 - + Maximum Results 最多結果 - - + + Message History 信息歷史 - + Failed to collect message history information. 不能獲取信息歷史消息 - + There are no messages for the selected filters. 沒有符合篩選條件的信息。 @@ -9012,218 +11468,558 @@ The more information you put in, the more specific your results will be. TabMessage - + Private &chat 私人&聊天 - + &Leave &離開 - + %1 - Private chat %1 - 私人聊天 - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. 你已被這用戶屏蔽, 它們不能再主聊天室看見你的信息,你亦不能參加它們的遊戲. - + Private message from 私人信息信到, 来自 - + %1 has left the server. %1 已離開伺服器. - + %1 has joined the server. %1 已加入伺服器. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system 本地檔案系統 - + Server replay storage 伺服器錄像倉庫 - - + + Watch replay 觀看遊戲錄像 - + Rename 改名 - - + + New folder 新文件夾 - - + + Delete 刪除 - + Open replays folder 打開錄像文件夾 - + Download replay 下載遊戲錄像 - + Toggle expiration lock 過期鎖定 - + Get replay share code - - + + Look up replay by share code - + Rename local folder 本地檔案夾改名 - + Rename local file 本地檔案改名 - + New name: 新名稱 - + Error 出錯 - + Rename failed 改名不成功 - + Name of new folder: 新建文件夾名稱: - + Delete local file 刪除本地檔案 - + Are you sure you want to delete the selected files? 你確定要剷除所有被選的檔案嗎? - + Are you sure you want to delete the selected replays? 你確定要刪除被選錄像檔案嗎 ? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay 刪除伺服器錄像 @@ -9233,65 +12029,478 @@ The more information you put in, the more specific your results will be. + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - - Friends - - - - - Online - - - - - Ignored - - - - + &Say: &説: - + Chat 聊天 - + &Room &房間 - + &Leave room &離開房間 - + &Clear chat &清除聊天記錄 - + Chat Settings... 聊天設置... - + mentioned you. 提到了你。 - + Click to view 點擊查看 - + You are flooding the chat. Please wait a couple of seconds. 你正在刷屏。請稍等幾秒鐘。 @@ -9299,35 +12508,35 @@ The more information you put in, the more specific your results will be. TabServer - + Server 伺服器 - - - - + + + + Error 出現錯誤 - + Failed to join the server room: it doesn't exist on the server. 加入伺服器房間失敗:在本伺服器上這房間並不存在。 - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. 伺服器認為你在房間內, 但Cockatrice無法顯示。請重新啓動Cockatrice。 - + You do not have the required permission to join this server room. 你未被允許進入該房間。 - + Failed to join the server room due to an unknown error: %1. 因為以下不知名的錯誤,所以不能參入伺服器房間: %1. @@ -9335,97 +12544,173 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - + Archidekt - + Home - + &Visual Deck Storage - + Visual Database Display - + Server - + Account - + Deck Storage - + Game Replays - + Administration - + Logs - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? 你確定嗎? - + There are still open games. Are you sure you want to quit? 遊戲還在繼續, 你確定要退出嗎? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view 點擊查看 - + Your buddy %1 has signed on! 你的好友%1已登錄! - + Unknown Event 不知名事件 - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -9436,38 +12721,38 @@ To update your client, go to Help -> Check for Updates. 要升級你的客户端,請點擊“幫助”->“檢查更新” - + Idle Timeout 閒置超時 - + You are about to be logged out due to inactivity. 你即將因為長時間沒有活動而被退出。 - + Promotion 升級 - + You have been promoted. Please log out and back in for changes to take effect. 你已被升級.請登出然後再登入讓本更改生效. - + Warned 被警告 - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. 你由於$1 原因,現在接受警告. 請避免繼續本行為, 否則你將有可能會接受進一步的警誡或懲罰. 若有任何疑問, 請私信我們門下的版主. - + You have received the following message from the server. (custom messages like these could be untranslated) 你收到松本伺服器發出的以下訊息(類似本信息的自定訊息可能未經翻譯) @@ -9486,6 +12771,34 @@ Please refrain from engaging in this activity or further actions may be taken ag + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -9500,6 +12813,65 @@ Please refrain from engaging in this activity or further actions may be taken ag 不能分析本牌庫. + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -9571,57 +12943,52 @@ Please refrain from engaging in this activity or further actions may be taken ag UserCardArtSettingsDialog - + Card Art Settings - + Type a card name... - + Card name: - - Card ProviderId: + + Printing: - + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + Left margin (%): - + Right margin (%): - - Vertical offset: - - - - - Zoom: - - - - - Parameters - - - - - Preview - - - - + Remove Banner Card @@ -9629,216 +12996,266 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details 用户&詳細信息 - + Private &chat 私人&聊天 - + Show this user's &games 顯示這個用户的&遊戲 - + + View this user's &public decks + + + + Add to &buddy list 加入到&好友列表 - + Remove from &buddy list 從&好友列表移除 - + Add to &ignore list 加入到&屏蔽列表 - + Remove from &ignore list 從&屏蔽列表移除 - + Kick from &game 從&遊戲中踢出 - + + Report user + + + + Warn user 警告用戶 - + View user's war&n history 查看用户警告记录 - + Ban from &server 把用戶從&伺服器禁用 - + View user's &ban history 查看用户&禁止記錄 - + &Promote user to moderator 升$用戶作為版主 - + Dem&ote user from moderator 把&用戶從版主降級 - + Promote user to &judge 把&用戶升級成裁判 - + Demote user from judge 把&用戶從裁判降級 - + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes 閱讀管理筆記 - - + Investigate user + + + + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games %1 的遊戲(眾) - - - + + + Ban History 禁用歷史 - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason 禁止時間;版主;禁止時長;禁止理由;可見理由 - + User has never been banned. 用戶從未被禁 - + Failed to collect ban information. 不能獲取禁用訊息 - - - + + + Warning History 警告歷史 - + Warning Time;Moderator;User Name;Reason 警告時間;版主;用戶名稱;理由 - + User has never been warned. 用戶從未接收警告 - + Failed to collect warning information. 不能獲取警告信息 - + Failed to get admin notes. 不能獲取管理筆記 - - + + Success 成功 - + Successfully promoted user. 用戶升級成功 - + Successfully demoted user. 用戶降級成功 - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + Kick Player - + Are you sure you want to kick this player from the game? - - - + + + Failed 失敗 - + Failed to promote user. 用戶升級失敗 - + Failed to demote user. 用戶降級失敗 - + Copy hash to clipboard 把哈希覆印到用戶剪貼 - + Remove this user's messages 移除這用戶訊息 @@ -9876,61 +13293,61 @@ Please refrain from engaging in this activity or further actions may be taken ag 管理員 - + Moderator 版主 - + Registered user 登記用戶 - - + + Unregistered user 未登記用戶 - + Judge 裁判 - + Unknown 不知名 - + The entered password does not match your account. 輸入密碼跟你戶口不匹配 - - - - + + + + Information 資訊 - + User information updated. 用戶資訊已更新。 - - - - - - + + + + + - - - + + + + Error 出現錯誤 @@ -9954,283 +13371,292 @@ Please refrain from engaging in this activity or further actions may be taken ag Edit Banner Card + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) %n年(眾) , - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format %10%n日(眾) %20 - + Enter Password 輸入密碼 - + Password verification is required in order to change your email address 要核實密碼才能更改你的電郵地址 - - - + + + An error occurred while trying to update your user information. 在嘗試更新你的用戶信息時出現錯誤. - + The selected card is blacklisted on this server or another error occurred. - + Banner card removed. - + Banner card updated. - + This server does not permit you to update your user informations. 本伺服器不容許你更新你的用戶資訊. - + Password changed. 密碼已更改 - + This server does not permit you to change your password. 本伺服器不容許你更改密碼 - + The new password is too short. 新的密碼太短 - + The old password is incorrect. 舊的密碼不正確 - + Avatar updated. 頭像已更新。 - + This server does not permit you to update your avatar. 本伺服器不容許你更新你的頭像. - + An error occured while trying to update your avatar. - - - This server does not permit you to update your user informations. - An error occured while trying to updater your avatar. - 本伺服器不容許你更新你的用戶資訊. - UserInfoPopup - - + + Games - + Chat - + Open private chat - + Profile - + View user profile - + Show this user's games - + + Invite + + + + + Invite to your game + + + + − Buddy - + Remove from buddy list - + + Buddy - + Add to buddy list - + − Ignore - + Remove from ignore list - + + Ignore - + Add to ignore list - + Ban - + Ban from server - + Warn - + Warn user - + Ban log - + View ban history - + Warn log - + View warning history - + Notes - + View admin notes - + − Mod - + Demote from moderator - + + Mod - + Promote to moderator - + − Judge - + Demote from judge - + + Judge - + Promote to judge - + Join game - + Spectate - - + + Loading games… - + Could not load games. - + No active games. @@ -10238,223 +13664,344 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings 一般通用接口設置 - + &Double-click cards to play them (instead of single-click) 雙點擊使出卡牌 (而不是單點擊使出) - + &Clicking plays all selected cards (instead of just the clicked card) &單一點擊把所有選擇的卡牌使出(不只是被點擊的單張卡牌) - + &Play all nonlands onto the stack (not the battlefield) by default &把所有使出的非地牌放入堆疊區(不是戰場) 作為常用設置 - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens 在令牌上標註牌中文字 - + Show selection count during drag selection - + Show total selection count - - Show subtype breakdown in selection tally + + + Disabled - + + + Enabled + + + + + + Automatic + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + Use tear-off menus, allowing right click menus to persist on screen 使用浮動菜單,允許右鍵單擊, 把菜單保留在屏幕上 - + Keep game chat focused when clicking in game (Note: disables card view search bar) - + Notifications settings 通告設置 - + Enable notifications in taskbar 開啓任務欄通告 - + Notify in the taskbar for game events while you are spectating 觀看時在任務欄提示遊戲信息 - + Notify in the taskbar when users in your buddy list connect 在任務欄中提示好友連到伺服器 - + Animation settings 動畫設定 - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation &橫置/重置 動畫 - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings - + Open deck in new tab by default 把用新頁開啟牌庫作為常用設置 - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - - do nothing + + Do nothing - - ask to convert to .cod + + Ask to convert to .cod - - always convert to .cod + + Always convert to .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings 重播設置 - + Buffer time for backwards skip via shortcut: 用速鍵啟動向後跳的緩衝時間: + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + Flip coin - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -10491,22 +14038,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... - + Save Filter - + Save all currently applied filters to a file - + Enter filename... @@ -10514,72 +14061,72 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterToolbarWidget - + Sort by - + Filter by - + Save and load filters - + Filter by exact card name - + Filter by card main-type - + Filter by card sub-type - + Filter by set - + Filter by format legality - + Save/Load - + Name - + Main Type - + Sub Type - + Sets - + Formats @@ -10681,7 +14228,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -10689,19 +14236,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -10747,28 +14294,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - - + + Visual - + Loading database ... - + Clear all filters - + Table @@ -10776,43 +14323,43 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat @@ -10828,12 +14375,12 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -10841,17 +14388,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -10859,7 +14406,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -10867,52 +14414,57 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + Show Color Identity - + Show Tags On Deck Previews - + + Show Upload Time + + + + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -10920,7 +14472,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -10928,22 +14480,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -10951,17 +14503,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + + Select decks to share + + + + Visual Deck Storage Settings @@ -10969,177 +14526,202 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? 你想發出那種警告? - + Redact all messages from this user in all rooms 把這用戶在所有房間留下的訊息刪除 - + &OK &確認 - + &Cancel &取消 - + Warn user for misconduct 對行為不當的用戶發出警告 - - + + Error 出現錯誤 - + User name to send a warning to can not be blank, please specify a user to warn. 接收警告的用戶名稱不能空白.請定好要接收警告的用戶名字. - + Warning to use can not be blank, please select a valid warning to send. 警告名稱不能留空. 請選擇有效的警告. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top 將選擇的牌組送到最頂 - + Move selected set up 將選擇的牌組往上一格 - + Move selected set down 將選擇的牌組往下一格 - + Move selected set to the bottom 將選擇的牌組送到最底 - - Search by set name, code, or type - 用排組名稱,代碼,或類型進行搜尋 - - - + Default order 常用次序 - + Restore original art priority order 恢復常用藝術先後次序 - + Enable all sets 啟用所有牌組 - + Disable all sets 禁用所有卡组 - + Enable selected set(s) 啟用選擇的牌組(眾)  - + Disable selected set(s) 停用選擇的牌組(眾) - + Deck Editor 牌庫編輯 - + Use CTRL+A to select all sets in the view. 請按 CTRL + A, 以此選擇所有看到的牌組 - + Only cards in enabled sets will appear in the card list of the deck editor. 只有已經被啟用的卡牌組中的卡牌,才會在牌庫編輯中的卡牌清單出現 - + Image priority is decided in the following order: 卡牌圖片先後次序由以下次序決定: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki 自訂檔案夾(%`1)優先, 此對話窗中已被啟用的牌組為後(由頂到底) - + Include cards rebalanced for Alchemy [requires restart] - + Card Art 卡牌藝術 - + + Search by set name, code, type, or release date + + + + How to use custom card art 如何使用自定卡牌圖片 - + Hints 提示 - + Note 筆記 - + Sorting by column allows you to find a set while not changing set priority. 按列排序允許您在不更改系列優先級別的情況下查找牌組。 - + To enable ordering again, click the column header until this message disappears. 要再次啓用排序,請單擊列標題,直到此消息消失。 - + Use the current sorting as the set priority instead 用現在排序代替牌組先後 - + Sorts the set priority using the same column 用同一列分牌組先後 - + Manage sets 管理牌組 @@ -11147,93 +14729,85 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped 未分組 - + Group by Type 按類型分組 - + Group by Mana Value 按法力數值分組 - + Group by Color 按顏色排序 - + Unsorted 未排序 - + Sort by Name 按名稱排次序 - + Sort by Type 按類型排序 - + Sort by Mana Cost 按法力費用排序 - + Sort by Colors 按顏色(眾) 排序 - + Sort by P/T 按力量/防禦力排序 - + Sort by Set 按排組排序 - + shuffle when closing 關閉界面時洗牌 - + pile view 棟疊觀看 - - i18n - - - English - - - main - + Connect on startup 啟動時自動連接 - + Debug to file 偵錯紀錄寫進檔案 @@ -12017,6 +15591,16 @@ Please refrain from engaging in this activity or further actions may be taken ag Graveyard 墳場 + + + Report Queue + + + + + Moderation + + diff --git a/cockatrice/translations/cockatrice_zh-Hans.ts b/cockatrice/translations/cockatrice_zh-Hans.ts index a2df2035e..827c25f06 100644 --- a/cockatrice/translations/cockatrice_zh-Hans.ts +++ b/cockatrice/translations/cockatrice_zh-Hans.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... &设置指示物 @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter 设置指示物 - + New value for counter '%1': 数值物新价值为 '%1' @@ -36,61 +36,110 @@ AbstractTabDeckEditor - + Open in new tab - + 在新标签内打开 - + Are you sure? - + 是否确定? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + 出错 - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck + 保存套牌 + + + + + Share deck - + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. + 未能保存套牌。 + + + + There are no cards in your deck to be exported + + + + + AccountSetupPage + + + Join a Server - - There are no cards in your deck to be exported + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. @@ -105,12 +154,12 @@ Please check that the directory is writable and try again. AdminNotesDialog - + Update Notes 更新后笔记 - + Admin Notes for %1 %1 的管理笔记 @@ -130,224 +179,310 @@ Please check that the directory is writable and try again. Tokens - + 衍生物 AppearanceSettingsPage - + seconds 秒 - + Error 出现错误 - + Could not create themes directory at '%1'. 不能在'%1'创造主题目录 - + Theme settings 主题设定 - + Current theme: 现在主题 - + Open themes folder 打开主题文件夹 - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + 禁用 - + Menu settings 选单设定 - + Show keyboard shortcuts in right-click menus 在右击选单中显示键盘快速键 - + Show game filter toolbar above list in room tab - + Card rendering 卡面算绘 - + Display card names on cards having a picture 最有图片的牌上显示牌名 - + Auto-Rotate cards with sideways layout 将打横佈局卡片自动向上旋转 - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Light - + 浅色 - + Dark - + 深色 - + + System + 跟随系统 + + + + Show all playmats - + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + Active theme palette: - + + Active theme style: + + + + + Qt widget style saved to this theme ("System" lets the application decide) + + + + Edit theme palette - + Home tab settings - + Display card name of background in bottom right - + + Dim the home tab background + + + + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + Styling settings - + Style user list - + Card printings - + Bump sets that the deck contains cards from to the top in the printing selector 将牌库中包含的牌组推到艺术选择视窗之顶 - + Scale cards on mouse over 卡牌随游标缩放 - + Use rounded card corners - + Card layout - + Minimum overlap percentage of cards on the stack and in vertical hand 在迭区中和直行手牌中,尽量减少交重迭百分点 - + Maximum initial height for card view window: 卡望框最高开头高度 - - + + rows 横列 - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout 手牌佈局 - + Display hand horizontally (wastes space) 打栋显示手牌(浪费空间) - + Enable left justification 啟用向左对齐 - + Table grid layout 表格布局 - + Invert vertical coordinate 反转垂直坐标 - + Minimum player count for multi-column layout: 界面佈局之内能够容纳的最少玩家数量: - + Maximum font size for information displayed on cards: 卡牌上显示的最大字体大小 @@ -355,12 +490,12 @@ Please check that the directory is writable and try again. ArchidektApiResponseDeckDisplayWidget - + Back to results - + Open Deck in Deck Editor @@ -386,112 +521,112 @@ Please check that the directory is writable and try again. BanDialog - + ban &user name 禁用 &用家名字 - + ban &IP address 禁用&IP地址 - + ban client I&D 禁用客户ID - + Ban type 禁用类型 - + &permanent ban &永久禁用 - + &temporary ban &暂时禁用 - + &Days: &日 - + &Hours: &小时 - + &Minutes: &分鐘 - + Duration of the ban 禁用时间 - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. 请记载禁用原因 这项只保留为版主, 被禁用户不能看到. - + Please enter the reason for the ban that will be visible to the banned person. 请记载被禁用户能看到的被禁原因. - + Redact all messages from this user in all rooms 将这用户所有室中打出的讯息完全移除 - + &OK &OK - + &Cancel &取消 - + Ban user from server 把用户从本伺服器禁用 - - - - + + + + Error 出现错误 - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. 若要对用户实施禁用,请你选择以下叁种的拼合:名字,IP,或用户id. - + You must have a value in the name ban when selecting the name ban checkbox. 若用用户名字施行禁用,必须要填写姓名. - + You must have a value in the ip ban when selecting the ip ban checkbox. 若用ip地址施行禁用,必须要写下IP地址. - + You must have a value in the clientid ban when selecting the clientid ban checkbox. 若用用户ID施行禁用,必须要写下用户ID. @@ -499,22 +634,22 @@ This is only saved for moderators and cannot be seen by the banned person. BetaReleaseChannel - + Beta 测试版 - + No reply received from the release update server. 从发佈更新伺服器收不到回覆. - + Invalid reply received from the release update server. 从发佈更新伺服器收不到有效的回覆. - + No reply received from the file update server. 从档案更新伺服器收不到有效的回覆. @@ -522,7 +657,20 @@ This is only saved for moderators and cannot be seen by the banned person. CardArtPreviewWidget - + + Banner preview + + + + + + + + Zoom %1× + + + + No card selected @@ -530,22 +678,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardArtRulesModel - + Card - + ProviderId - + Mode - + 模式 - + Reason @@ -553,65 +701,209 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name 名字 - + Sets 牌组 - + Mana cost 法力费用 - + Card type 卡牌类别 - + P/T 力量/防御力 - + Color(s) 顏色 + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + CardDatabaseView - + Add to Deck - + Add to Sideboard - + Select Printing - + + Add to Zone + + + + + Create &new zone... + + + + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards @@ -710,33 +1002,33 @@ This is only saved for moderators and cannot be seen by the banned person. Main Type - + 主要类型 Sub Type - + 子类型 CardInfoFrameWidget - + Image 卡图 - + Description 描述 - + Both 两个都显示 - + View transformation @@ -744,50 +1036,50 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards 查看关联卡牌 - + Add card to deck - + 添加卡牌到牌库 - + Mainboard - + 主牌库 - + Sideboard - + 备牌 CardInfoTextWidget - + Name: 名称: - + Set: - + Collector Number: - + Related cards: 有关牌名: - + Unknown card: 不知名的牌 @@ -795,129 +1087,129 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &All players - + View related cards - + 查看关联卡牌 - + Token: - + 衍生物: - + All tokens - - - - - &Select All - + 所有衍生物 + &Select All + &全选 + + + S&elect Row - + S&elect Column - + &Play - + &Hide - + &隐藏 - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Reduce life by power - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -932,13 +1224,13 @@ This is only saved for moderators and cannot be seen by the banned person. Filesystem - + 文件系统 CardSizeWidget - + Card Size 卡牌大小 @@ -1078,6 +1370,24 @@ This is only saved for moderators and cannot be seen by the banned person. + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser @@ -1104,283 +1414,385 @@ This is only saved for moderators and cannot be seen by the banned person. Pick colour + 选择颜色 + + + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket ConnectionController - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - + Failed Login - + 登入失败 - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - - - - - - - - - - - + + + + + + + + + + - - + + + Error - + 出错 - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - - Server Full + + Password Change Required - + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + + Server Full + 服务器已满 + + + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - + + + + Registration denied - + 注册被拒绝 - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + 密码太短。 - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + Server timeout - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Information - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -1388,113 +1800,113 @@ To update your client, go to Help -> Check for Updates. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - + Requesting forgotten password to %1 as %2... - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. @@ -1544,7 +1956,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1560,17 +1972,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add card to &maindeck - + Add card to &sideboard @@ -1578,97 +1990,158 @@ To update your client, go to Help -> Check for Updates. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + Format: - + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1676,17 +2149,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1694,114 +2167,119 @@ To update your client, go to Help -> Check for Updates. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + + Share deck... + + + + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1809,7 +2287,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1817,110 +2295,152 @@ To update your client, go to Help -> Check for Updates. DeckEditorSettingsPage - - + + Update Spoilers 更新预览卡牌 - + Success 成功 - + Download URLs have been reset. 下载URL已重置。 - + Add URL 添加URL - - + + URL: URL: - - + + Edit URL 更改URL - + Add New URL 添置新 URL - + Remove URL 移除 URL. - + Updating... 更新中... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path 选择路径 - + URL Download Priority URL下载优先次序 - + Spoilers 预览 - + Download Spoilers Automatically 自动下载预览 - + Spoiler Location: 预览位置: - + Last Change 最后变更 - + Spoilers download automatically on launch 开啟时自动下载预览 - + Press the button to manually update without relaunching 免重新啟动人手按制更新 - + Do not close settings until manual update is complete 人手更新完成前,不能关掉设置 - + Download card pictures on the fly 即时下载卡牌图片 - + How to add a custom URL 如何添加自定URL - + Reset Download URLs 重置下载URL @@ -1928,32 +2448,32 @@ To update your client, go to Help -> Check for Updates. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1961,27 +2481,27 @@ To update your client, go to Help -> Check for Updates. DeckListModel - + Count 数目 - + Set 牌组 - + Number 号码 - + Provider ID 提供者 id - + Card 卡牌 @@ -2002,17 +2522,22 @@ To update your client, go to Help -> Check for Updates. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -2020,7 +2545,7 @@ To update your client, go to Help -> Check for Updates. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -2028,62 +2553,62 @@ To update your client, go to Help -> Check for Updates. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2091,94 +2616,109 @@ To update your client, go to Help -> Check for Updates. DeckPreviewWidget - + Banner Card - + Open in deck editor - + + Share deck... + + + + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Rename deck + + + + + Rename file + + + + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -2186,57 +2726,102 @@ To update your client, go to Help -> Check for Updates. DeckStateManager - + Rename deck to "%1" from "%2" - + Updated comments (was %1 chars, now %2 chars) - + Set banner card to %1 (%2) - + + Set playmat to %1 + + + + Tags changed - + Set format to %1 - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + %1 1 × "%2" (%3) - + Added - + Removed @@ -2258,94 +2843,122 @@ To update your client, go to Help -> Check for Updates. DeckViewContainer - + Load deck... 载入牌库... - + Load remote deck... 载入伺服器上的牌库... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start 準备好 - + Force start 强迫开始 - + Sideboard unlocked 备牌解锁 - + Sideboard locked 解锁锁上 - - + + Error 出现错误 - + The selected file could not be loaded. 选择的档案不能再入 - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? - + Remember and automatically apply choice in the future @@ -2358,143 +2971,143 @@ This will kick all non-ready players from the game. 下载中... - + Known Hosts 已知主办机 - + Delete the currently selected saved server 删除现选储藏伺服器. - + Refresh the server list with known public servers 从已知的伺服器更新伺服器列表 - + New Host 新主办 - + Name: 名称: - + &Host: &主办机: - + &Port: &端口: - + Player &name: 玩家 &名字: - + P&assword: &密码: - + &Save password &储存密码 - + A&uto connect &自动连接 - + Automatically connect to the most recent login when Cockatrice opens 当Cockatrice启动时自动连接到最近的登录 - + If you have any trouble connecting or registering then contact the server staff for help! 如在连接或註册谁中遇到问题,请联络伺服器职员寻求援助! - - + + Webpage 网页 - + Reset Password 重设密码 - + Forgot password? 密码忘记了? - + &Connect 连接 - + Server 伺服器 - + Login 登录 - + Server Contact 伺服器联络人 - + Connect to Server 连接伺服器 - + Server URL 伺服器URL - + Communication Port 通信端口 - + Unique Server Name 独特伺服器名字 - + Connection Warning 连接警告 - + You need to name your new connection profile. 你必须将你的连接简介命名 - + Connect Warning 连接警告 - + The player name can't be empty. 玩家名称不能空置 @@ -2502,122 +3115,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings &记住设定 - + &Description: &描述: - + P&layers: 玩家: - + General 一般 - + Game type 游戏类型 - + &Password: &密码: - + Only &buddies can join 只容许&好友加入 - + Only &registered users can join 只容许&註册用户加入 - + Joining restrictions 加入条件 - + &Spectators can watch 允许观看者 - + Spectators &need a password to watch 观看者&需要密码 - + Spectators can &chat 观看者可以&聊天 - + Spectators can see &hands 观看者可见&手牌 - + Create game as spectator 以旁观者的身份创建游戏 - + Spectators 观看者 - + Starting life total: 开始生命总数 - + Open decklists in lobby - + Create game as judge - + Game setup options 游戏设定选择 - + &Clear &清除 - + Create game 创建游戏 - + Game information 游戏资讯 - + Error 出现错误 - + Server error. 伺服器出现错误. @@ -2625,97 +3238,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: &名称: - + Token 令牌 - + C&olor: &顏色: - + white 白色 - + blue 蓝色 - + black 黑色 - + red 红色 - + green 绿色 - + multicolor 多色 - + colorless 无色 - + &P/T: &力量/防御力: - + &Annotation: &註释: - + &Destroy token when it leaves the table &此令牌离场时会被摧毁 - + Create face-down (Only hides name) - + Token data 令牌资料 - + Show &all tokens 显示&所有令牌 - + Show tokens from this &deck 显示本牌库所有令牌 - + Choose token from list 从清单中选择令牌 - + Create token 派出令牌 @@ -2723,53 +3336,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + ✖ - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2778,7 +3391,7 @@ This will kick all non-ready players from the game. DlgEditAvatar - + No image chosen. 未选择图像 @@ -2790,27 +3403,32 @@ To remove your current avatar, confirm without choosing a new image. 若要移除现有头像, 请不要选择新的图片,直接确认. - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... 瀏览... - + Change avatar 更改头像 - + Open Image 打开图片 - + Image Files (*.png *.jpg *.bmp) 图片文件(*.png *.jpg *.bmp格式) - + Invalid image chosen. 所选图片不能使用。 @@ -2836,38 +3454,38 @@ To remove your current avatar, confirm without choosing a new image. DlgEditPassword - + Old password: 旧密码: - + New password: 新密码: - + Confirm new password: 确认新密码: - + Change password 更改密码 - - + + Error 出现错误 - + Your password is too short. 你的密码太短。 - + The new passwords don't match. 新旧密码不一致 @@ -2875,93 +3493,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: &名称: - + C&olor: &顏色: - + white 白色 - + blue 蓝色 - + black 黑色 - + red 红色 - + green 绿色 - + multicolor 多色 - + colorless 无色 - + &P/T: &力量/防御力: - + &Annotation: &註释: - + Token data 令牌资料 - - + + Add token 添加令牌 - + Remove token 移除令牌 - + Edit custom tokens 编辑自定令牌 - + Please enter the name of the token: 请输入令牌名称: - + Error 出现错误 - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. 现时选择的名字跟现有的卡牌或令牌出现衝突.请从"牌组管理"中啟动'令牌牌组',保持正确显示. @@ -3039,7 +3657,8 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games that have started + Hide started games + Hide games that have started @@ -3075,8 +3694,9 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - &Creator name: - &创建者名称: + &Host name: + &Creator name: + @@ -3142,52 +3762,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning 重设密码验证警告 - + A problem has occurred. Please try to request a new password again. 程序出现故障,请再要求新的密码. - + Enter the information of the server and the account you'd like to request a new password for. 请输入需要新密码的伺服器和相关户口资料. - + &Host: &主办机: - + &Port: &端口: - + Player &name: 玩家 &名字: - + Email: 电邮: - + Reset Password Challenge 重设密码验证 - + Reset Password Challenge Error 重设密码验证错误 - + The email address can't be empty. 电邮不能空置. @@ -3195,37 +3815,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. 请输入需要新密码的伺服器资料. - + &Host: &主办机: - + &Port: &端口: - + Player &name: 玩家 &名字: - + Reset Password Request 重设密码要求 - + Reset Password Error 重设密码错误 - + The player name can't be empty. 玩家名称不能空置 @@ -3233,94 +3853,127 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning 重设密码警告 - + A problem has occurred. Please try to request a new password again. 程序出现故障,请再要求新的密码. - + Enter the received token and the new password in order to set your new password. 请输入收到验证码和新的密码,以便定立新的密码. - + &Host: &主办机: - + &Port: &端口: - + Player &name: 玩家 &名字: - + Token: 验证码 - - + + New Password: 新密码: - + Reset Password 重设密码 - + The player name can't be empty. 玩家名称不能空置 - - - - + + + + Reset Password Error 重设密码错误 - + The token can't be empty. 验证码不能为空白 - + The new password can't be empty. 新密码不能为空白 - + Error 出现错误 - + Your password is too short. 你的新密码太短。 - + The passwords do not match. 新旧密码不一致. + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck 载入牌库 @@ -3404,78 +4057,258 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLocalGameOptions - + Players: - + General - + Starting life total: - + Game setup options - + Remember settings - + Local game options + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): 卡牌名称(或搜寻表达式) - + Number of hits: 因为cockatrice原生没有ssl支援, 所以你不能自动下载更新. 请瀏览到我们的下载网页,进行人手更新. - + Auto play hits 开关副牌上锁 - + Put top cards on stack until... 把顶牌放在重迭区, 直到... - + No cards matching the search expression exists in the card database. Proceed anyways? 在卡牌资料库中找不到符合搜寻表达式的卡牌.你想继续吗? - + Cockatrice Cockatrice - + Invalid filter 筛选无效 + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. 输入你, 和你想登记的伺服器的资料. @@ -3483,88 +4316,309 @@ Your email will be used to verify your account. 我们会使用你的电邮来核实你的户口. - + &Host: &主办机: - + &Port: &端口: - + Player &name: 玩家 &名字: - + P&assword: &密码: - + Password (again): (再次)输入密码: - + Email: 电邮: - + Email (again): (再次)输入电邮: - + Country: 国家: - + Undefined 未定义 - + Real name: 实名: - + Register to server 註册加入伺服器 - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning 注册警告 - + Your password is too short. 你的密码太短。 - + Your passwords do not match, please try again. 两次输入的密码不一致,请重新输入, - + Your email addresses do not match, please try again. 两次输入的电子邮箱不一致,请重新输入。 - + The player name can't be empty. 玩家名称不能空置 + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice @@ -3630,46 +4684,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database 读取卡牌资料库时出现不知名错误 - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - 你的卡牌资料库无效。 - -资料库无效可能会导致Cockatrice无法正确运行。 - -你应该重新启动Oracle,更新卡牌资料库。 - -你想要重新设置卡牌资料库路径吗? - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - 你的卡牌资料库过旧。 - -这可能会导致卡牌图片或资料出现问题。 - -你应该重新启动Oracle,更新卡牌资料库。 - -你想要重新设置卡牌资料库路径吗? - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3682,7 +4702,7 @@ Would you like to change your database location setting? 你想要重新设置卡牌资料库路径吗? - + File Error loading your card database. Would you like to change your database location setting? @@ -3690,7 +4710,7 @@ Would you like to change your database location setting? 你想要重新设置卡牌资料库路径吗? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3699,7 +4719,7 @@ Would you like to change your database location setting? 你想要重新设置卡牌资料库路径吗? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3715,104 +4735,235 @@ https://github.com/Cockatrice/Cockatrice/issues 你想不想更改你的资料库位置设定? - - - + + + Error 出现错误 - + The path to your deck directory is invalid. Would you like to go back and set the correct path? 你的牌库目录路径现在无效. 你想要重新设置套牌路径吗? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? 你的卡牌图片目录路径现在无效. 你想要重新设置卡牌图片资料库路径吗? - + Settings 设定 - + + Search settings... + + + + + OK + + + + General 一般 - + Appearance 外观 - + User Interface 用户界面 - + Storage - + Card Sources 卡牌来源 - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat 聊天 - + Sound 声音 - + Shortcuts 快捷键 + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3820,17 +4971,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next 下一个 - + Previous 上一个 - + Tip of the Day 每日提示 @@ -3838,165 +4989,175 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel 现在发行渠道 - + Reinstall 重新安装 - + Cancel Download 取消下载 - + Open Download Page 打开下载页面 - + Check for Client Updates 检查客户端更新 - - - - + + + + Error 出现错误 - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. 鸡蛇不支持建立SSL连接,因此你不能自动下载更新! 请访问下载页面以手动更新。 - + Downloading update: %1 - + Checking for updates... 正在检查更新... - + Finished checking for updates 更新检查已完成 - + No Update Available 没有更新可用 - + Cockatrice is up to date! Cockatrice 已经达到最新版本! - + You are already running the latest version available in the chosen release channel. 你运行的已经是最新的发行版本了。 - + Current version 现行版本 - + Selected release channel 现选发行渠道 - - + + Update Available 现有更新版本 - - + + A new version of Cockatrice is available! Cockatrice有新版本可用! - - + + New version 新版本 - - + + Released 已发行 - - + + Changelog 变更记载 - + Do you want to update now? 你打算现在更新吗? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error 更新出现错误 - + An error occurred while checking for updates: 检测更新时出现错误: - + An error occurred while downloading an update: 下载更新时出现错误 - + Installing... 安装中 - + Cockatrice is unable to open the installer. Cockatrice 不能啟动安装程式 - + Try to update manually by closing Cockatrice and running the installer. 请你尝试人手更新, 关掉cockatrice, 然后重开安装程式. - + Download location 下载位置 @@ -4004,17 +5165,17 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing 关闭时清除纪录 - + Copy to clipboard - + Debug Log 侦错纪录 @@ -4252,7 +5413,7 @@ You may have to manually download the new version. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -4260,7 +5421,7 @@ You may have to manually download the new version. FilterBuilder - + Type your filter here 输入你的筛选类型 @@ -4268,45 +5429,88 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -4314,140 +5518,156 @@ You may have to manually download the new version. GameSelector - - - - - - - - - + + + + + + + + + Error 出现错误 - + Please join the appropriate room first. 请先参加合适房间 - + Wrong password. 密码错误. - + Spectators are not allowed in this game. 这个游戏不允许观眾加入. - + The game is already full. 这个游戏已经满了. - + The game does not exist any more. 这个游戏已经不存在了。 - + This game is only open to registered users. 这个游戏只开放给註册用户. - + This game is only open to its creator's buddies. 这个游戏只开放给房间主办的好友。 - + You are being ignored by the creator of this game. 你被这个房间的主办屏蔽了. - + Join Game - + Spectate Game - + Game Information - + + Cop&y game link + + + + Join Game as Judge - + Spectate Game as Judge - + + Join game 加入游戏 - - Password: - 密码: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. 请先参加相关的房间. - + &Filter games &筛选游戏 - + C&lear filter &清除筛选项目 - + C&reate &创建 - + &Join &加入 - + Join as judge - + J&oin as spectator &作为观看者加入 - + Join as judge spectator - + Games shown: %1 / %2 显示游戏:%1/%2 - + Games 游戏 @@ -4488,105 +5708,105 @@ You may have to manually download the new version. GamesModel - + >1 day >1日 - + %1%2 hr short age in hours %1%2 小时 - + new 新建 - + %1%2 min short age in minutes %1%2 分鐘 - + password 密码 - + buddies only 仅限好友 - + reg. users only 仅限註册用户 - + open decklists - - + + can chat 允许聊天 - + see hands 查看手牌 - + can see hands 允许查看手牌 - + not allowed 不允许 - + Room 房间 - + Age 时长 - + Description 描述: - - Creator - 创建者 + + Host + - + Type 类型 - + Restrictions 限制 - + Players 玩家 - + Spectators 观看者 @@ -4594,158 +5814,275 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths 重置所有路径 - + All paths have been reset 重置所有路径 - - - - - - - + + + + English + 简体中文 (Chinese Simplified) + + + + + + + + + Choose path 选择路径 - + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> + + + + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + Language settings - + Language: 语言: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + Version settings - + Card database - + Startup settings - + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) 路径(在便携模式下不能编辑) - + Paths 路径 - + How to help with translations - + Decks directory: 牌库路径: - + Filters directory: - + Replays directory: 游戏录像目录: - + Pictures directory: 图片目录: - + Card database: 卡牌资料库: - + Custom database directory: 自主资料库目录 - + Token database: 令牌资料库: - + Update channel 更新渠道 - + Check for client updates on startup 啟动时检查客户更新 - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client 如果客户端缺少伺服器支持的更功能时,请提示我 - + Automatically run Oracle when running a new version of Cockatrice 啟动新版本Cockatrice时,自动运行Oracle. - + Show tips on startup 显示启动时提示 - + Last update check on %1 (%2 days ago) @@ -4753,47 +6090,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4887,59 +6224,353 @@ You may have to manually download the new version. + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu @@ -5157,273 +6788,294 @@ You may have to manually download the new version. MainWindow - - + + Player %1 玩家 %1 - + Load replay 载入游戏录像 - + About Cockatrice 关於Cockatrice - + Version 版本 - + Cockatrice Webpage Cockatrice 网页 - + Project Manager: 项目经理: - + Past Project Managers: 前项目经理: - + Developers: 开发者: - + Our Developers 我们的开发者: - + Help Develop! 协助开发! - + Translators: 翻译者: - + Our Translators 翻译者名单 - + Help Translate! 协助翻译! - + Support: 支援: - + Report an Issue 报告问题 - + Troubleshooting 排除故障 - + F.A.Q. 问答 - - + + Error 出现错误 - + &Connect... &连线... - + &Disconnect &断线 - + Start &local game... 开始&离线游戏 - + &Watch replay... &观看游戏录像 - + &Full screen &全屏 - + &Register to server... &註册加入伺服器 - + &Restore password... &弄回密码 - + &Settings... 设置⋯ - + &Exit &退出 - + A&ctions &动作 - + &Cockatrice &Cockatrice - + C&ard Database &卡牌资料库 - + &Manage sets... &管理牌组 - + Edit custom &tokens... 编辑自定 &令牌... - + Open custom image folder 打开自定图片文件夹 - + Open custom sets folder 打开自定牌组文件夹 - + Add custom sets/cards 添加自定卡组/卡牌 - + Reload card database 刷新卡牌资料库 - + Tabs - + &Help &求助 - + &About Cockatrice &关於Cockatrice - + &Tip of the Day &每日提示 - + Check for Client Updates 检查客户端更新 - + Check for Card Updates... 检查卡牌更新 - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log 查看&侦错纪录 - + Open Settings Folder 打开设置文件夹 - + + Re-run Onboarding Wizard... + + + + Show/Hide 显示/隐藏 - + + Sign-in cancelled + + + + New Version 新版本 - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. 恭喜你将Cockatrice升级为%1. 现在会帮你啟动oracle, 更新你的卡牌资料库. - + Cockatrice installed Cockatrice 已被安装 - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. 恭喜你成功安装Cockatrice%1. 现在会帮你啟动oracle, 安装你的卡牌资料库. - + + Are you sure? + + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database 卡牌资料库 - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5432,29 +7084,29 @@ If unsure or first time user, choose "Yes" 如果不确定, 或者是第一次使用,请选择“是/Yes” - - + + Yes 是/Yes - - + + No 否/No - + Open settings 打开设置 - + New sets found 发现新卡组 - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? @@ -5463,22 +7115,22 @@ Do you want to enable it/them? 要啟用它们吗? - + Yes, always enable - + View sets 查看卡组 - + Welcome 欢迎 - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5487,88 +7139,88 @@ Read more about changing the set order or disabling specific sets and consequent 请阅读,了解更多在“卡组设置”对话框裡.更改卡组顺序或者禁用某些卡组的功能. - + Information 资讯 - + A card database update is already running. 资料库更新现已进行. - + Unable to run the card database updater: 不能运行卡牌资料附更新程式: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - - - - - + + + + + Load sets/cards 载入卡组/卡牌 - + Selected file cannot be found. 找不到选择的文件。 - + You can only import XML databases at this time. 你暂时只能导入xml资料库. - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. 已经成功添加新的卡组/卡牌. Cockatrice会重新载入卡牌资料库. - + Sets/cards failed to import. 卡组/卡牌导入失败. @@ -5773,615 +7425,615 @@ Cockatrice will now reload the card database. MessageLogWidget - + from play 从战场上 - + from their graveyard 从它们的坟场 - + from exile 从放逐区 - + from their hand 从它们的手牌 - + the top card of %1's library %1牌库的顶牌 - + the top card of their library 它们的顶牌 - + from the top of %1's library 从%1牌库的顶 - + from the top of their library 从它们1牌库顶 - + the bottom card of %1's library %1牌库的底牌 - + the bottom card of their library 它们牌库的底牌 - + from the bottom of %1's library 从%1的牌库底 - + from the bottom of their library 从它们的牌库底 - + from %1's library 从%1的牌库 - + from their library 从它们的牌库 - + from sideboard 从备牌中 - + from the stack 从迭区中 - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1现在保持%2顶牌显现. - + %1 is not revealing the top card %2 any longer. %1不再展示牌库顶牌%2。 - + %1 can now look at top card %2 at any time. %1可以随时查看牌库顶牌 %2. - + %1 no longer can look at top card %2 at any time. %1再不可以随时查看牌库顶牌 %2. - + %1 attaches %2 to %3's %4. %1 结附 %2 指向 %3的 %4. - + %1 has conceded the game. %1 在此游戏宣佈降服. - + %1 has unconceded the game. %1 收回在此游戏降服. - + %1 has restored connection to the game. %1已恢復连接。 - + %1 has lost connection to the game. %1 已失去连接。 - + %1 points from their %2 to themselves. %1 从它们的%2 指向自己。 - + %1 points from their %2 to %3. %1 从它们的%2指向%3。 - + %1 points from %2's %3 to themselves. %1 从%2的%3 指向自己。 - + %1 points from %2's %3 to %4. %1 从%2的%3 指向%4。 - + %1 points from their %2 to their %3. %1 从它们的%2指向它们的%3。 - + %1 points from their %2 to %3's %4. %1 从它们的%2指向%3的%4。 - + %1 points from %2's %3 to their own %4. %1 从%2的%3指向自己的%4。 - + %1 points from %2's %3 to %4's %5. %1 从%2的%3指向%4的%5。 - + %1 creates a face down token. - + %1 creates token: %2%3. %1 派出令牌: %2%3 - + %1 has loaded a deck (%2). %1 已载入牌库(%2) - + %1 has loaded a deck with %2 sideboard cards (%3). %1已载入含有 %2张备牌的牌库(%3). - + %1 destroys %2. %1销毁了%2。 - + a card 一张牌 - + %1 gives %2 control over %3. %1将%3的控制转移给%2. - + %1 puts %2 into play%3 face down. %1将%2%3牌面朝下地放进战场。 - + %1 puts %2 into play%3. %1将%2放进战场%3。 - + %1 puts %2%3 into their graveyard face down. - + %1 puts %2%3 into their graveyard. %1将%2%3置入它们的的坟场。 - + %1 exiles %2%3 face down. - + %1 exiles %2%3. %1 放逐%2%3. - + %1 moves %2%3 to their hand. %1将%2%3移到它们的的手牌中。 - + %1 puts %2%3 into their library. %1将%2%3放入它们的牌库。 - + %1 puts %2%3 onto the bottom of their library. %1将%2%3放在它们的牌库底部。 - + %1 puts %2%3 on top of their library. %1将%2%3放在它们的牌库顶部。 - + %1 puts %2%3 into their library %4 cards from the top. %1将%2%3放在它们的牌库顶部%4张牌以下。 - + %1 moves %2%3 to sideboard. %1 将%2%3移到副牌库. - + %1 plays %2%3 face down. - + %1 plays %2%3. %1使出 %2%3。 - + %1 moves %2%3 to custom zone '%4' face down. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library %1尝试从空牌库抽牌 - + %1 draws %2 card(s). %1抽%2张牌。 - + %1 is looking at %2. %1 正在查看%2。 - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural %1 正在查看%2的 %4 %3张卡牌. - + bottom 底部 - + top 顶部 - + %1 turns %2 face-down. %1把%2翻为面朝下。 - + %1 turns %2 face-up. %1把%2翻为面朝上。 - + The game has been closed. 游戏已经关闭。 - + The game has started. 游戏已开始。 - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 已经加入游戏。 - + %1 is now watching the game. %1 正在旁观游戏。 - + You have been kicked out of the game. 你已被踢出游戏。 - + %1 has left the game (%2). %1 已经离开游戏(%2)。 - + %1 is not watching the game any more (%2). %1 已经不再旁观游戏(%2)。 - + %1 is not ready to start the game any more. %1 还未準备好开始游戏。 - + %1 shuffles their deck and draws a new hand of %2 card(s). %1将其牌库洗牌,然后抽%2张牌作为新的起手。 - + %1 shuffles their deck and draws a new hand. %1将其牌库洗牌,然后抽起新的手牌。 - + You are watching a replay of game #%1. 你正在观看游戏#%1的录像。 - + %1 is ready to start the game. %1已準备好开始游戏。 - + cards an unknown amount of cards 卡牌(眾)  - + %1 card(s) a card for singular, %1 cards for plural %1 卡牌(眾)  - + %1 lends %2 to %3. %1把&2借给%3. - + %1 reveals %2 to %3. %1把%2展示给%3. - + %1 reveals %2. %1 展示 %2。 - + %1 randomly reveals %2%3 to %4. %1随机展示%2%3给%4。 - + %1 randomly reveals %2%3. %1随机展示%2%3。 - + %1 peeks at face down card #%2. %1查看面朝下的卡牌#%2。 - + %1 peeks at face down card #%2: %3. %1查看面朝下的卡牌#%2:%3。 - + %1 reveals %2%3 to %4. %1展示%2%3给%4。 - + %1 reveals %2%3. %1展示%2%3。 - + %1 reversed turn order, now it's %2. %1 逆转回合次序,现在轮到%2。 - + reversed 逆转 - + normal 正常 - + Heads 正面 - + Tails 翻面 - + %1 flipped a coin. It landed as %2. %1掷了硬币。结果为%2。 - + %1 rolls a %2 with a %3-sided die. %1掷%3面骰子,结果为%2。 - + %1 flips %2 coins. There are %3 heads and %4 tails. %1掷了%2个硬币。结果为%3个正面和%4个反面。 - + %1 rolls a %2-sided dice %3 times: %4. %1把%2面骰子掷了%3次:%4 - + %1's turn. 轮到 %1. - + %1 sets annotation of %2 to %3. %1给%2添加了註释成%3。 - + %1 places %2 %3%4 counter(s) on %5 (now %6). - + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + %1 failed to undo their last draw. - + %1 sets counter %2 to %3 (%4%5). %1 将%2指示物设置为 %3 (%4%5)。 - + %1 sets %2 to not untap normally. %1 将 %2 设置为不被重置。 - + %1 sets %2 to untap normally. %1 将 %2 设置为照常重置。 - + %1 removes the PT of %2. %1 移除%2的力量/防御. - + %1 changes the PT of %2 from nothing to %4. %1把%2的力量/防御从无数值转为%4. - + %1 changes the PT of %2 from %3 to %4. %1把%2的力量/防御从%3转为%4. - + %1 has locked their sideboard. %1已经锁定它们的的副牌库。 - + %1 has unlocked their sideboard. %1已经把它们的的副牌库解锁。 - + %1 taps their permanents. %1横置它们的永久卡牌 - + %1 untaps their permanents. %1重置它们的永久卡牌. - + %1 taps %2. %1 横置 %2. - + %1 untaps %2. %1 重置 %2。 - + %1 shuffles %2. % 1 对 %2 洗牌 - + %1 shuffles the bottom %3 cards of %2. %1 洗 %2 底的 %3 张卡牌. - + %1 shuffles the top %3 cards of %2. %1 洗 %2 顶的 %3 张卡牌. - + %1 shuffles cards %3 - %4 of %2. %1到%2.洗%3 - %4 - + %1 unattaches %2. %1 取消了%2的结附。 - + %1 undoes their last draw. %1 放回它们最后一张抽到的卡牌. - + %1 undoes their last draw (%2). %1 放回它们最后一张抽到的卡牌(%2). @@ -6389,115 +8041,125 @@ Cockatrice will now reload the card database. MessagesSettingsPage - + Word1 Word2 Word3 词1 词2 词3 - + Add New Message 添加新信息 - + Edit Message 更改信息 - + Remove Message 移除信息 - + Add message 添加信息 - - + + Message: 信息: - + Edit message 更改信息 - + Chat settings 聊天设定 - + Custom alert words 自定通知触发词语 - + Enable chat mentions 允许聊天中提到某人 - + Enable mention completer 允许自动完成提名 - + In-game message macros 游戏内短信速键 - + How to use in-game message macros 如何使用游戏内短信速键 - + Ignore chat room messages sent by unregistered users 不要显示未註册用户的聊天消息。 - + Ignore private messages sent by unregistered users 不要显示未註册用户的私人消息. - + Ignore private messages sent by non-buddy users - - + + Invert text color 反转文本顏色 - + Enable desktop notifications for private messages 容许桌面提醒提及私人消息 - + Enable desktop notification for mentions 容许桌面提醒提及聊天提名 - + Enable room message history on join 加入聊天室时开启消息历史 - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) (顏色为16进制) - + Separate words with a space, alphanumeric characters only 将单词以空格区分,仅支持字母. @@ -6505,42 +8167,42 @@ Cockatrice will now reload the card database. MoveMenu - + Move to - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + T&able - + &Hand - + &Graveyard - + &Exile @@ -6619,148 +8281,134 @@ Cockatrice will now reload the card database. PaletteEditorDialog - - + + Reset - - - Apply - - - - - - Save && Apply - - - - + ▼ Edit Palette - - + + ▶ Edit Palette - + Palette Editor — %1 - + <b>Palette Editor</b> &nbsp;·&nbsp; %1 - - This theme ships no default palette files - - - - - Replace current colours with the theme author's defaults - - - - - Switch between the light and dark palette files + + + Save - Editing: + Delete this scheme's custom palette and revert to the theme default (or the application palette) - Show or hide the per-role colour grid for manual tweaks + Switch between the light and dark palette files + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + ↺ Revert to theme default - + Discard unsaved edits and restore the last saved palette - - Preview this palette without saving to disk + + Cannot save: this theme has no writable directory - + Write palette-%1.toml and reload the theme - - Cannot save: this theme has no directory on disk - - - - + + Save failed - + + Could not write %1 to: %2 - - - No default found - - - - - No default palette file found for the "%1" scheme. - - PaletteGridWidget - + Active - + Disabled - + Inactive - + Normal interactive state - + Widget is disabled / not interactive - + Window is in background / unfocused + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + Phase @@ -6883,30 +8531,21 @@ Cockatrice will now reload the card database. 回合完结部 - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - zhs - - PlayerActions - - - - + + + + grave - - - - + + + + exile @@ -7025,16 +8664,272 @@ Cockatrice will now reload the card database. PlayerMenu - + Player "%1" - + &Counters + + PlaymatCollectionDialog + + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + PrintingDisabledInfoWidget @@ -7055,12 +8950,12 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelector - + Display Navigation Buttons - + Printing Selector @@ -7068,25 +8963,65 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardOverlayWidget - + Preference 喜好 - + Pin Printing 版本上别针 - + Unpin Printing 版本脱别针 - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards 显示关联卡牌 + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -7117,28 +9052,28 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardSortingWidget - + Alphabetical 英文字母次序 - + Preference 喜好 - + Release Date 发行日期 - - + + Descending 由后到先 - + Ascending 由先倒后 @@ -7154,95 +9089,103 @@ This setting means you'll only see the default printing for each card, instead o PtMenu - + Power / toughness - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services 服务 - + Hide %1 隐藏 %1 - + Hide Others 隐藏其他 - + Show All 全部展示 - + Preferences... 喜好... - + Quit %1 退出%1 - + About %1 关於 %1 @@ -7250,17 +9193,17 @@ This setting means you'll only see the default printing for each card, instead o QObject - + Cockatrice card database (*.xml) Cockatrice卡牌资料库 (*.xml) - + All files (*.*) 所有档案格式 (*.*) - + Cockatrice replays (*.cor) Cockatrice录像文件 (*.cor) @@ -7280,17 +9223,22 @@ This setting means you'll only see the default printing for each card, instead o 眾令牌 - + + Maybeboard + + + + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7301,7 +9249,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7312,100 +9260,110 @@ Are you sure you would like to disable this feature? - + Confirm Change + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK OK/确认 - + Save 保存 - + Save All 全部保存 - + Open 打开 - + &Yes &是/Yes - + Yes to &All &全选是/Yes - + &No &否/No - + N&o to All &全选否/No - + Abort 退出 - + Retry 重试 - + Ignore 不理 - + Close 关闭 - + Cancel 取消 - + Discard 弃牌 - + Help 协助 - + Apply 应用 - + Reset 重设 - + Restore Defaults 恢復常用认值 @@ -7413,121 +9371,189 @@ Are you sure you would like to disable this feature? QuickSetupPanel - + %1% - + <b>Quick Setup</b> - + Generate all palette roles automatically from a single accent colour - + Accent: - + Primary hue. Used directly for highlights and links. At high intensity it also tints buttons and backgrounds. - + Intensity: - + Subtle - + Full colour - + 0–30 Subtle tint — only highlights and links change hue 30–70 Accented — buttons, tooltips, and borders join in 70–100 Full colour — backgrounds, everything - + 70% - - - Generate ↓ - - - - - Derive all palette roles from the accent colour above. -Fine-tune individual colours in the grid afterwards. - - RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name 名字 - + ID ID - + Upload time 上载时间 + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID ID - + Name 名字 - + Players 玩家(眾) - + Keep 选用 - + Time started 开始时间 - + Duration (sec) 维持(秒) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu @@ -7569,37 +9595,37 @@ Fine-tune individual colours in the grid afterwards. RoomSelector - + Rooms 房间(眾)  - + Joi&n &加入 - + Room 房间 - + Description 描述 - + Permissions 许可 - + Players 玩家(眾) - + Games 游戏(眾) @@ -7607,7 +9633,7 @@ Fine-tune individual colours in the grid afterwards. SayMenu - + S&ay @@ -7615,32 +9641,32 @@ Fine-tune individual colours in the grid afterwards. SequenceEdit - + Choose an action from the table 从列表中选择行动 - + Hit the key/combination of keys you want to set for this action 按下以此行动设置的单键或组键 - + Invalid key 设置的单键或组键无效 - + Shortcut already in use by: 捷键已被:&使用 - + Clear 清除 - + Restore default 恢復常用认值 @@ -7673,56 +9699,92 @@ Fine-tune individual colours in the grid afterwards. 发行日期 + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts 恢復所有常用快键 - + Do you really want to restore all default shortcuts? 确定要恢復所有常用快键吗? - + Clear all default shortcuts 清除所有常用快键 - + Do you really want to clear all shortcuts? 确认清除所有快键吗? - + Section: 部分: - + Action: 行动: - + Shortcut: 快键: - + + Shortcut editor + + + + How to set custom shortcuts 如何设置自定快键 - + Clear all shortcuts 清除所有快键 - + Search by shortcut name 按快键名称搜寻 @@ -7778,12 +9840,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -7791,27 +9853,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds 启用&声效 - + Current sounds theme: 现前声效主题: - + Test system sound engine 测试系统声效引擎 - + Sound settings 声效设置 - + Master volume 主音量 @@ -7819,48 +9881,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended 预览季节已结束 - + Deleting spoiler.xml. Please run Oracle 现在正删除spoiler.xml。请啟动Oracle - - + + Spoilers download failed 预览下载出错 - + No internet connection 没有网络连接 - + Error 出现错误 - + Spoilers already up to date 预览已是最新 - + No new spoilers added 没有添加新预览 - + Spoilers have been updated! 预览已被更新 - + Last change: 最后变更: @@ -7868,157 +9930,170 @@ Please check your shortcut settings! StableReleaseChannel - + Default 常用 - + No reply received from the release update server. 从发佈更新伺服器收不到有效的回覆. - + Invalid reply received from the release update server. 从发佈更新伺服器收不到有效的回覆. - + No reply received from the tag update server. 未收到标籤更新伺服器的回应. - + Invalid reply received from the tag update server. 从标籤更新伺服器收不到有效的回覆. + + StatsTally + + + Total Power + + + + + Total Toughness + + + StorageSettingsPage - - - + + + Success - + Cached card pictures have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + In-memory (currently loaded) card pictures have been reset. - + Card Picture Loader Caching Method: - + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. - + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. - + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. - + Delete Cached Images - + Delete Saved Images - + Clear In-Memory Images - + Card Picture Loader Cache Method - + Network Cache - + Filesystem - + In-Memory Picture Cache - + Network Cache Size: - + On-disk cache for downloaded pictures - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + In-memory cache for pictures not currently on screen - + Naming scheme: - + Day(s) @@ -8026,17 +10101,33 @@ Please check your shortcut settings! TabAccount - + + + My Reports + + + + Add to Buddy List - + Add to Ignore List - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account @@ -8157,118 +10248,118 @@ Please check your shortcut settings! TabArchidekt - - - + + + Desc. - - + + AND - - + + Require ALL selected colors - - + + Deck name... - - + + Owner... - - + + Packages - - + + Advanced Filters - + Bracket: - - + + Any - - + + Contains card... - - + + Commander... - - + + Tag... - - + + Deck Size - + Cards: - - + + Asc. - + Sort by: - + Filter by: - + Display Settings - - + + Search - - + + Formats @@ -8281,42 +10372,42 @@ Please check your shortcut settings! TabCardArtRules - + Card: - + ProviderId: - + Mode: - + Reason: - + Type a card name... - + Add rule - + Remove rule - + Refresh @@ -8329,52 +10420,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info 卡牌资讯 - + Deck 牌库 - + Filters 筛选项目 - + &View &视图 - + Card Database - + Printing 印刷版本 - + Visible 可见 - + Floating 浮动 - + Reset layout 重置布局 - + Deck: %1 牌库:%1 @@ -8382,49 +10473,49 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - + 卡牌信息 - - + + Deck - + &View - + Printing - + Visible - + Floating - + Reset layout @@ -8432,22 +10523,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -8455,138 +10546,236 @@ Please check your shortcut settings! TabDeckStorage - + Local file system 本地档案系统 - + Server deck storage 伺服器牌库储藏 - - + + Open in deck editor 在套牌编辑打开 - + Rename deck or folder - + Upload deck 上载牌库 - + Download deck 下载套牌 - - - - + + + + New folder 新文件夹 - - + + Delete 删除 - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder 打开牌库文件夹 - + Rename local folder - + Rename local file - + New name: - - - - + + + + + + + Error 出现错误 - + Rename failed - - + + + Invalid deck file 无效牌库档案 - + Enter deck name 输入牌库名称 - + This decklist does not have a name. Please enter a name: 本牌库列表未有名称.请输入名称: - + Unnamed deck 未命名牌库 - + Failed to upload deck to server 牌库上载於伺服器时失败 - + Delete local file 删除本地档案 - + Are you sure you want to delete the selected files? 你确定要删除被选档案(眾)吗 ? - + Delete remote decks 删除伺服器上的牌库 - + Are you sure you want to delete the selected decks? 你确定要剷除所有被选的牌库吗? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: 新建文件夹名称: - + Deck Storage @@ -8594,20 +10783,271 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -8620,27 +11060,27 @@ Please enter a name: TabEdhRecMain - + &Cards - + Top Commanders - + Tags - + Search for a card ... - + Search @@ -8653,191 +11093,207 @@ Please enter a name: TabGame - - - + + + Replay 游戏录像重温 - - + + Game 游戏 - - + + Player List 玩家列表 - - + + + Invite + + + + + Card Info 卡牌资讯 - - + + Messages 信息 - - + + Replay Timeline 录像时间线 - + &Phases 阶段 - + &Game 游戏 - + Next &phase 下个&阶段 - + Next phase with &action 下个阶段&行动 - + Next &turn 下个&回合 - + Reverse turn order 逆转回合次序 - + &Remove all local arrows &移除所有自设箭头 - + Rotate View Cl&ockwise &顺时针旋转视角 - + Rotate View Co&unterclockwise &逆时针旋转视角 - + Game &information 游戏&资讯 - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede 撤销&投降 - - - + + + &Concede &投降 - + &Leave game &离开游戏 - + C&lose replay &关闭游戏录像 - + &Focus Chat &聚焦聊天 - + &Say: &説: - + Selected cards - + &View &视图 - + Visible 可见 - + Floating 浮动 - + Reset layout 重置布局 - + Concede 投降 - + Are you sure you want to concede this game? 你确定要宣佈投降吗? - + Unconcede 撤销投降 - + You have already conceded. Do you want to return to this game? 你已经宣佈投降,你想回到这场游戏吗? - + Leave game 离开游戏 - + Are you sure you want to leave this game? 你确定要离开这个游戏吗? - + A player has joined game #%1 有玩家加入游戏 #%1 - + %1 has joined the game %1 已经加入游戏。 - + You have been kicked out of the game. 你已被踢出游戏。 @@ -8853,158 +11309,158 @@ Please enter a name: TabLog - + Logs 记录 - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName 时间;发送者姓名;发送者IP;信息;目标ID;目标姓名 - + Room Logs 房间记录 - + Game Logs 游戏记录 - + Chat Logs 聊天记录 - - + + Error 出现错误 - + You must select at least one filter. 您必须选择至少一个过滤项. - + You have to select a valid number of days to locate. 您必须选择一个可用的日数来作为定位. - + Username: 用户名称: - + IP Address: IP地址 - + Game Name: 游戏名称: - + GameID: 游戏ID: - + Message: 信息: - + Main Room 主房 - + Game Room 游戏房间 - + Private Chat 私人聊天 - + Past X Days: 过去 X 天: - + Today 今天 - + Last Hour 1小时前 - + Maximum Results: 最多结果: - + At least one filter is required. The more information you put in, the more specific your results will be. 至少需要1个筛选项。 您输入的信息越多,找到结果越準确。 - + Get User Logs 获取用户记录 - + Clear Filters 清除筛选项目 - + Filters 筛选项目 - + Log Locations 记录位置 - + Date Range 日期范围 - + Maximum Results 最多结果 - - + + Message History 信息历史 - + Failed to collect message history information. 不能获取信息历史消息 - + There are no messages for the selected filters. 没有符合筛选条件的信息。 @@ -9012,218 +11468,558 @@ The more information you put in, the more specific your results will be. TabMessage - + Private &chat 私人&聊天 - + &Leave &离开 - + %1 - Private chat %1 - 私人聊天 - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. 你已被这用户屏蔽, 它们不能再主聊天室看见你的信息,你亦不能参加它们的游戏. - + Private message from 私人信息信到, 来自 - + %1 has left the server. %1 已离开伺服器. - + %1 has joined the server. %1 已加入伺服器. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system 本地档案系统 - + Server replay storage 伺服器录像仓库 - - + + Watch replay 观看游戏录像 - + Rename 改名 - - + + New folder 新文件夹 - - + + Delete 删除 - + Open replays folder 打开录像文件夹 - + Download replay 下载游戏录像 - + Toggle expiration lock 过期锁定 - + Get replay share code - - + + Look up replay by share code - + Rename local folder 本地档案夹改名 - + Rename local file 本地档案改名 - + New name: 新名称 - + Error 出错 - + Rename failed 改名不成功 - + Name of new folder: 新建文件夹名称: - + Delete local file 删除本地档案 - + Are you sure you want to delete the selected files? 你确定要剷除所有被选的档案吗? - + Are you sure you want to delete the selected replays? 你确定要删除被选录像档案吗 ? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay 删除伺服器录像 @@ -9233,65 +12029,478 @@ The more information you put in, the more specific your results will be. + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - - Friends - - - - - Online - - - - - Ignored - - - - + &Say: &説: - + Chat 聊天 - + &Room &房间 - + &Leave room &离开房间 - + &Clear chat &清除聊天记录 - + Chat Settings... 聊天设置... - + mentioned you. 提到了你。 - + Click to view 点击查看 - + You are flooding the chat. Please wait a couple of seconds. 你正在刷屏。请稍等几秒鐘。 @@ -9299,35 +12508,35 @@ The more information you put in, the more specific your results will be. TabServer - + Server 伺服器 - - - - + + + + Error 出现错误 - + Failed to join the server room: it doesn't exist on the server. 加入伺服器房间失败:在本伺服器上这房间并不存在。 - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. 伺服器认为你在房间内, 但Cockatrice无法显示。请重新启动Cockatrice。 - + You do not have the required permission to join this server room. 你未被允许进入该房间。 - + Failed to join the server room due to an unknown error: %1. 因为以下不知名的错误,所以不能参入伺服器房间: %1. @@ -9335,97 +12544,173 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - + Archidekt - + Home - + &Visual Deck Storage - + Visual Database Display - + Server - + Account - + Deck Storage - + Game Replays - + Administration - + Logs - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? 你确定吗? - + There are still open games. Are you sure you want to quit? 游戏还在继续, 你确定要退出吗? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view 点击查看 - + Your buddy %1 has signed on! 你的好友%1已登录! - + Unknown Event 不知名事件 - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -9436,38 +12721,38 @@ To update your client, go to Help -> Check for Updates. 要升级你的客户端,请点击“帮助”->“检查更新” - + Idle Timeout 閒置超时 - + You are about to be logged out due to inactivity. 你即将因为长时间没有活动而被退出。 - + Promotion 升级 - + You have been promoted. Please log out and back in for changes to take effect. 你已被升级.请登出然后再登入让本更改生效. - + Warned 被警告 - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. 你由於$1 原因,现在接受警告. 请避免继续本行为, 否则你将有可能会接受进一步的警诫或惩罚. 若有任何疑问, 请私信我们门下的版主. - + You have received the following message from the server. (custom messages like these could be untranslated) 你收到松本伺服器发出的以下讯息(类似本信息的自定讯息可能未经翻译) @@ -9486,6 +12771,34 @@ Please refrain from engaging in this activity or further actions may be taken ag + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -9500,6 +12813,65 @@ Please refrain from engaging in this activity or further actions may be taken ag 不能分析本牌库. + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -9571,57 +12943,52 @@ Please refrain from engaging in this activity or further actions may be taken ag UserCardArtSettingsDialog - + Card Art Settings - + Type a card name... - + Card name: - - Card ProviderId: + + Printing: - + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + Left margin (%): - + Right margin (%): - - Vertical offset: - - - - - Zoom: - - - - - Parameters - - - - - Preview - - - - + Remove Banner Card @@ -9629,216 +12996,266 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details 用户&详细信息 - + Private &chat 私人&聊天 - + Show this user's &games 显示这个用户的&游戏 - + + View this user's &public decks + + + + Add to &buddy list 加入到&好友列表 - + Remove from &buddy list 从&好友列表移除 - + Add to &ignore list 加入到&屏蔽列表 - + Remove from &ignore list 从&屏蔽列表移除 - + Kick from &game 从&游戏中踢出 - + + Report user + + + + Warn user 警告用户 - + View user's war&n history 查看用户警告记录 - + Ban from &server 把用户从&伺服器禁用 - + View user's &ban history 查看用户&禁止记录 - + &Promote user to moderator 升$用户作为版主 - + Dem&ote user from moderator 把&用户从版主降级 - + Promote user to &judge 把&用户升级成裁判 - + Demote user from judge 把&用户从裁判降级 - + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes 阅读管理笔记 - - + Investigate user + + + + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games %1 的游戏(眾) - - - + + + Ban History 禁用历史 - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason 禁止时间;版主;禁止时长;禁止理由;可见理由 - + User has never been banned. 用户从未被禁 - + Failed to collect ban information. 不能获取禁用讯息 - - - + + + Warning History 警告历史 - + Warning Time;Moderator;User Name;Reason 警告时间;版主;用户名称;理由 - + User has never been warned. 用户从未接收警告 - + Failed to collect warning information. 不能获取警告信息 - + Failed to get admin notes. 不能获取管理笔记 - - + + Success 成功 - + Successfully promoted user. 用户升级成功 - + Successfully demoted user. 用户降级成功 - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + Kick Player - + Are you sure you want to kick this player from the game? - - - + + + Failed 失败 - + Failed to promote user. 用户升级失败 - + Failed to demote user. 用户降级失败 - + Copy hash to clipboard 把哈希覆印到用户剪贴 - + Remove this user's messages 移除这用户讯息 @@ -9876,61 +13293,61 @@ Please refrain from engaging in this activity or further actions may be taken ag 管理员 - + Moderator 版主 - + Registered user 登记用户 - - + + Unregistered user 未登记用户 - + Judge 裁判 - + Unknown 不知名 - + The entered password does not match your account. 输入密码跟你户口不匹配 - - - - + + + + Information 资讯 - + User information updated. 用户资讯已更新。 - - - - - - + + + + + - - - + + + + Error 出现错误 @@ -9954,283 +13371,292 @@ Please refrain from engaging in this activity or further actions may be taken ag Edit Banner Card + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) %n年(眾) , - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format %10%n日(眾) %20 - + Enter Password 输入密码 - + Password verification is required in order to change your email address 要核实密码才能更改你的电邮地址 - - - + + + An error occurred while trying to update your user information. 在尝试更新你的用户信息时出现错误. - + The selected card is blacklisted on this server or another error occurred. - + Banner card removed. - + Banner card updated. - + This server does not permit you to update your user informations. 本伺服器不容许你更新你的用户资讯. - + Password changed. 密码已更改 - + This server does not permit you to change your password. 本伺服器不容许你更改密码 - + The new password is too short. 新的密码太短 - + The old password is incorrect. 旧的密码不正确 - + Avatar updated. 头像已更新。 - + This server does not permit you to update your avatar. 本伺服器不容许你更新你的头像. - + An error occured while trying to update your avatar. - - - This server does not permit you to update your user informations. - An error occured while trying to updater your avatar. - 本伺服器不容许你更新你的用户资讯. - UserInfoPopup - - + + Games - + Chat - + Open private chat - + Profile - + View user profile - + Show this user's games - + + Invite + + + + + Invite to your game + + + + − Buddy - + Remove from buddy list - + + Buddy - + Add to buddy list - + − Ignore - + Remove from ignore list - + + Ignore - + Add to ignore list - + Ban - + Ban from server - + Warn - + Warn user - + Ban log - + View ban history - + Warn log - + View warning history - + Notes - + View admin notes - + − Mod - + Demote from moderator - + + Mod - + Promote to moderator - + − Judge - + Demote from judge - + + Judge - + Promote to judge - + Join game - + Spectate - - + + Loading games… - + Could not load games. - + No active games. @@ -10238,223 +13664,344 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings 一般通用接口设置 - + &Double-click cards to play them (instead of single-click) 双点击使出卡牌 (而不是单点击使出) - + &Clicking plays all selected cards (instead of just the clicked card) &单一点击把所有选择的卡牌使出(不只是被点击的单张卡牌) - + &Play all nonlands onto the stack (not the battlefield) by default &把所有使出的非地牌放入堆迭区(不是战场) 作为常用设置 - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens 在令牌上标註牌中文字 - + Show selection count during drag selection - + Show total selection count - - Show subtype breakdown in selection tally + + + Disabled - + + + Enabled + + + + + + Automatic + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + Use tear-off menus, allowing right click menus to persist on screen 使用浮动菜单,允许右键单击, 把菜单保留在屏幕上 - + Keep game chat focused when clicking in game (Note: disables card view search bar) - + Notifications settings 通告设置 - + Enable notifications in taskbar 开启任务栏通告 - + Notify in the taskbar for game events while you are spectating 观看时在任务栏提示游戏信息 - + Notify in the taskbar when users in your buddy list connect 在任务栏中提示好友连到伺服器 - + Animation settings 动画设定 - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation &横置/重置 动画 - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings - + Open deck in new tab by default 把用新页开啟牌库作为常用设置 - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - - do nothing + + Do nothing - - ask to convert to .cod + + Ask to convert to .cod - - always convert to .cod + + Always convert to .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings 重播设置 - + Buffer time for backwards skip via shortcut: 用速键啟动向后跳的缓衝时间: + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + Flip coin - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -10491,22 +14038,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... - + Save Filter - + Save all currently applied filters to a file - + Enter filename... @@ -10514,72 +14061,72 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterToolbarWidget - + Sort by - + Filter by - + Save and load filters - + Filter by exact card name - + Filter by card main-type - + Filter by card sub-type - + Filter by set - + Filter by format legality - + Save/Load - + Name - + Main Type - + Sub Type - + Sets - + Formats @@ -10681,7 +14228,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -10689,19 +14236,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -10747,28 +14294,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - - + + Visual - + Loading database ... - + Clear all filters - + Table @@ -10776,43 +14323,43 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat @@ -10828,12 +14375,12 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -10841,17 +14388,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -10859,7 +14406,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -10867,52 +14414,57 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + Show Color Identity - + Show Tags On Deck Previews - + + Show Upload Time + + + + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -10920,7 +14472,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -10928,22 +14480,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -10951,17 +14503,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + + Select decks to share + + + + Visual Deck Storage Settings @@ -10969,177 +14526,202 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? 你想发出那种警告? - + Redact all messages from this user in all rooms 把这用户在所有房间留下的讯息删除 - + &OK &确认 - + &Cancel &取消 - + Warn user for misconduct 对行为不当的用户发出警告 - - + + Error 出现错误 - + User name to send a warning to can not be blank, please specify a user to warn. 接收警告的用户名称不能空白.请定好要接收警告的用户名字. - + Warning to use can not be blank, please select a valid warning to send. 警告名称不能留空. 请选择有效的警告. + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top 将选择的牌组送到最顶 - + Move selected set up 将选择的牌组往上一格 - + Move selected set down 将选择的牌组往下一格 - + Move selected set to the bottom 将选择的牌组送到最底 - - Search by set name, code, or type - 用排组名称,代码,或类型进行搜寻 - - - + Default order 常用次序 - + Restore original art priority order 恢復常用艺术先后次序 - + Enable all sets 啟用所有牌组 - + Disable all sets 禁用所有卡组 - + Enable selected set(s) 啟用选择的牌组(眾)  - + Disable selected set(s) 停用选择的牌组(眾) - + Deck Editor 牌库编辑 - + Use CTRL+A to select all sets in the view. 请按 CTRL + A, 以此选择所有看到的牌组 - + Only cards in enabled sets will appear in the card list of the deck editor. 只有已经被啟用的卡牌组中的卡牌,才会在牌库编辑中的卡牌清单出现 - + Image priority is decided in the following order: 卡牌图片先后次序由以下次序决定: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki 自订档案夹(%`1)优先, 此对话窗中已被啟用的牌组为后(由顶到底) - + Include cards rebalanced for Alchemy [requires restart] - + Card Art 卡牌艺术 - + + Search by set name, code, type, or release date + + + + How to use custom card art 如何使用自定卡牌图片 - + Hints 提示 - + Note 笔记 - + Sorting by column allows you to find a set while not changing set priority. 按列排序允许您在不更改系列优先级别的情况下查找牌组。 - + To enable ordering again, click the column header until this message disappears. 要再次启用排序,请单击列标题,直到此消息消失。 - + Use the current sorting as the set priority instead 用现在排序代替牌组先后 - + Sorts the set priority using the same column 用同一列分牌组先后 - + Manage sets 管理牌组 @@ -11147,93 +14729,85 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped 未分组 - + Group by Type 按类型分组 - + Group by Mana Value 按法力数值分组 - + Group by Color 按顏色排序 - + Unsorted 未排序 - + Sort by Name 按名称排次序 - + Sort by Type 按类型排序 - + Sort by Mana Cost 按法力费用排序 - + Sort by Colors 按顏色(眾) 排序 - + Sort by P/T 按力量/防御力排序 - + Sort by Set 按排组排序 - + shuffle when closing 关闭界面时洗牌 - + pile view 栋迭观看 - - i18n - - - English - 简体中文 (Chinese Simplified) - - main - + Connect on startup 啟动时自动连接 - + Debug to file 侦错纪录写进档案 @@ -12017,6 +15591,16 @@ Please refrain from engaging in this activity or further actions may be taken ag Graveyard 坟场 + + + Report Queue + + + + + Moderation + + diff --git a/cockatrice/translations/cockatrice_zh-Hant.ts b/cockatrice/translations/cockatrice_zh-Hant.ts index 2ce4cbd53..0dad917b0 100644 --- a/cockatrice/translations/cockatrice_zh-Hant.ts +++ b/cockatrice/translations/cockatrice_zh-Hant.ts @@ -2,7 +2,7 @@ AbstractCounter - + &Set counter... 設置指示物為... @@ -10,12 +10,12 @@ AbstractCounterDialog - + Set counter 設置指示物 - + New value for counter '%1': 新的指示物數值'%1': @@ -36,64 +36,113 @@ AbstractTabDeckEditor - + Open in new tab - + Are you sure? - + The decklist has been modified. Do you want to save the changes? - - - - - - + + + + + + Error - + Could not open deck at %1 - + Could not save remote deck - - + + The deck could not be saved. Please check that the directory is writable and try again. - + Save deck - + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + The deck is empty. Add cards before sharing it. + + + + The deck could not be saved. - + There are no cards in your deck to be exported + + AccountSetupPage + + + Join a Server + + + + + Optional — you can always do this later from the menu. + + + + + Playing online needs a server account. + + + + + Register a new account… + + + + + I already have one — Connect… + + + + + Just want to play locally? Skip this and connect whenever you're ready. + + + AddAnalyticsPanelDialog @@ -105,12 +154,12 @@ Please check that the directory is writable and try again. AdminNotesDialog - + Update Notes - + Admin Notes for %1 @@ -136,218 +185,304 @@ Please check that the directory is writable and try again. AppearanceSettingsPage - + seconds - + Error 錯誤 - + Could not create themes directory at '%1'. - + Theme settings 主題設定 - + Current theme: 當前主題 - + Open themes folder 打開主題文件夾 - + Home tab background source: - + Home tab background shuffle frequency: - + Disabled - + Menu settings - + Show keyboard shortcuts in right-click menus - + Show game filter toolbar above list in room tab - + Card rendering 牌面 - + Display card names on cards having a picture 顯示有圖卡牌的名稱 - + Auto-Rotate cards with sideways layout - + Override all card art with personal set preference (Pre-ProviderID change behavior) - + Light - + Dark - + + System - + + Show all playmats + + + + + Show own playmat only + + + + + Don't use playmats + + + + + Override deck playmat + + + + + Fallback if deck has none + + + + + Deck only, ignore collection + + + + Active theme palette: - + + Active theme style: + + + + + Qt widget style saved to this theme ("System" lets the application decide) + + + + Edit theme palette - + Home tab settings - + Display card name of background in bottom right - + + Dim the home tab background + + + + + Draw a translucent overlay over the home tab background so buttons and text stand out + + + + + Home tab button color: + + + + + Use the theme's identity accent colors, or extract colors from the background image + + + + + Playmat settings + + + + + Playmat visibility: + + + + + Default collection behavior: + + + + + Default playmat collection: + + + + + Edit... + + + + Styling settings - + Style user list - + Card printings - + Bump sets that the deck contains cards from to the top in the printing selector - + Scale cards on mouse over 卡牌隨指針縮放 - + Use rounded card corners - + Card layout - + Minimum overlap percentage of cards on the stack and in vertical hand - + Maximum initial height for card view window: - - + + rows - + Maximum expanded height for card view window: - + Card counters - + Counter %1 - + Hand layout 手牌區域佈局 - + Display hand horizontally (wastes space) 水平顯示手牌區域 (浪費空間) - + Enable left justification 開啓左對齊 - + Table grid layout 表格佈局 - + Invert vertical coordinate 反轉垂直坐標 - + Minimum player count for multi-column layout: 界面佈局之內能夠容納的玩家欄數量: - + Maximum font size for information displayed on cards: 卡牌上顯示的最大字號 @@ -355,12 +490,12 @@ Please check that the directory is writable and try again. ArchidektApiResponseDeckDisplayWidget - + Back to results - + Open Deck in Deck Editor @@ -386,112 +521,112 @@ Please check that the directory is writable and try again. BanDialog - + ban &user name 禁止該用戶 - + ban &IP address 禁止該IP地址 - + ban client I&D 禁止該客户端 - + Ban type 禁止類型 - + &permanent ban 永久禁止 - + &temporary ban 暫時禁止 - + &Days: 天: - + &Hours: 小時: - + &Minutes: 分鐘: - + Duration of the ban 持續時長 - + Please enter the reason for the ban. This is only saved for moderators and cannot be seen by the banned person. 請輸入禁止原因. 此項為管理緣由而保存並且對於這位被禁止的用戶不可見. - + Please enter the reason for the ban that will be visible to the banned person. 請輸入對於被禁止用戶可見的禁止原因 - + Redact all messages from this user in all rooms 修改該用戶在所有房間中的所有消息 - + &OK 確認 - + &Cancel 取消 - + Ban user from server 禁止此用戶 - - - - + + + + Error 錯誤 - + You have to select a name-based, IP-based, clientId based, or some combination of the three to place a ban. 你必須選擇姓名、IP、客户端或任意多選組合來執行禁止操作。 - + You must have a value in the name ban when selecting the name ban checkbox. 在禁用姓名時必須填寫一個姓名。 - + You must have a value in the ip ban when selecting the ip ban checkbox. 在禁用IP時必須填寫一個IP。 - + You must have a value in the clientid ban when selecting the clientid ban checkbox. 在禁用客戶端ID時必須填寫一個客戶端ID。 @@ -499,22 +634,22 @@ This is only saved for moderators and cannot be seen by the banned person. BetaReleaseChannel - + Beta - + No reply received from the release update server. 未收到更新伺服器的回應。 - + Invalid reply received from the release update server. 收到更新伺服器的無效回應 - + No reply received from the file update server. 未收到文件更新伺服器的回應 @@ -522,7 +657,20 @@ This is only saved for moderators and cannot be seen by the banned person. CardArtPreviewWidget - + + Banner preview + + + + + + + + Zoom %1× + + + + No card selected @@ -530,22 +678,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardArtRulesModel - + Card - + ProviderId - + Mode - + Reason @@ -553,65 +701,209 @@ This is only saved for moderators and cannot be seen by the banned person. CardDatabaseModel - + Name 名稱 - + Sets 系列 - + Mana cost 魔法力費用 - + Card type 卡牌類別 - + P/T 力量/防禦力 - + Color(s) 顏色 + + CardDatabaseSetupPage + + + + Press Download to fetch the card database, or Skip to do it later. + + + + + Downloading the card database (%1%)… + + + + + Parsing the card database (%1%)… + + + + + Importing cards (%1%)… + + + + + Download + + + + + ▼ Advanced: custom download source + + + + + ▶ Advanced: custom download source + + + + + Invalid URL + + + + + That doesn't look like a valid URL. Double-check it and try again, or clear the field to use the default source. + + + + + Downloading the latest card database… + + + + + Card database ready ✓ + + + + + Couldn't download the card database automatically. Check your connection and retry, set it up manually, or skip this for now — you can do it later from the Card Database menu. + + + + + Card Database + + + + + Cockatrice needs card data to know what you're playing with. + + + + + Retry + + + + + Set up manually… + + + + + Leave blank to use the default source + + + + + Only change this if you know you need a mirror or a custom card data source. + + + + + Restore default + + + + + Apply && retry + + + + + Check for card database updates on startup + + + + + Don't check + + + + + Prompt for update + + + + + Always update in the background + + + + + Check for card database updates every + + + + + days + + + CardDatabaseView - + Add to Deck - + Add to Sideboard - + Select Printing - + + Add to Zone + + + + + Create &new zone... + + + + Show on EDHRec (Commander) - + Show on EDHRec (Card) - + Show Related cards @@ -721,22 +1013,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoFrameWidget - + Image - + Description - + Both - + View transformation @@ -744,22 +1036,22 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoPictureWidget - + View related cards - + Add card to deck - + Mainboard - + Sideboard @@ -767,27 +1059,27 @@ This is only saved for moderators and cannot be seen by the banned person. CardInfoTextWidget - + Name: - + Set: - + Collector Number: - + Related cards: - + Unknown card: @@ -795,129 +1087,129 @@ This is only saved for moderators and cannot be seen by the banned person. CardMenu - + Re&veal to... - + &All players - + View related cards - + Token: - + All tokens - + &Select All - + S&elect Row - + S&elect Column - + &Play - + &Hide - + Play &Face Down - + &Tap / Untap Turn sideways or back again - + Skip &untapping - + T&urn Over Turn face up/face down - + &Peek at card face - + &Clone - + Attac&h to card... - + Unattac&h - + &Draw arrow... - + &Set annotation... - + Reduce life by power - + Ca&rd counters - + &Add counter (%1) - + &Remove counter (%1) - + &Set counters (%1)... @@ -938,7 +1230,7 @@ This is only saved for moderators and cannot be seen by the banned person. CardSizeWidget - + Card Size @@ -1078,6 +1370,24 @@ This is only saved for moderators and cannot be seen by the banned person. + + ChatView + + + Join game "%1" (#%2) on %3 + + + + + Join game #%1 on %2 + + + + + Join game + + + CockatriceXml3Parser @@ -1107,280 +1417,382 @@ This is only saved for moderators and cannot be seen by the banned person. + + CommanderBracketNames + + + CommanderSpellbook + + + + + Official (approximate) + + + + + The bracket system combines both objective data, as well as subjective play experience to estimate a bracket for a deck. +CommanderSpellbook's estimation is algorithmical, which means that it can only operate on the objective data, not the subjective intent. +They have chosen to represent this by defining their own bracket system which matches their algorithm. +This custom bracket system maps loosely to the standard system. +You may choose to use these mapped standardized names if these are more familiar to you, however, you should keep in mind that these are just rough estimations. + +Always consider the subjective factors of the bracket system when determing a deck's final bracket! + + + + + CommanderBracketWidget + + + + Bracket: + + + + + CommanderSpellbook integration + + + + + CommanderSpellbook can analyze your deck and estimate its Commander bracket. + +This sends your deck list to an external service. + +CommanderSpellbook uses its own bracket naming system based on their own algorithm. These names can be mapped to the official Commander brackets, but the mapping is only an approximation. + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + + Bracket naming: + + + + + Enable + + + + + Automatic + + + + + Disable + + + + + Calculating… + + + + + Why this bracket? + + + + + Recalculate bracket + + + ConnectionController - - + + The server has reached its maximum user capacity, please check back later. - + There are too many concurrent connections from your address. - + Banned by moderator - + Expected end time: %1 - + This ban lasts indefinitely. - + Scheduled server shutdown. - - + + Invalid username. - + You have been logged out due to logging in at another location. - + Connection closed - + The server has terminated your connection. Reason: %1 - + The server is going to be restarted in %n minute(s). All running games will be lost. Reason for shutdown: %1 - + Scheduled server shutdown - + Failed Login - + Your client seems to be missing features this server requires for connection. - + To update your client, go to 'Help -> Check for Client Updates'. - - - - - - - - - - - + + + + + + + + + + - - + + + Error - + Incorrect username or password. Please check your authentication information and try again. - + There is already an active session using this user name. Please close that session first and re-login. - - + + You are banned until %1. - - + + You are banned indefinitely. - + This server requires user registration. Do you want to register now? - + This server requires client IDs. Your client is either failing to generate an ID or you are running a modified client. Please close and reopen your client to try again. - + An internal error has occurred, please close and reopen Cockatrice before trying again. If the error persists, ensure you are running the latest version of the software and if needed contact the software developers. - + Account activation - + Your account has not been activated yet. You need to provide the activation token received in the activation email. - + + Password Change Required + + + + + An administrator has reset your password. Please contact your server administrator to obtain your temporary password, then log in and change it via Account -> Change Password. + + + + Server Full - + Unknown login error: %1 - - + + This usually means that your client version is out of date, and the server sent a reply your client doesn't understand. - - - - + + + + Registration denied - + Registration is currently disabled on this server - + There is already an existing account with the same user name. - + It's mandatory to specify a valid email address when registering. - + It appears you are attempting to register a new account on this server yet you already have an account registered with the email provided. This server restricts the number of accounts a user can register per address. Please contact the server operator for further assistance or to obtain your credential information. - + Password too short. - + Registration failed for a technical problem on the server. - + The connection to the server has been lost. - + Unknown registration error: %1 - + Account activation failed - + Socket error: %1 - + Server timeout - + You are trying to connect to an obsolete server. Please downgrade your Cockatrice version or connect to a suitable server. Local version is %1, remote version is %2. - + Your Cockatrice client is obsolete. Please update your Cockatrice version. Local version is %1, remote version is %2. - - + + Success - + Registration accepted. Will now login. - + Account activation accepted. Will now login. - + Information - + This server supports additional features that your client doesn't have. This is most likely not a problem, but this message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -1388,113 +1800,113 @@ To update your client, go to Help -> Check for Updates. - - - + + + Reset Password - + Your password has been reset successfully, you can now log in using the new credentials. - + Failed to reset user account password, please contact the server operator to reset your password. - + Activation request received, please check your email for an activation token. - + Connecting to %1... - + Registering to %1 as %2... - + Disconnected - + Connected, logging in at %1 - + Requesting forgotten password to %1 as %2... - + Your username must respect these rules: - + is %1 - %2 characters long - + can %1 contain lowercase characters - - - - + + + + NOT - + can %1 contain uppercase characters - + can %1 contain numeric characters - + can contain the following punctuation: %1 - + first character can %1 be a punctuation mark - + no unacceptable language as specified by these server rules: note that the following lines will not be translated - + can not contain any of the following words: %1 - + can not match any of the following expressions: %1 - + You may only use A-Z, a-z, 0-9, _, ., and - in your username. @@ -1544,7 +1956,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorCardDatabaseDockWidget - + Card Database @@ -1560,17 +1972,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorDatabaseDisplayWidget - + Search by card name (or search expressions) - + Add card to &maindeck - + Add card to &sideboard @@ -1578,97 +1990,158 @@ To update your client, go to Help -> Check for Updates. DeckEditorDeckDockWidget - + Loading Database... - + Banner Card - + Main Type - + Mana Cost - + Colors - + + Playmat + + + + + + Edit Playmat... + + + + + Edit Playmat (%1) + + + + + &Rename zone... + + + + + Change &board + + + + + &Delete zone + + + + + Move or remove all cards first. + + + + + Delete zone + + + + + Delete the zone "%1"? + + + + Select Printing - + + Move to &zone + + + + + Create new zone and move &here... + + + + + Create &new zone... + + + + Deck - + Deck &name: - + Banner Card/Tags Visibility Settings - + Show banner card selection menu - + Show tags selection menu - + &Comments: - + Group by: - + Format: - + Hash: - + &Increment number - + &Decrement number - + &Remove row - + Swap card to/from sideboard @@ -1676,17 +2149,17 @@ To update your client, go to Help -> Check for Updates. DeckEditorFilterDockWidget - + Filters - + &Clear all filters - + Delete selected @@ -1694,114 +2167,119 @@ To update your client, go to Help -> Check for Updates. DeckEditorMenu - + &Deck Editor - + &New deck - + &Load deck... - + Load recent deck... - + Clear - + &Save deck - + Save deck &as... - + + Share deck... + + + + Load deck from cl&ipboard... - + Edit deck in clipboard - - + + Annotated - - + + Not Annotated - + Save deck to clipboard - + Annotated (No set info) - + Not Annotated (No set info) - + &Print deck... - + Load deck from online service... - + &Send deck to online service - + Create decklist (decklist.org) - + Create decklist (decklist.xyz) - + Analyze deck (deckstats.net) - + Analyze deck (tappedout.net) - + &Close @@ -1809,7 +2287,7 @@ To update your client, go to Help -> Check for Updates. DeckEditorPrintingSelectorDockWidget - + Printing Selector @@ -1817,110 +2295,152 @@ To update your client, go to Help -> Check for Updates. DeckEditorSettingsPage - - + + Update Spoilers 更新預覽 - + Success 成功 - + Download URLs have been reset. 下載URL已重置。 - + Add URL 添加URL - - + + URL: URL: - - + + Edit URL 編輯URL - + Add New URL 添加新的URL - + Remove URL 移除URL - + Updating... 更新中... - + + %1 (unlimited) + + + + + %1 (%2/s) + + + + + + + Adjust Rate Limit + + + + + Select a URL in the list first. + + + + + The selected URL does not have a valid host. + + + + + Requests per second (0 = unlimited, fastest; up to %1): + + + + + Requests per second (developer maximum is %1): + + + + + Adjust Rate Limit for %1 + + + + Choose path 選擇路徑 - + URL Download Priority URL下載優先級 - + Spoilers 預覽 - + Download Spoilers Automatically 自動下載預覽 - + Spoiler Location: 預覽位置: - + Last Change 最近的變更 - + Spoilers download automatically on launch 自動下載預覽運行 - + Press the button to manually update without relaunching 按下按鈕手動更新而不重新啓動 - + Do not close settings until manual update is complete 手動更新完成之前,請勿關閉設置 - + Download card pictures on the fly 即時下載卡牌圖片 - + How to add a custom URL 如何添加自定義URL - + Reset Download URLs 重置用於下載的URL @@ -1928,32 +2448,32 @@ To update your client, go to Help -> Check for Updates. DeckListHistoryManagerWidget - + Undo - + Redo - + Undo/Redo history - + Click on an entry to revert to that point in the history. - + [redo] - + [undo] @@ -1961,27 +2481,27 @@ To update your client, go to Help -> Check for Updates. DeckListModel - + Count - + Set - + Number 數量 - + Provider ID - + Card 卡牌 @@ -2002,17 +2522,22 @@ To update your client, go to Help -> Check for Updates. DeckPreviewColorIdentityFilterWidget - + Mode: Exact Match - + Mode: Includes - + + Mode: Excludes + + + + Color identity filter mode (AND/OR/NOT conjunctions of filters) @@ -2020,7 +2545,7 @@ To update your client, go to Help -> Check for Updates. DeckPreviewDeckTagsDisplayWidget - + Edit tags ... @@ -2028,62 +2553,62 @@ To update your client, go to Help -> Check for Updates. DeckPreviewTagDialog - + Deck Tags Manager - + Manage your deck tags. Check or uncheck tags as needed, or add new ones: - + Add a new tag (e.g., Aggro️) - + Add Tag - + Filter tags... - + OK - + Edit default tags - + Cancel - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2091,94 +2616,109 @@ To update your client, go to Help -> Check for Updates. DeckPreviewWidget - + Banner Card - + Open in deck editor - + + Share deck... + + + + Edit Tags - + Rename Deck - + Save Deck to Clipboard - + Annotated - + Annotated (No set info) - + Not Annotated - + Not Annotated (No set info) - + Rename File - + Delete File - + Set Banner Card - - + + New name: - - + + Rename deck + + + + + Rename file + + + + + Error - + Rename failed - + Delete file - + Are you sure you want to delete the selected file? - + Delete failed @@ -2186,57 +2726,102 @@ To update your client, go to Help -> Check for Updates. DeckStateManager - + Rename deck to "%1" from "%2" - + Updated comments (was %1 chars, now %2 chars) - + Set banner card to %1 (%2) - + + Set playmat to %1 + + + + Tags changed - + Set format to %1 - + Added (%1): %2 (%3) %4 - + Moved to %1 1 × "%2" (%3) - + Removed "%1" (all copies) - + + Moved %1 × "%2" (%3) to %4 + + + + + Created zone "%1" in %2 + + + + + Renamed zone "%1" to "%2" + + + + + Moved zone "%1" to %2 + + + + + Deleted zone "%1" + + + + + Enter a zone name. + + + + + This name is reserved. + + + + + A zone with this name already exists. + + + + %1 1 × "%2" (%3) - + Added - + Removed @@ -2258,94 +2843,122 @@ To update your client, go to Help -> Check for Updates. DeckViewContainer - + Load deck... 讀取套牌... - + Load remote deck... 載入伺服器上的套牌... - + Load from clipboard... - + Load from website... - + Unload deck - + Ready to start 準備好開始 - + Force start - + Sideboard unlocked 解鎖備牌 - + Sideboard locked 鎖定備牌 - - + + Error 錯誤 - + The selected file could not be loaded. 此文件無法被載入 - + Deck is greater than maximum file size. - + Are you sure you want to force start? This will kick all non-ready players from the game. - + Cockatrice + + DeckZoneDialog + + + New zone + + + + + Rename zone + + + + + Zone &name: + + + + + &Parent zone: + + + + + Enter a zone name. + + + DialogConvertDeckToCodFormat - + Deck Format Conversion - + You tried to add a tag to a .txt format deck. Tags can only be added to .cod format decks. Do you want to convert the deck to the .cod format? - + Remember and automatically apply choice in the future @@ -2358,143 +2971,143 @@ This will kick all non-ready players from the game. 下載中... - + Known Hosts 已知主機: - + Delete the currently selected saved server - + Refresh the server list with known public servers 從已知的伺服器更新伺服器列表 - + New Host 新主機 - + Name: 名稱: - + &Host: 主機: - + &Port: 端口: - + Player &name: 玩家名字: - + P&assword: 密碼: - + &Save password 記住密碼 - + A&uto connect 自動連接 - + Automatically connect to the most recent login when Cockatrice opens 當Cockatrice啓動時自動連接到最近的登錄 - + If you have any trouble connecting or registering then contact the server staff for help! 如果你在連接或者註冊上遇到了任何麻煩,可以聯繫伺服器的工作人員獲得幫助。 - - + + Webpage 網頁 - + Reset Password 重設密碼 - + Forgot password? - + &Connect 連接 - + Server 伺服器 - + Login 登錄 - + Server Contact 聯繫伺服器 - + Connect to Server 連接伺服器 - + Server URL 伺服器URL - + Communication Port 通信端口 - + Unique Server Name 唯一的伺服器名稱 - + Connection Warning 連接警告 - + You need to name your new connection profile. 你需要給連接配置文件命名。 - + Connect Warning 連接警告 - + The player name can't be empty. 玩家名稱不能為空白 @@ -2502,122 +3115,122 @@ This will kick all non-ready players from the game. DlgCreateGame - + Re&member settings 記住設定 - + &Description: 描述: - + P&layers: 玩家: - + General 綜合 - + Game type 房間類型 - + &Password: 密碼: - + Only &buddies can join 只容許好友加入 - + Only &registered users can join 只容許註冊用戶加入 - + Joining restrictions 加入條件 - + &Spectators can watch 允許觀看者 - + Spectators &need a password to watch 觀看者需要密碼 - + Spectators can &chat 觀看者可以聊天 - + Spectators can see &hands 觀看者可見手牌 - + Create game as spectator 以旁觀者的身份創建遊戲 - + Spectators 觀看者 - + Starting life total: - + Open decklists in lobby - + Create game as judge - + Game setup options - + &Clear 清除 - + Create game 創建一個房間 - + Game information 房間信息 - + Error 錯誤 - + Server error. 伺服器錯誤. @@ -2625,97 +3238,97 @@ This will kick all non-ready players from the game. DlgCreateToken - + &Name: 名稱: - + Token 衍生物 - + C&olor: 顏色: - + white 白 - + blue 藍 - + black 黑 - + red 紅 - + green 綠 - + multicolor 多色 - + colorless 無色 - + &P/T: 力量/防禦力: - + &Annotation: 註釋: - + &Destroy token when it leaves the table 此衍生物離場時會被摧毀 - + Create face-down (Only hides name) - + Token data 衍生物數據資料 - + Show &all tokens 顯示所有衍生物 - + Show tokens from this &deck 顯示此套牌所使用的衍生物 - + Choose token from list 從列表中選擇衍生物 - + Create token 派出一個衍生物 @@ -2723,53 +3336,53 @@ This will kick all non-ready players from the game. DlgDefaultTagsEditor - + Edit Tags - + Add - + Confirm - + Cancel - + Enter a tag and press Enter - - + + ✖ - + Invalid Input - + Tag name cannot be empty! - + Duplicate Tag - + This tag already exists. @@ -2778,7 +3391,7 @@ This will kick all non-ready players from the game. DlgEditAvatar - + No image chosen. 未選擇圖像 @@ -2790,27 +3403,32 @@ To remove your current avatar, confirm without choosing a new image. 要清除你當前的頭像,可以不用選擇新頭像直接確認。 - + + Images are automatically downscaled to at most %1x%1 pixels. + + + + Browse... 瀏覽... - + Change avatar 改變頭像 - + Open Image 打開圖片 - + Image Files (*.png *.jpg *.bmp) 圖片文件(*.png *.jpg *.bmp格式) - + Invalid image chosen. 所選圖片不可用。 @@ -2836,38 +3454,38 @@ To remove your current avatar, confirm without choosing a new image. DlgEditPassword - + Old password: 舊密碼: - + New password: 新密碼: - + Confirm new password: 確認新密碼: - + Change password 修改密碼: - - + + Error 錯誤 - + Your password is too short. 你的密碼太短。 - + The new passwords don't match. 兩次輸入的新密碼不一致。 @@ -2875,93 +3493,93 @@ To remove your current avatar, confirm without choosing a new image. DlgEditTokens - + &Name: 名稱: - + C&olor: 顏色: - + white 白 - + blue 藍 - + black 黑 - + red 紅 - + green 綠 - + multicolor 多色 - + colorless 無色 - + &P/T: 力量/防禦力: - + &Annotation: 註釋: - + Token data 衍生物數據資料 - - + + Add token 添加衍生物 - + Remove token 刪除衍生物 - + Edit custom tokens 編輯自定義衍生物 - + Please enter the name of the token: 請輸入衍生物名稱: - + Error 錯誤 - + The chosen name conflicts with an existing card or token. Make sure to enable the 'Token' set in the "Manage sets" dialog to display them correctly. 所選擇的名字與現有的卡牌或衍生物有衝突。 @@ -3040,7 +3658,8 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - Hide games that have started + Hide started games + Hide games that have started @@ -3076,8 +3695,9 @@ Make sure to enable the 'Token' set in the "Manage sets" dia - &Creator name: - 創建者名字: + &Host name: + &Creator name: + @@ -3143,52 +3763,52 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordChallenge - + Reset Password Challenge Warning 重設密碼驗證警告 - + A problem has occurred. Please try to request a new password again. - + Enter the information of the server and the account you'd like to request a new password for. - + &Host: 主機: - + &Port: 端口: - + Player &name: 玩家名字: - + Email: 電子郵箱: - + Reset Password Challenge 重設密碼驗證 - + Reset Password Challenge Error 重設密碼驗證錯誤 - + The email address can't be empty. 電郵不能為空白 @@ -3196,37 +3816,37 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordRequest - + Enter the information of the server you'd like to request a new password for. - + &Host: 主機: - + &Port: 端口: - + Player &name: 玩家名字: - + Reset Password Request 重設密碼請求 - + Reset Password Error 重設密碼錯誤 - + The player name can't be empty. 玩家名稱不能為空白 @@ -3234,94 +3854,127 @@ Make sure to enable the 'Token' set in the "Manage sets" dia DlgForgotPasswordReset - + Reset Password Warning 重設密碼警告 - + A problem has occurred. Please try to request a new password again. - + Enter the received token and the new password in order to set your new password. - + &Host: 主機: - + &Port: 端口: - + Player &name: 玩家名字: - + Token: 驗證碼 - - + + New Password: 新密碼: - + Reset Password - + The player name can't be empty. 玩家名稱不能為空白 - - - - + + + + Reset Password Error - + The token can't be empty. 驗證碼不能為空白 - + The new password can't be empty. 新密碼不能為空白 - + Error 錯誤 - + Your password is too short. 你的密碼太短。 - + The passwords do not match. 密碼不一致 + + DlgInviteToGame + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Invite to Game + + + + + Search users... + + + + + Invite + + + + + Cancel + + + DlgLoadDeck - + Load Deck @@ -3405,165 +4058,566 @@ https://tappedout.net/mtg-decks/your-deck-name/ DlgLocalGameOptions - + Players: - + General - + Starting life total: - + Game setup options - + Remember settings - + Local game options + + DlgLoginPrompt + + + Sign in + + + + + This link requires you to be signed in. +Sign in to %1: + + + + + Username: + + + + + Password: + + + + + Save password for this server + + + DlgMoveTopCardsUntil - + Card name (or search expressions): - + Number of hits: - + Auto play hits - + Put top cards on stack until... - + No cards matching the search expression exists in the card database. Proceed anyways? - + Cockatrice - + Invalid filter + + DlgMyReports + + + My Reports + + + + + # + + + + + Time + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Report Details + + + + + Chat Log Context + + + + + Comments: + + + + + Add a comment: + + + + + + Type your comment here... + + + + + Send + + + + + Close + + + + + Refresh + + + + + + + Loading... + + + + + Failed to load reports. + + + + + %1 report(s) + + + + + This report is closed. + + + + + Failed to load report details. + + + + + No comments yet. + + + + + [Moderator] + + + + + [You] + + + DlgRegister - + + Known Hosts + + + + + Delete the currently selected saved server + + + + + Refresh the server list with known public servers + + + + + New Host + + + + + Server + + + + Enter your information and the information of the server you'd like to register to. Your email will be used to verify your account. - + &Host: 主機: - + &Port: 端口: - + Player &name: 玩家名字: - + P&assword: 密碼: - + Password (again): (再次)輸入密碼: - + Email: 電子郵箱: - + Email (again): (再次)輸入電郵: - + Country: 國家: - + Undefined 未定義 - + Real name: 真實姓名: - + Register to server 註冊到伺服器 - - - - + + Server URL + + + + + Communication Port + + + + + + + Registration Warning 註冊警告 - + Your password is too short. 你的密碼太短。 - + Your passwords do not match, please try again. 兩次輸入的密碼不一致,請重新輸入, - + Your email addresses do not match, please try again. 兩次輸入的電子郵箱不一致,請重新輸入。 - + The player name can't be empty. 玩家名稱不能為空白 + + + Downloading... + + + + + DlgReportUser + + + Report User + + + + + Reports are reviewed by moderators. False reports may result in account penalties. + + + + + Report Details + + + + + Reported User: + + + + + Game ID: + + + + + (Optional) Enter game ID if available + + + + + If the report is related to a specific game, enter its ID. + + + + + Category + + + + + Category: + + + + + Description + + + + + Please describe what happened. Include dates, game details, or any evidence if available. + + + + + Chat Log Context + + + + + No chat context available (not triggered from chat). + + + + + This chat log is captured from your local chat window and may not reflect the full conversation. + + + + + + Submit Report + + + + + Missing description + + + + + Please describe what happened before submitting. + + + + + Confirm Report + + + + + Submit report against %1 for %2? + + + + + Submitting... + + + + + Report Submitted + + + + + Your report has been submitted and will be reviewed by a moderator. Thank you. + + + + + + + Submission Failed + + + + + You have reached the daily report limit. Please try again later. + + + + + The reported user could not be found. Guests (unregistered users) cannot be reported. + + + + + Failed to submit report. Please try again. + + + + + Cheating / Unsporting behavior + + + + + Bug Abuse + + + + + Harassment / Abuse + + + + + Verbal Abuse + + + + + Hate speech + + + + + Spam + + + + + Other + + + + + Using external tools, cheat programs, or exploiting game bugs + + + + + Exploiting a bug or glitch to gain an unfair advantage + + + + + Threatening, bullying, or persistent unwanted contact + + + + + Abusive or offensive language directed at another player + + + + + Discriminatory language targeting race, gender, religion, etc. + + + + + Repeated unwanted messages or advertisements + + + + + Any behavior not covered by the above categories + + DlgRollDice @@ -3629,46 +4683,12 @@ Your email will be used to verify your account. DlgSettings - + Unknown Error loading card database 讀取卡牌數據庫時出現未知錯誤 - - Your card database is invalid. - -Cockatrice may not function correctly with an invalid database - -You may need to rerun oracle to update your card database. - -Would you like to change your database location setting? - 你的卡牌資料庫無效。 - -資料庫無效可能會導致Cockatrice無法正確運行。 - -你應該重新啓動Oracle以便更新卡牌資料庫。 - -你想要重新設置卡牌資料庫路徑嗎? - - - - Your card database version is too old. - -This can cause problems loading card information or images - -Usually this can be fixed by rerunning oracle to to update your card database. - -Would you like to change your database location setting? - 你的卡牌資料庫過舊。 - -這可能會導致卡牌圖片出現問題。 - -你應該重新啓動Oracle以便更新卡牌資料庫。 - -你想要重新設置卡牌資料庫路徑嗎? - - - + Your card database did not finish loading Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues with your cards.xml attached @@ -3677,7 +4697,7 @@ Would you like to change your database location setting? - + File Error loading your card database. Would you like to change your database location setting? @@ -3686,7 +4706,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌數據庫路徑嗎? - + Your card database was loaded but contains no cards. Would you like to change your database location setting? @@ -3695,7 +4715,7 @@ Would you like to change your database location setting? 你想要重新設置卡牌資料庫路徑嗎? - + Unknown card database load status Please file a ticket at https://github.com/Cockatrice/Cockatrice/issues @@ -3704,104 +4724,235 @@ Would you like to change your database location setting? - - - + + + Error 錯誤 - + The path to your deck directory is invalid. Would you like to go back and set the correct path? 你的套牌路徑無效。你想要重新設置正確的路徑嗎? - + The path to your card pictures directory is invalid. Would you like to go back and set the correct path? 你想要重新設置卡牌數據庫路徑嗎? - + Settings 設定 - + + Search settings... + + + + + OK + + + + General 常規 - + Appearance 外觀 - + User Interface 用戶界面 - + Storage - + Card Sources 卡牌的來源 - + + Your card database is invalid. + +Cockatrice may not function correctly with an invalid database + +You may need to rerun Oracle to update your card database. + +Would you like to change your database location setting? + + + + + Your card database version is too old. + +This can cause problems loading card information or images + +Usually this can be fixed by rerunning Oracle to to update your card database. + +Would you like to change your database location setting? + + + + Chat 聊天 - + Sound 聲音 - + Shortcuts 快捷鍵 + + DlgShareDeck + + + + + + Share deck + + + + + + Shared deck + + + + + Share name: + + + + + Create share link + + + + + Cancel + + + + + Failed to create the share link (server response code %1). + + + + + Share link created and copied to the clipboard: + +%1 + +The share expires on %2. + + + + + The server did not respond in time. Try again. + + + + + DlgSharedDecksPreview + + + Open shared decks + + + + + Share: %1 + + + + + Untitled + + + + + From %1 + + + + + This share link expires on %1 + + + + + Open selected + + + + + Open all + + + + + Cancel + + + + + Downloading deck %1 of %2: %3 + + + DlgStartupCardCheck - + Card Update Check - + It has been more than %2 days since you last checked your card database for updates. Choose how you would like to run the card database updater. You can always change this behavior in the 'General' settings tab. - + Run in foreground - + Run in background - + Run in background and always from now on - + Don't prompt again and don't run - + Don't run this time @@ -3809,17 +4960,17 @@ You can always change this behavior in the 'General' settings tab. DlgTipOfTheDay - + Next 下一個 - + Previous 已知 - + Tip of the Day 每日提示 @@ -3827,165 +4978,175 @@ You can always change this behavior in the 'General' settings tab. DlgUpdate - + Current release channel 當前的發行通道 - + Reinstall 重新安裝 - + Cancel Download 取消下載 - + Open Download Page 打開下載頁面 - + Check for Client Updates - - - - + + + + Error 錯誤 - + Cockatrice was not built with SSL support, therefore you cannot download updates automatically! Please visit the download page to update manually. 雞蛇不支持建立SSL連接,因此你不能自動下載更新! 請訪問下載頁面以手動更新。 - + Downloading update: %1 - + Checking for updates... 正在檢查更新... - + Finished checking for updates 更新檢查完成 - + No Update Available 沒有更新可用 - + Cockatrice is up to date! 雞蛇已經是最新的了! - + You are already running the latest version available in the chosen release channel. 你運行的已經是最新的發行版本了。 - + Current version 當前版本 - + Selected release channel 選擇發行渠道 - - + + Update Available 更新可用 - - + + A new version of Cockatrice is available! 雞蛇有新版本可用! - - + + New version 新版本 - - + + Released 發佈 - - + + Changelog 更新日誌 - + Do you want to update now? 你打算現在就更新嗎? - + Unfortunately, the automatic updater failed to find a compatible download. You may have to manually download the new version. - + Please check the <a href="%1">releases page</a> on our Github and download the build for your system. - - - + + Update + + + + + The update installer is already running and waits about a minute for Cockatrice to close. Cockatrice is still busy, so save your work and close it before then. Otherwise the installer gives up and the update is cancelled. + + + + + + Update Error 更新錯誤 - + An error occurred while checking for updates: 檢查更新時發生錯誤: - + An error occurred while downloading an update: 下載更新時發生錯誤: - + Installing... 正在安裝... - + Cockatrice is unable to open the installer. 雞蛇無法打開安裝程序。 - + Try to update manually by closing Cockatrice and running the installer. 嘗試關閉雞蛇運行安裝程序手動更新。 - + Download location 下載區域信息 @@ -3993,17 +5154,17 @@ You may have to manually download the new version. DlgViewLog - + Clear log when closing 關閉時清除日誌 - + Copy to clipboard - + Debug Log 問題日誌 @@ -4241,7 +5402,7 @@ You may have to manually download the new version. EdhrecCommanderResponseCommanderDetailsDisplayWidget - + Salt: @@ -4249,7 +5410,7 @@ You may have to manually download the new version. FilterBuilder - + Type your filter here 輸入你打算篩選的類型 @@ -4257,45 +5418,88 @@ You may have to manually download the new version. FilterDisplayWidget - + Confirm Delete - + Are you sure you want to delete the filter '%1'? - + Delete Failed - + Failed to delete filter '%1'. + + FinishPage + + + You're All Set + + + + + That's everything for now. Jump into Settings any time to change your mind about any of this. + +Have fun! + + + + + FirstRunWizard + + + Finish + + + + + Next + + + + + Welcome to Cockatrice + + + + + Back + + + + + Skip + + + GameEventHandler - + kicked by game host or moderator - + player left the game - + player disconnected from server - + reason unknown @@ -4303,140 +5507,156 @@ You may have to manually download the new version. GameSelector - - - - - - - - - + + + + + + + + + Error 錯誤 - + Please join the appropriate room first. 首先請加入一個房間. - + Wrong password. 密碼錯誤. - + Spectators are not allowed in this game. 這個遊戲不允許觀眾加入. - + The game is already full. 這個遊戲已經滿了. - + The game does not exist any more. 這個遊戲不能裝下啊更多了. - + This game is only open to registered users. 這個遊戲只開放給註冊用戶. - + This game is only open to its creator's buddies. 這個遊戲只開放給好友。 - + You are being ignored by the creator of this game. 你屏蔽了這個與遊戲的創造者. - + Join Game - + Spectate Game - + Game Information - + + Cop&y game link + + + + Join Game as Judge - + Spectate Game as Judge - + + Join game 加入遊戲 - - Password: - 密碼: + + The game is full. Join as a spectator instead? + - + + Password for game #%1: + + + + + Password for "%1": + + + + Please join the respective room first. 請各自加入一個房間. - + &Filter games 過濾房間 - + C&lear filter 清除篩選項目 - + C&reate 創建 - + &Join 加入 - + Join as judge - + J&oin as spectator 觀看 - + Join as judge spectator - + Games shown: %1 / %2 顯示遊戲:%1/%2 - + Games 遊戲 @@ -4477,105 +5697,105 @@ You may have to manually download the new version. GamesModel - + >1 day - + %1%2 hr short age in hours - + new 新建 - + %1%2 min short age in minutes - + password 密碼 - + buddies only 僅限好友 - + reg. users only 僅限註冊用户 - + open decklists - - + + can chat 允許聊天 - + see hands 查看手牌 - + can see hands 允許查看手牌 - + not allowed 不允許 - + Room 房間 - + Age 時長 - + Description 描述 - - Creator - 創建者 + + Host + - + Type 類型 - + Restrictions 限制 - + Players 玩家 - + Spectators 觀看者 @@ -4583,158 +5803,275 @@ You may have to manually download the new version. GeneralSettingsPage - + Reset all paths - + All paths have been reset - - - - - - - + + + + English + 繁體中文 (Chinese Traditional) + + + + + + + + + Choose path 選擇路徑 - + + <p>The card database only contains English card data. To see cards in <b>%1</b>, <b>Oracle</b> must run once with this language selected and re-import the card database.</p><p>The cached database and the downloaded card pictures have been cleared, so a re-import is picked up without stale entries.</p> + + + + + <p>The Scryfall URL that resolves card art by translated name was added to the top of your download list. You can remove or reorder it any time.</p> + + + + + <p>Run Oracle now?</p> + + + + + Card text & images language changed + + + + Language settings - + Language: 語言: - + + Card text & images language: + + + + + Foreign card names, text and art apply after you update the card database (Oracle). + + + + + Language used in card search: + + + + + Selected card language (untranslated cards still match in English) + + + + + English and selected card language + + + + Version settings - + Card database - + Startup settings - + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + + + The tab shown when Cockatrice starts. If the chosen tab is not open yet, it is opened. + + + + + Server: + + + + + Room: + + + + + Room name + + + + Paths (editing disabled in portable mode) 路徑(在便攜模式下禁止編輯) - + Paths 路徑 - + How to help with translations - + Decks directory: 套牌路徑: - + Filters directory: - + Replays directory: 遊戲錄像目錄: - + Pictures directory: 圖片目錄: - + Card database: 卡牌數據庫: - + Custom database directory: - + Token database: 衍生物數據庫: - + Update channel 更新通道 - + Check for client updates on startup - + Check for card database updates on startup - + Don't check - + Prompt for update - + Always update in the background - + Check for card database updates every - + days - + Notify if a feature supported by the server is missing in my client 當客戶端缺少伺服器支持的特性時提示我 - + Automatically run Oracle when running a new version of Cockatrice 運行新版本雞蛇時自動運行Oracle - + Show tips on startup 顯示啓動時提示 - + Last update check on %1 (%2 days ago) @@ -4742,47 +6079,47 @@ You may have to manually download the new version. GraveyardMenu - + &Graveyard - + &View graveyard - + &Move graveyard to... - + &Top of library - + &Bottom of library - + &All players - + &Hand - + &Exile - + Reveal random card to... @@ -4876,59 +6213,353 @@ You may have to manually download the new version. + + HomeTabButtonColor + + + From theme colors + + + + + Extract from background + + + HomeWidget - + Create New Deck - + Browse Decks - + Browse Card Database - + Browse EDHRec - + Browse Archidekt - + View Replays - + Quit - + Connecting... - + Connect - + Play + + IntentConnectToServer + + + Timed out while connecting to %1:%2 + + + + + Failed to connect to %1:%2: %3 + + + + + IntentGetLoginCredentials + + + + No saved credentials for this server + + + + + IntentJoinServerGame + + + Game %1 not found in the room + + + + + IntentJoinServerRoom + + + No server tab available + + + + + Failed to join the server room %1 + + + + + Timed out while joining the server room %1 + + + + + IntentOpenLocalDeck + + + Unable to load deck file %1 + + + + + IntentOpenServerRoomByName + + + No server tab available + + + + + Timed out while looking for the server room %1 + + + + + Disconnected while looking for the server room %1 + + + + + Failed to join the server room %1: it doesn't exist on the server. + + + + + You do not have the required permission to join the server room %1. + + + + + Failed to join the server room %1 due to an unknown error. + + + + + IntentOpenSharedDeck + + + The shared deck could not be found or has expired + + + + + + The shared deck is empty + + + + + Failed to download the shared deck + + + + + The shared deck could not be loaded + + + + + + Unknown deck + + + + + Open shared decks + + + + + Could not download the deck "%1". + +%n deck(s) were already downloaded. Open them? + + + + + Timed out while loading the shared deck + + + + + Timed out while downloading the shared deck + + + + + IntentUrlParser + + + Open game + + + + + Missing or empty hostname in the game link + + + + + Invalid or missing port in the game link + + + + + Invalid or missing room id in the game link + + + + + Invalid or missing game id in the game link + + + + + Join game + + + + + Join game #%1 in "%2" on %3? + + + + + Join game #%1 on %2? + + + + + Join game "%1" (#%2) in "%3" on %4? + + + + + Join game "%1" (#%2) on %3? + + + + + + + + Open shared deck + + + + + Missing or empty hostname in the share link + + + + + Invalid or missing port in the share link + + + + + Missing or empty share value in the share link + + + + + Opening this share link connects you to %1 instead of %2. + +Continue? + + + + + Opening this share link connects you to %1. + +Continue? + + + + + Opening this share link connects you to %1, a server you have never connected to before. + +Continue? + + + + + LatencyStatusWidget + + + Ping + + + + + Ping: %1 ms + + + + + Connection latency details + + + + + Connection quality over the last %n sample(s): + + + + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + LibraryMenu @@ -5146,273 +6777,294 @@ You may have to manually download the new version. MainWindow - - + + Player %1 玩家 %1 - + Load replay 載入遊戲錄像 - + About Cockatrice 關於Cockatrice雞蛇 - + Version 版本 - + Cockatrice Webpage Cockatrice雞蛇網站 - + Project Manager: 項目經理: - + Past Project Managers: 前項目經理: - + Developers: 開發者: - + Our Developers 我們的開發者: - + Help Develop! 協助開發! - + Translators: 翻譯者: - + Our Translators 翻譯者名單 - + Help Translate! 協助翻譯! - + Support: 支持: - + Report an Issue 報告錯誤 - + Troubleshooting 排除故障 - + F.A.Q. 問答 - - + + Error 錯誤 - + &Connect... 連接⋯ - + &Disconnect 斷開連接 - + Start &local game... 開始本地遊戲... - + &Watch replay... 觀看錄像... - + &Full screen 全屏 - + &Register to server... 註冊到伺服器 - + &Restore password... - + &Settings... 設置⋯ - + &Exit 退出 - + A&ctions 動作 - + &Cockatrice CockatriceCockatrice - + C&ard Database 卡牌資料庫 - + &Manage sets... 系列管理 - + Edit custom &tokens... 編輯自定義&tokens - + Open custom image folder 打開自定義圖片文件夾 - + Open custom sets folder 打開自定義系列文件夾 - + Add custom sets/cards 添加自定義系列/卡牌 - + Reload card database - + Tabs - + &Help 幫助 - + &About Cockatrice 關於Cockatrice雞蛇 - + &Tip of the Day & 每日提示 - + Check for Client Updates 檢查客戶端更新 - + Check for Card Updates... - + Check for Card Updates (Automatic) - + Show Status Bar - + View &Debug Log - + Open Settings Folder - + + Re-run Onboarding Wizard... + + + + Show/Hide - + + Sign-in cancelled + + + + New Version 新版本 - + Congratulations on updating to Cockatrice %1! Oracle will now launch to update your card database. 恭喜Cockatrice更新到%1! 現在啓動Oracle更新你的卡牌資料庫。 - + Cockatrice installed 雞蛇已安裝 - + Congratulations on installing Cockatrice %1! Oracle will now launch to install the initial card database. 恭喜Cockatrice更新到%1! 現在啓動Oracle更新你的卡牌資料庫。 - + + Are you sure? + + + + + A card database update is still running. Quitting now will cancel it. +Are you sure you want to quit? + + + + Card database 卡牌數據庫 - + Cockatrice is unable to load the card database. Do you want to update your card database now? If unsure or first time user, choose "Yes" @@ -5421,51 +7073,51 @@ If unsure or first time user, choose "Yes" 如果不確定或者你是第一次使用,請選擇“是” - - + + Yes 是 - - + + No 否 - + Open settings 打開設定 - + New sets found 發現新系列 - + %n new set(s) found in the card database Set code(s): %1 Do you want to enable it/them? - + Yes, always enable - + View sets 查看系列 - + Welcome 歡迎 - + Hi! It seems like you're running this version of Cockatrice for the first time. All the sets in the card database have been enabled. Read more about changing the set order or disabling specific sets and consequent effects in the "Manage Sets" dialog. @@ -5474,89 +7126,89 @@ Read more about changing the set order or disabling specific sets and consequent 瞭解更多用“系列管理”窗口更改系列順序或者禁用某系列的信息。 - + Information 信息 - + A card database update is already running. 數據庫更新已經在運行中。 - + Unable to run the card database updater: 無法運行卡組數據庫更新器: - + Card database update running. - + Failed to start. The file might be missing, or permissions might be incorrect. - + The process crashed some time after starting successfully. - + Timed out. The process took too long to respond. The last waitFor...() function timed out. - + An error occurred when attempting to write to the process. For example, the process may not be running, or it may have closed its input channel. - + An error occurred when attempting to read from the process. For example, the process may not be running. - + Unknown error occurred. - + The card database updater exited with an error: %1 - - - - - + + + + + Load sets/cards 載入系列/卡牌 - + Selected file cannot be found. 找不到選擇的文件。 - + You can only import XML databases at this time. 當前只能導入XML格式數據庫。 - + The new sets/cards have been added successfully. Cockatrice will now reload the card database. 新的系列/卡牌已添加成功。 CockatriceCockatrice現在會重新載入卡組數據庫。 - + Sets/cards failed to import. 系列/卡牌導入失敗。 @@ -5761,615 +7413,615 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 MessageLogWidget - + from play 從戰場上 - + from their graveyard 從他的墳墓場 - + from exile 從放逐區 - + from their hand 從他的手牌 - + the top card of %1's library %1牌庫頂的牌 - + the top card of their library 他牌庫頂的卡牌 - + from the top of %1's library 從%1的牌庫頂 - + from the top of their library 從他的牌庫頂 - + the bottom card of %1's library %1的牌庫底牌 - + the bottom card of their library 他牌庫底部的牌 - + from the bottom of %1's library 從%1的牌庫底 - + from the bottom of their library 從他的牌庫底 - + from %1's library 從%1的牌庫中 - + from their library 從他的牌庫 - + from sideboard 從備牌中 - + from the stack 從堆疊中 - + from custom zone '%1' - + %1 is now keeping the top card %2 revealed. %1已持續展示牌庫頂牌%2 - + %1 is not revealing the top card %2 any longer. %1不再%2展示牌庫頂牌。 - + %1 can now look at top card %2 at any time. - + %1 no longer can look at top card %2 at any time. - + %1 attaches %2 to %3's %4. %1 結附 %2 指向 %3的 %4. - + %1 has conceded the game. %1 已放棄遊戲。 - + %1 has unconceded the game. %1 已放棄遊戲。 - + %1 has restored connection to the game. %1已恢復連接。 - + %1 has lost connection to the game. %1 已失去連接。 - + %1 points from their %2 to themselves. %1 將%2 指向自己。 - + %1 points from their %2 to %3. %1 將%2指向%3。 - + %1 points from %2's %3 to themselves. %1將%2的%3 - + %1 points from %2's %3 to %4. %1 將%2的%3指向%4。 - + %1 points from their %2 to their %3. %1將他的%2指向他的%3。 - + %1 points from their %2 to %3's %4. %1將他的%2指向%3的%4。 - + %1 points from %2's %3 to their own %4. %1將%2的%3指向他自己的%4。 - + %1 points from %2's %3 to %4's %5. %1 將%2的%3指向%4的%5。 - + %1 creates a face down token. - + %1 creates token: %2%3. %1創建衍生物:%2%3。 - + %1 has loaded a deck (%2). %1 已載入套牌(%2) - + %1 has loaded a deck with %2 sideboard cards (%3). %1已載入含有 %2張備牌的套牌(%3) - + %1 destroys %2. %1銷燬了%2。 - + a card 一張牌 - + %1 gives %2 control over %3. %1讓%2控制%3。 - + %1 puts %2 into play%3 face down. - + %1 puts %2 into play%3. %1將%2%3放進戰場。 - + %1 puts %2%3 into their graveyard face down. - + %1 puts %2%3 into their graveyard. %1將%2%3置入他的墳墓場。 - + %1 exiles %2%3 face down. - + %1 exiles %2%3. %1 %3放逐了 %2 - + %1 moves %2%3 to their hand. %1將%2%3放回他的手牌中。 - + %1 puts %2%3 into their library. %1將%2%3放入他的牌庫。 - + %1 puts %2%3 onto the bottom of their library. - + %1 puts %2%3 on top of their library. %1將%2%3置於其牌庫頂。 - + %1 puts %2%3 into their library %4 cards from the top. %1將%2%3放入牌庫%4張牌放在牌庫頂 - + %1 moves %2%3 to sideboard. %1 將%2%3移動到備牌 - + %1 plays %2%3 face down. - + %1 plays %2%3. %1使用 %2%3。 - + %1 moves %2%3 to custom zone '%4' face down. - + %1 moves %2%3 to custom zone '%4'. - + %1 tries to draw from an empty library - + %1 draws %2 card(s). - + %1 is looking at %2. %1 正在查看%2。 - + %1 is looking at the %4 %3 card(s) %2. top card for singular, top %3 cards for plural - + bottom - + top - + %1 turns %2 face-down. %1回合%2翻為面朝下。 - + %1 turns %2 face-up. %1回合%2翻為面朝上。 - + The game has been closed. 遊戲已經關閉。 - + The game has started. 遊戲已開始。 - + You are flooding the game. Please wait a couple of seconds. - + %1 has joined the game. %1 已經加入遊戲。 - + %1 is now watching the game. %1 正在旁觀遊戲。 - + You have been kicked out of the game. 你已被踢出遊戲。 - + %1 has left the game (%2). %1 已經離開了遊戲(%2)。 - + %1 is not watching the game any more (%2). %1 不再旁觀遊戲(%2)。 - + %1 is not ready to start the game any more. %1 還未準備好開始遊戲。 - + %1 shuffles their deck and draws a new hand of %2 card(s). - + %1 shuffles their deck and draws a new hand. %1洗牌並抓了新的起手 - + You are watching a replay of game #%1. 你正在觀看遊戲#%1的錄像。 - + %1 is ready to start the game. %1已準備好開始遊戲。 - + cards an unknown amount of cards 牌 - + %1 card(s) a card for singular, %1 cards for plural - + %1 lends %2 to %3. - + %1 reveals %2 to %3. %1將%2展示給%3。 - + %1 reveals %2. %1 展示 %2。 - + %1 randomly reveals %2%3 to %4. %1隨機展示%2%3給%4。 - + %1 randomly reveals %2%3. %1隨機展示%2%3。 - + %1 peeks at face down card #%2. %1查看面朝下的卡牌#%2。 - + %1 peeks at face down card #%2: %3. %1查看面朝下的卡牌#%2:%3。 - + %1 reveals %2%3 to %4. %1展示%2%3給%4。 - + %1 reveals %2%3. %1展示%2%3。 - + %1 reversed turn order, now it's %2. %1 反轉回合順序,現在是%2的回合。 - + reversed 反轉 - + normal 正常 - + Heads 正面 - + Tails 反面 - + %1 flipped a coin. It landed as %2. %1擲硬幣。結果為%2。 - + %1 rolls a %2 with a %3-sided die. %1擲%3面骰子,結果為%2。 - + %1 flips %2 coins. There are %3 heads and %4 tails. - + %1 rolls a %2-sided dice %3 times: %4. - + %1's turn. 現在是%1的回合。 - + %1 sets annotation of %2 to %3. %1給%2添加了註釋%3。 - + %1 places %2 %3%4 counter(s) on %5 (now %6). - + %1 removes %2 %3%4 counter(s) from %5 (now %6). - + %1 failed to undo their last draw. - + %1 sets counter %2 to %3 (%4%5). %1 將%2指示物設置為 %3 (%4%5)。 - + %1 sets %2 to not untap normally. %1 將 %2 設置為不會被通常重置。 - + %1 sets %2 to untap normally. %1 將 %2 設置為可通常重置。 - + %1 removes the PT of %2. %1 移除%2力量/防禦 - + %1 changes the PT of %2 from nothing to %4. %1改變%2的力量/防禦至%4 - + %1 changes the PT of %2 from %3 to %4. %1將%2的力量/防禦從%3改變至%4 - + %1 has locked their sideboard. %1已鎖定他的備牌。 - + %1 has unlocked their sideboard. %1 解除鎖定他的備牌。 - + %1 taps their permanents. %1 橫置了永久物。 - + %1 untaps their permanents. %1 重置了永久物。 - + %1 taps %2. %1 橫置了 %2。 - + %1 untaps %2. %1 重置了 %2。 - + %1 shuffles %2. %1 切洗了%2。 - + %1 shuffles the bottom %3 cards of %2. %1 洗 %2 底的 %3 張卡 - + %1 shuffles the top %3 cards of %2. %1 洗 %2 頂的 %3 張卡 - + %1 shuffles cards %3 - %4 of %2. %1洗%3 - %4 到%2. - + %1 unattaches %2. %1 取消了%2的結附。 - + %1 undoes their last draw. %1撤銷了最後的抓牌。 - + %1 undoes their last draw (%2). %1撤銷了最後的抓牌(%2)。 @@ -6377,115 +8029,125 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 MessagesSettingsPage - + Word1 Word2 Word3 詞1 詞2 詞3 - + Add New Message 添加新信息 - + Edit Message 編輯信息 - + Remove Message - + Add message 添加信息 - - + + Message: 信息: - + Edit message 編輯消息 - + Chat settings 聊天設定 - + Custom alert words 自定義警告語 - + Enable chat mentions 允許聊天中提到某人 - + Enable mention completer 允許自動完成提名 - + In-game message macros 遊戲內消息宏 - + How to use in-game message macros - + Ignore chat room messages sent by unregistered users 忽略未註冊用戶的聊天室消息。 - + Ignore private messages sent by unregistered users 忽略未註冊用戶發出的私人消息 - + Ignore private messages sent by non-buddy users - - + + Invert text color 反轉文本顏色 - + Enable desktop notifications for private messages 開啓私人消息桌面提醒 - + Enable desktop notification for mentions 開啓聊天提名桌面提醒 - + Enable room message history on join 開啓加入聊天室時的歷史消息 - - + + Ignore all private messages + + + + + Use game time instead of local time in game logs + + + + + (Color is hexadecimal) (顏色為16進制) - + Separate words with a space, alphanumeric characters only 將單詞以空格區分,僅支持字母 @@ -6493,42 +8155,42 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 MoveMenu - + Move to - + &Top of library in random order - + X cards from the top of library... - + &Bottom of library in random order - + T&able - + &Hand - + &Graveyard - + &Exile @@ -6607,148 +8269,134 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 PaletteEditorDialog - - + + Reset - - - Apply - - - - - - Save && Apply - - - - + ▼ Edit Palette - - + + ▶ Edit Palette - + Palette Editor — %1 - + <b>Palette Editor</b> &nbsp;·&nbsp; %1 - - This theme ships no default palette files - - - - - Replace current colours with the theme author's defaults - - - - - Switch between the light and dark palette files + + + Save - Editing: + Delete this scheme's custom palette and revert to the theme default (or the application palette) - Show or hide the per-role colour grid for manual tweaks + Switch between the light and dark palette files + Editing: + + + + + Show or hide the per-role colour grid for manual tweaks + + + + ↺ Revert to theme default - + Discard unsaved edits and restore the last saved palette - - Preview this palette without saving to disk + + Cannot save: this theme has no writable directory - + Write palette-%1.toml and reload the theme - - Cannot save: this theme has no directory on disk - - - - + + Save failed - + + Could not write %1 to: %2 - - - No default found - - - - - No default palette file found for the "%1" scheme. - - PaletteGridWidget - + Active - + Disabled - + Inactive - + Normal interactive state - + Widget is disabled / not interactive - + Window is in background / unfocused + + + App colors + + + + + Application-specific colors layered on top of the Qt palette + + Phase @@ -6871,30 +8519,21 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 結束步驟 - - PictureLoader - - - en - code for scryfall's language property, not available for all languages - - - PlayerActions - - - - + + + + grave - - - - + + + + exile @@ -7013,16 +8652,272 @@ CockatriceCockatrice現在會重新載入卡組數據庫。 PlayerMenu - + Player "%1" - + &Counters + + PlaymatCollectionDialog + + + Default Playmats + + + + + Add... + + + + + Edit... + + + + + Remove + + + + + Move Up + + + + + Move Down + + + + + List mode: + + + + + Fixed (always the first entry) + + + + + Round-robin (cycle through entries) + + + + + Random (pick one per game) + + + + + PlaymatPreviewWidget + + + Playmat crop + + + + + + + + + Zoom %1× + + + + + No card selected + + + + + PlaymatSettingsDialog + + + Playmat Settings + + + + + Type a card name... + + + + + Card name: + + + + + Printing: + + + + + Show numerical editors + + + + + Left margin (%): + + + + + Right margin (%): + + + + + Vertical offset: + + + + + Zoom: + + + + + Card + + + + + Crop + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. Dimmed strips mark where a wider table crops further. + + + + + Remove Playmat + + + + + PreferencesSetupPage + + + A Few Preferences + + + + + Defaults are fine — tweak these now or from Settings anytime. + + + + + Gameplay + + + + + Double-click cards to play them + + + + + When disabled, a single click plays the selected card onto the table. + + + + + Display hand horizontally + + + + + Shows your hand as a row along the bottom of the table instead of a column beside it. + + + + + Play all nonlands onto the stack by default + + + + + Cards you play appear on the stack so other players can respond to them, as in a tabletop game. + + + + + Notifications && Sound + + + + + Show desktop notifications + + + + + Play sound effects + + + + + Startup + + + + + Startup tab: + + + + + Home + + + + + Visual Deck Storage + + + + + Deck Storage + + + + + Game Replays + + + + + Deck Editor + + + + + Visual Deck Editor + + + + + Server + + + + + Server Room + + + PrintingDisabledInfoWidget @@ -7043,12 +8938,12 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelector - + Display Navigation Buttons - + Printing Selector @@ -7056,25 +8951,65 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardOverlayWidget - + Preference - + Pin Printing - + Unpin Printing - + + Image Overrides + + + + + Load Custom Image... + + + + + Clear Custom Image + + + + + %1 %2 + + + + Show Related cards + + + Select Card Image + + + + + Images (*.png *.jpg *.jpeg *.webp) + + + + + Load Custom Image + + + + + The selected file could not be read as an image. + + PrintingSelectorCardSearchWidget @@ -7105,28 +9040,28 @@ This setting means you'll only see the default printing for each card, instead o PrintingSelectorCardSortingWidget - + Alphabetical - + Preference - + Release Date - - + + Descending - + Ascending @@ -7142,95 +9077,103 @@ This setting means you'll only see the default printing for each card, instead o PtMenu - + Power / toughness - + &Increase power - + &Decrease power - + I&ncrease toughness - + D&ecrease toughness - + In&crease power and toughness - + Dec&rease power and toughness - + Increase power and decrease toughness - + Decrease power and increase toughness - + Set &power and toughness... - + Reset p&ower and toughness + + PublicDeckPreviewWidget + + + Uploaded %1 + + + QMenuBar - + Services 服務 - + Hide %1 隱藏 %1 - + Hide Others 隱藏其他 - + Show All 展示所有 - + Preferences... 參數 - + Quit %1 退出%1 - + About %1 關於 %1 @@ -7238,17 +9181,17 @@ This setting means you'll only see the default printing for each card, instead o QObject - + Cockatrice card database (*.xml) CockatriceCockatrice卡牌數據庫 (*.xml) - + All files (*.*) 全部文件 (*.*) - + Cockatrice replays (*.cor) Cockatrice錄像文件 (*.cor) @@ -7268,17 +9211,22 @@ This setting means you'll only see the default printing for each card, instead o 衍生物 - + + Maybeboard + + + + Overwrite Existing File? - + A .cod version of this deck already exists. Overwrite it? - + Enabling this feature will disable the use of the Printing Selector. You will not be able to manage printing preferences on a per-deck basis, or see printings other people have selected for their decks. @@ -7289,7 +9237,7 @@ Are you sure you would like to enable this feature? - + Disabling this feature will enable the Printing Selector. You can now choose printings on a per-deck basis in the Deck Editor and configure which printing gets added to a deck by default by pinning it in the Printing Selector. @@ -7300,100 +9248,110 @@ Are you sure you would like to disable this feature? - + Confirm Change + + + Art: %1 + + + + + Open deck + + QPlatformTheme - + OK 確定 - + Save 保存 - + Save All 保存全部 - + Open 打開 - + &Yes 是 - + Yes to &All 全選是 - + &No 否 - + N&o to All 全選否 - + Abort 退出 - + Retry 重試 - + Ignore 忽略 - + Close 關閉 - + Cancel 取消 - + Discard 棄牌 - + Help 幫助 - + Apply 應用 - + Reset 重置 - + Restore Defaults 恢復為默認值 @@ -7401,121 +9359,189 @@ Are you sure you would like to disable this feature? QuickSetupPanel - + %1% - + <b>Quick Setup</b> - + Generate all palette roles automatically from a single accent colour - + Accent: - + Primary hue. Used directly for highlights and links. At high intensity it also tints buttons and backgrounds. - + Intensity: - + Subtle - + Full colour - + 0–30 Subtle tint — only highlights and links change hue 30–70 Accented — buttons, tooltips, and borders join in 70–100 Full colour — backgrounds, everything - + 70% - - - Generate ↓ - - - - - Derive all palette roles from the accent colour above. -Fine-tune individual colours in the grid afterwards. - - RemoteDeckList_TreeModel - + + + Public + + + + + + Public (inherited) + + + + + + Private + + + + + This folder is visible to other users + + + + + This folder is private, but a parent folder is public (inherited). + + + + + This folder is only visible to you + + + + + This deck is visible to other users + + + + + This deck is private, but a parent folder is public (inherited). + + + + + This deck is only visible to you + + + + Name 名稱 - + ID ID - + Upload time 上傳時間 + + + Visibility + + + + + RemotePublicDecksModel + + + The server did not respond in time. Try again. + + + + + Failed to load the user's public decks (server response code %1). + + RemoteReplayList_TreeModel - + ID ID - + Name 名稱 - + Players 玩家 - + Keep 維持 - + Time started 遊戲時間 - + Duration (sec) 持續(離開) + + ReplayQuickSettingsWidget + + + Fast forward speed: + + + + + Skip empty sections + + + RfgMenu @@ -7557,37 +9583,37 @@ Fine-tune individual colours in the grid afterwards. RoomSelector - + Rooms 房間 - + Joi&n 加入 - + Room 房間 - + Description 信息 - + Permissions 許可證 - + Players 玩家 - + Games 遊戲 @@ -7595,7 +9621,7 @@ Fine-tune individual colours in the grid afterwards. SayMenu - + S&ay @@ -7603,32 +9629,32 @@ Fine-tune individual colours in the grid afterwards. SequenceEdit - + Choose an action from the table 從列表中選擇動作 - + Hit the key/combination of keys you want to set for this action 點擊要為此操作設置的鍵/組合鍵 - + Invalid key 無效密鑰 - + Shortcut already in use by: - + Clear 清理 - + Restore default 恢復至默認狀態 @@ -7661,56 +9687,92 @@ Fine-tune individual colours in the grid afterwards. 發佈日期 + + ShareBarWidget + + + Share name + + + + + Cancel + + + + + Create share link + + + + + SharedDeckPreviewWidget + + + Select this deck + + + + + (selected) + + + ShortcutSettingsPage - - + + Restore all default shortcuts 恢復所有默認快捷鍵 - + Do you really want to restore all default shortcuts? 確定要恢復所有默認快捷鍵嗎? - + Clear all default shortcuts 清楚所有默認快捷鍵 - + Do you really want to clear all shortcuts? 確定要清楚所有快捷鍵? - + Section: 部分: - + Action: 行動: - + Shortcut: 快捷鍵: - + + Shortcut editor + + + + How to set custom shortcuts 如何設置自定義快捷鍵 - + Clear all shortcuts - + Search by shortcut name @@ -7766,12 +9828,12 @@ Please check your shortcut settings! SideboardMenu - + &Sideboard - + &View sideboard @@ -7779,27 +9841,27 @@ Please check your shortcut settings! SoundSettingsPage - + Enable &sounds 啓用聲效 - + Current sounds theme: 當前聲效主題: - + Test system sound engine 測試系統聲效 - + Sound settings 聲效設定 - + Master volume 主音量 @@ -7807,48 +9869,48 @@ Please check your shortcut settings! SpoilerBackgroundUpdater - + Spoilers season has ended 預覽季節已結束 - + Deleting spoiler.xml. Please run Oracle 刪除spoiler.xml。請運行Oracle - - + + Spoilers download failed 預覽下載錯誤 - + No internet connection 沒有網絡連接 - + Error 錯誤 - + Spoilers already up to date Spoilers更新已準備好 - + No new spoilers added 沒有新的預覽被添加 - + Spoilers have been updated! 預覽已更新! - + Last change: 最後變更: @@ -7856,157 +9918,170 @@ Please check your shortcut settings! StableReleaseChannel - + Default - + No reply received from the release update server. 更新伺服器未回應。 - + Invalid reply received from the release update server. 收到更新伺服器的無效回應。 - + No reply received from the tag update server. 未收到標籤更新伺服器的回應 - + Invalid reply received from the tag update server. 收到標籤更新伺服器的無效回應 + + StatsTally + + + Total Power + + + + + Total Toughness + + + StorageSettingsPage - - - + + + Success - + Cached card pictures have been reset. - + Downloaded card pictures have been reset. - + Error - + One or more downloaded card pictures could not be cleared. - + In-memory (currently loaded) card pictures have been reset. - + Card Picture Loader Caching Method: - + The network cache is the preferred way of storing images. Downloaded images are stored here until the size of the cache exceeds the configured size. Cockatrice automatically monitors this cache and deletes the least recently seen card images to ensure the cache does not exceed the configured size. - + Writing card images directly to a folder on your hard drive is another way of storing images. This does not change how Cockatrice accesses or downloads images. Cockatrice will NOT automatically monitor and clear this folder, so if you enable this option, it is up to you to ensure sufficient available space. It should also be noted that if a provider outage causes you to download the wrong picture (i.e. wrong printing) you will be stuck with it until you manually delete the file, as opposed to using the network cache, which automatically rotates and thus correct errors after a while. - + This is the in-memory picture cache used by the application at runtime. It determines how much memory (RAM) Cockatrice can use before it has to fetch card images from the hard disk again. Increasing this will allow more card images to be displayed at once but shouldn't be necessary. Clearing this will make Cockatrice reload all images from the network cache or the disk. - + Delete Cached Images - + Delete Saved Images - + Clear In-Memory Images - + Card Picture Loader Cache Method - + Network Cache - + Filesystem - + In-Memory Picture Cache - + Network Cache Size: - + On-disk cache for downloaded pictures - + Redirect Cache TTL: - + How long cached redirects for urls are valid for. - + Picture Cache Size: - + In-memory cache for pictures not currently on screen - + Naming scheme: - + Day(s) @@ -8014,17 +10089,33 @@ Please check your shortcut settings! TabAccount - + + + My Reports + + + + Add to Buddy List - + Add to Ignore List - + + Buddies are marked with a star in chat, a sound plays when they join or leave the server, and they can be invited to buddy-only games. + + + + + Ignored users' chat messages are hidden from you, and they cannot send you private messages or join your games. + + + + Account @@ -8145,118 +10236,118 @@ Please check your shortcut settings! TabArchidekt - - - + + + Desc. - - + + AND - - + + Require ALL selected colors - - + + Deck name... - - + + Owner... - - + + Packages - - + + Advanced Filters - + Bracket: - - + + Any - - + + Contains card... - - + + Commander... - - + + Tag... - - + + Deck Size - + Cards: - - + + Asc. - + Sort by: - + Filter by: - + Display Settings - - + + Search - - + + Formats @@ -8269,42 +10360,42 @@ Please check your shortcut settings! TabCardArtRules - + Card: - + ProviderId: - + Mode: - + Reason: - + Type a card name... - + Add rule - + Remove rule - + Refresh @@ -8317,52 +10408,52 @@ Please check your shortcut settings! TabDeckEditor - + Card Info 卡牌信息 - + Deck 套牌 - + Filters 過濾器 - + &View 視圖 - + Card Database - + Printing - + Visible 可見 - + Floating 移動窗口 - + Reset layout 重置界面 - + Deck: %1 套牌: %1 @@ -8370,49 +10461,49 @@ Please check your shortcut settings! TabDeckEditorVisual - + Visual Deck: %1 - + &Visual Deck Editor - - + + Card Info - - + + Deck - + &View - + Printing - + Visible - + Floating - + Reset layout @@ -8420,22 +10511,22 @@ Please check your shortcut settings! TabDeckEditorVisualTabWidget - + Visual Deck View - + Visual Database Display - + Deck Analytics - + Sample Hand @@ -8443,139 +10534,237 @@ Please check your shortcut settings! TabDeckStorage - + Local file system 本地文件系統 - + Server deck storage 伺服器上的套牌倉庫 - - + + Open in deck editor 打開套牌編輯 - + Rename deck or folder - + Upload deck 上傳套牌 - + Download deck 下載套牌 - - - - + + + + New folder 新建文件夾 - - + + Delete 刪除 - + + Share decks + + + + + Publish/unpublish deck + + + + Open decks folder - + Rename local folder - + Rename local file - + New name: - - - - + + + + + + + Error 錯誤 - + Rename failed - - + + + Invalid deck file - + Enter deck name 輸入套牌名稱 - + This decklist does not have a name. Please enter a name: 這個套牌沒有名稱. 請輸入一個名稱: - + Unnamed deck 未命名套牌 - + Failed to upload deck to server - + Delete local file 刪除本地文件 - + Are you sure you want to delete the selected files? - + Delete remote decks - + Are you sure you want to delete the selected decks? - - + + + Shared decks + + + + + + Only one folder can be shared at a time. + + + + + + Share either a folder or decks, not both. + + + + + Select folders or decks in the tree to share. + + + + + %n folder(s) + + + + + %n deck(s) + + + + + No decks selected + + + + + Selected: %1 + + + + + The root folder cannot be shared. + + + + + Select decks to share. + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + Failed to change deck visibility on server (response code %1). + + + + + Failed to change the visibility of %n selected deck(s). + + + + + Name of new folder: 新建文件夾的名稱: - + Deck Storage @@ -8583,20 +10772,271 @@ Please enter a name: TabDeckStorageVisual - + Visual Deck Storage - + Error - + Could not open deck at %1 + + + + Shared decks + + + + + Share deck + + + + + You must be connected to the server to share a deck. + + + + + %n deck(s) + + + + + Click deck tiles to select the decks you want to share. + + + + + One deck selected. Create the link to share it with other players. + + + + + %n decks selected. Create the link to share them with other players. + + + + + + Share decks + + + + + Select at least one deck to share. + + + + + Unable to load deck file %1 + + + + + Failed to create the share link (server response code %1). + + + + + Share link copied to the clipboard. +Expires on %1. + + + + + Share link + + + + + The server did not respond in time. Try again. + + + + + TabDeveloper + + + Auto-refresh + + + + + Automatically request fresh server statistics at a fixed interval. + + + + + s + + + + + Seconds between automatic refreshes. + + + + + Refresh server stats + + + + + Statistic;Value + + + + + Command;Count;Total ms;Avg ms + + + + + %1 GiB + + + + + %1 MiB + + + + + %1 KiB + + + + + %1 bytes + + + + + %1 s + + + + + %1 ms + + + + + No server statistics available yet. + + + + + Registered users online + + + + + Moderators online + + + + + Games running + + + + + Traffic sent (last tick) + + + + + Traffic received (last tick) + + + + + Server uptime + + + + + %1d %2h %3m + + + + + %1h %2m + + + + + Snapshot taken + + + + + Live Metrics + + + + + Cards in live games + + + + + Total commands processed + + + + + Avg command time + + + + + Active command types + + + + + Event loop stalls + + + + + Last stall overshoot + + + + + Worst stall overshoot + + + + + Game starts + + + + + Avg game start time + + + + + Updated %1 + + + + + Developer + + TabEdhRec @@ -8609,27 +11049,27 @@ Please enter a name: TabEdhRecMain - + &Cards - + Top Commanders - + Tags - + Search for a card ... - + Search @@ -8642,191 +11082,207 @@ Please enter a name: TabGame - - - + + + Replay 錄像 - - + + Game 遊戲 - - + + Player List 玩家列表 - - + + + Invite + + + + + Card Info 卡牌信息 - - + + Messages 消息 - - + + Replay Timeline 錄像時間線 - + &Phases 階段 - + &Game 遊戲 - + Next &phase 下個階段 - + Next phase with &action 下個階段&行動 - + Next &turn 下個回合 - + Reverse turn order 反轉回合順序 - + &Remove all local arrows 移除所有箭頭 - + Rotate View Cl&ockwise 順時針旋轉視角 - + Rotate View Co&unterclockwise 逆時針旋轉視角 - + Game &information 遊戲信息 - + + Cop&y game link + + + + + Invite to Game... + + + + Un&concede - - - + + + &Concede 放棄遊戲 - + &Leave game 離開遊戲 - + C&lose replay 關閉遊戲錄像 - + &Focus Chat 聚焦聊天室 - + &Say: 説: - + Selected cards - + &View 視圖 - + Visible 可見 - + Floating 移動窗口 - + Reset layout 重置界面 - + Concede 投降 - + Are you sure you want to concede this game? 你確定放棄這個遊戲? - + Unconcede 取消投降 - + You have already conceded. Do you want to return to this game? 你已經投降,你想回到這場遊戲嗎? - + Leave game 離開遊戲 - + Are you sure you want to leave this game? 你確定離開這這個遊戲? - + A player has joined game #%1 - + %1 has joined the game - + You have been kicked out of the game. 你已被踢出遊戲。 @@ -8842,158 +11298,158 @@ Please enter a name: TabLog - + Logs 記錄 - - - + + + Time;SenderName;SenderIP;Message;TargetID;TargetName 時間;發送者姓名;發送者IP;消息;目標ID;目標姓名 - + Room Logs 房間記錄 - + Game Logs 遊戲記錄 - + Chat Logs 聊天記錄 - - + + Error 錯誤 - + You must select at least one filter. 您必須至少選擇一個過濾項 - + You have to select a valid number of days to locate. 您必須選擇一個可用的天數用來定位 - + Username: 用户名: - + IP Address: IP地址: - + Game Name: 遊戲名: - + GameID: 遊戲ID: - + Message: 消息: - + Main Room 主房間 - + Game Room 遊戲房間 - + Private Chat 私人聊天 - + Past X Days: 過去 X 天: - + Today 今天 - + Last Hour 1小時前 - + Maximum Results: 最大結果: - + At least one filter is required. The more information you put in, the more specific your results will be. 至少需要1個篩選項。 您輸入的信息越多,找到結果越準確。 - + Get User Logs 獲取用戶記錄 - + Clear Filters 清楚篩選項目 - + Filters 過濾器 - + Log Locations 記錄位置 - + Date Range 日期範圍 - + Maximum Results 最大結果 - - + + Message History 消息歷史 - + Failed to collect message history information. 獲取消息歷史信息失敗。 - + There are no messages for the selected filters. 沒有符合篩選條件的消息。 @@ -9001,218 +11457,558 @@ The more information you put in, the more specific your results will be. TabMessage - + Private &chat 私人聊天 - + &Leave 離開 - + %1 - Private chat %1 - 私人聊天 - + + You have ignored %1; your messages are not delivered. + + + + This user is ignoring you, they cannot see your messages in main chat and you cannot join their games. 該用戶已屏蔽你,他無法在主聊天中看見你的消息,你也不能加入他的遊戲。 - + Private message from 私人信息來自 - + %1 has left the server. %1 已經離開了伺服器. - + %1 has joined the server. %1 已經加入了伺服器. + + + Message not sent — %1 is offline. + + + + + TabModeration + + + User name + + + + + Investigate + + + + + + Reset Password + + + + + + Remove Avatar + + + + + Refresh + + + + + User Info + + + + + Name: + + + + + Registered: + + + + + Last login: + + + + + Status: + + + + + Counts: + + + + + Admin notes: + + + + + Alts + + + + + Sessions + + + + + Staff Last Logins + + + + + + User + + + + + eMail + + + + + + Client ID + + + + + Registered + + + + + + Last login + + + + + Warns + + + + + Bans + + + + + + Active + + + + + IP + + + + + Start + + + + + End + + + + + Type + + + + + Level + + + + + Unknown + + + + + + Loading... + + + + + Error loading user info. + + + + + active + + + + + inactive + + + + + admin + + + + + Reports: %1 Bans: %2 Warnings: %3 + + + + + (none) + + + + + yes + + + + + no + + + + + Admin + + + + + Developer + + + + + Moderator + + + + + Judge + + + + + Reset the password of %1? A temporary password will be generated and shown to you. The user must change it on their first login. + + + + + + Error + + + + + Password reset failed. + + + + + Password reset + + + + + Temporary password for %1: + +%2 + +Pass it to the user through a secure channel. + + + + + Remove the avatar of %1? The user will have to upload a new one. + + + + + Could not remove the avatar. + + + + + Avatar removed + + + + + The avatar of %1 has been removed. + + + + + Moderation + + + + + TabPublicDecks + + + + + Public decks of %1 + + + + + + + This user has not published any decks. + + + + + + No decks match your filters. + + + + + + Refresh + + + + + Public Decks Settings + + + + + Loading public decks… + + + + + + Open public deck + + + + + Failed to open the public deck (server response code %1). + + + + + The public deck could not be parsed. + + TabReplays - + Local file system 本地文件系統 - + Server replay storage 伺服器錄像倉庫 - - + + Watch replay 觀看錄像 - + Rename - - + + New folder - - + + Delete 刪除 - + Open replays folder - + Download replay 下載遊戲錄像 - + Toggle expiration lock 過期鎖定 - + Get replay share code - - + + Look up replay by share code - + Rename local folder - + Rename local file - + New name: - + Error - + Rename failed - + Name of new folder: - + Delete local file 刪除本地文件 - + Are you sure you want to delete the selected files? - + Are you sure you want to delete the selected replays? - + Failed to get code - - + + Either this server does not support replay sharing, or does not permit replay sharing for you. - - - + + + Failed - + Could not get replay code - + Replay Share Code - + Others can use this code to add the replay to their list of remote replays: %1 - + Copy to clipboard - + Replay share code - + Replay code found - + Replay was added, or you already had access to it. - + Replay code not found - + Failed to submit code - + Unexpected error - + Delete remote replay 刪除伺服器上的錄像 @@ -9222,65 +12018,478 @@ The more information you put in, the more specific your results will be. + + TabReport + + + Search by username, category... + + + + + All Statuses + + + + + Open + + + + + Assigned + + + + + Resolved + + + + + Dismissed + + + + + Unresolved only + + + + + Refresh + + + + + # + + + + + Time + + + + + Reporter + + + + + Reported User + + + + + Category + + + + + Game ID + + + + + Status + + + + + Assigned To + + + + + Replay + + + + + Room + + + + + Description + + + + + Chat Log Context + + + + + Chat log attached by the reporter. It is captured from their client and may be incomplete or edited. + + + + + Comments / Thread + + + + + No description. + + + + + No chat log attached. + + + + + + No comments yet. + + + + + + Type a reply... + + + + + Send + + + + + Reported User Context + + + + + User: + + + + + Account Age: + + + + + Reports: + + + + + Bans: + + + + + Warns: + + + + + Admin Notes: + + + + + Recent Reports Against User: + + + + + Report Statistics + + + + + Assign to Me + + + + + Resolve + + + + + Resolve with note... + + + + + Dismiss... + + + + + View Replay + + + + + Join Game + + + + + + + + Loading... + + + + + Failed to load reports. + + + + + Yes + + + + + No + + + + + %1 report(s) + + + + + %1 open | %2 assigned | %3 resolved | %4 dismissed + + + + + This report is closed. + + + + + Failed to load report details. + + + + + [Moderator] + + + + + [Reporter] + + + + + Assigning... + + + + + Assigned. + + + + + Assignment failed. + + + + + Dismiss Report + + + + + Resolve Report + + + + + Optional note: + + + + + Resolution note (optional): + + + + + Dismissing... + + + + + Resolving... + + + + + Done. + + + + + Action failed. + + + + + Loading replay... + + + + + No replay available for this game. + + + + + Replay opened. + + + + + Failed to parse replay. + + + + + No room recorded for this report, use the replay instead. + + + + + Attempting to join game... + + + + + Failed to send comment. + + + + + Error loading user info. + + + + + %1 days (since %2) + + + + + (none) + + + + + No previous reports against this user. + + + + + Loading statistics... + + + + + Error loading statistics. + + + + + Total: %1 reports (%2 open, %3 assigned, %4 resolved/dismissed) + + + + + Last 24h: %1 | Last 7d: %2 | Last 30d: %3 | Avg resolution: %4h + + + + + This week: %1 vs last week: %2 (%3%) + + + + + By category: + + + + + === Top Categories === + + + + + === Most Reported Users === + + + + + === Top Reporters === + + + + + Report Queue + + + TabRoom - - Friends - - - - - Online - - - - - Ignored - - - - + &Say: 説: - + Chat 聊天 - + &Room 房間 - + &Leave room 離開房間 - + &Clear chat 清除聊天記錄 - + Chat Settings... 聊天設定 - + mentioned you. 提到了你。 - + Click to view 點擊查看 - + You are flooding the chat. Please wait a couple of seconds. 你正在灌水,請等待幾秒再發言. @@ -9288,35 +12497,35 @@ The more information you put in, the more specific your results will be. TabServer - + Server 伺服器 - - - - + + + + Error 錯誤 - + Failed to join the server room: it doesn't exist on the server. - + The server thinks you are in the server room but your client is unable to display it. Try restarting your client. - + You do not have the required permission to join this server room. - + Failed to join the server room due to an unknown error: %1. @@ -9324,97 +12533,173 @@ The more information you put in, the more specific your results will be. TabSupervisor - + Deck Editor - + Visual Deck Editor - + EDHRec - + Archidekt - + Home - + &Visual Deck Storage - + Visual Database Display - + Server - + Account - + Deck Storage - + Game Replays - + Administration - + Logs - + + Report Queue + + + + + Moderation + + + + + Card Art Rules + + + + + Developer + + + + Are you sure? 你確定嗎? - + There are still open games. Are you sure you want to quit? 遊戲還在繼續, 你確定要退出嗎? + + + Connection quality over the last %n sample(s): + + - + + Last: %1 ms + + + + + Median: %1 ms + + + + + 95th percentile: %1 ms + + + + + Maximum: %1 ms + + + + + Game joins are only available on a remote server. + + + + + Could not join game %1. +%2 + + + + + Ignored user + + + + + You have ignored %1. Remove them from your ignore list to open a private chat. + + + + + Game #%1 + + + + + Game #%1 — %2 + + + + Click to view 點擊查看 - + Your buddy %1 has signed on! 你的好友%1已登錄! - + Unknown Event 未知事件 - + The server has sent you a message that your client does not understand. This message might mean there is a new version of Cockatrice available or this server is running a custom or pre-release version. @@ -9425,39 +12710,39 @@ To update your client, go to Help -> Check for Updates. 要升級你的客户端,請點擊“幫助”->“檢查更新” - + Idle Timeout 閒置超時 - + You are about to be logged out due to inactivity. 你即將因為長時間未操作而被退出。 - + Promotion 晉升 - + You have been promoted. Please log out and back in for changes to take effect. 你已被提升為版主。請退出再登陸以使修改生效。 - + Warned 被警告 - + You have received a warning due to %1. Please refrain from engaging in this activity or further actions may be taken against you. If you have any questions, please private message a moderator. 你因為%1被警告。 請杜絕此類行為否則會被採取進一步行動。如果有任何疑問,請私信版主。 - + You have received the following message from the server. (custom messages like these could be untranslated) 你收到來自伺服器的如下消息。 @@ -9477,6 +12762,34 @@ Please refrain from engaging in this activity or further actions may be taken ag + + TallyMenu + + + Tally + + + + + None + + + + + Subtypes + + + + + Total Power + + + + + Total Toughness + + + TappedOutInterface @@ -9491,6 +12804,65 @@ Please refrain from engaging in this activity or further actions may be taken ag 無法分析套牌。 + + ThemeSetupPage + + + Light + + + + + Dark + + + + + Match system + + + + + Theme: + + + + + Appearance: + + + + + Home screen background: + + + + + Save failed + + + + + Could not write the theme palette to: +%1 + + + + + Pick a Look + + + + + You can fine-tune every colour later from Settings → Appearance. + + + + + Accent colour (optional) + + + TipsOfTheDay @@ -9562,57 +12934,52 @@ Please refrain from engaging in this activity or further actions may be taken ag UserCardArtSettingsDialog - + Card Art Settings - + Type a card name... - + Card name: - - Card ProviderId: + + Printing: - + + Card + + + + + Drag to pan, scroll to zoom, arrow keys nudge, plus and minus zoom, Backspace or Esc restores. + + + + + Banner + + + + Left margin (%): - + Right margin (%): - - Vertical offset: - - - - - Zoom: - - - - - Parameters - - - - - Preview - - - - + Remove Banner Card @@ -9620,216 +12987,266 @@ Please refrain from engaging in this activity or further actions may be taken ag UserContextMenu - + User &details 用户詳細信息 - + Private &chat 私人聊天 - + Show this user's &games 顯示這個用户的遊戲 - + + View this user's &public decks + + + + Add to &buddy list 加入到好友列表 - + Remove from &buddy list 從好友列表移除 - + Add to &ignore list 加入到屏蔽列表 - + Remove from &ignore list 從屏蔽列表移除 - + Kick from &game 從遊戲中踢出 - + + Report user + + + + Warn user 警告用戶 - + View user's war&n history 查看用户警告記錄 - + Ban from &server 在伺服器中禁止 - + View user's &ban history 查看用户禁止記錄 - + &Promote user to moderator 將用戶提升為版主 - + Dem&ote user from moderator 撤銷用户版主職位 - + Promote user to &judge - + Demote user from judge 撤銷用戶的裁判身份 - + + Promote user to &developer + + + + + Demote user from de&veloper + + + + View admin notes - - + Investigate user + + + + + + Error - + This user does not exist. - + You are being ignored by %1 and can't see their games. - + Could not get %1's games. - + %1's games %1的遊戲 - - - + + + Ban History 禁止歷史 - + Ban Time;Moderator;Ban Length;Ban Reason;Visible Reason 禁止時間;版主;禁止時長;禁止理由;可見理由 - + User has never been banned. 用戶從未被禁止 - + Failed to collect ban information. 獲取禁止信息失敗。 - - - + + + Warning History 警告歷史 - + Warning Time;Moderator;User Name;Reason 警告時間;版主;用户名;理由 - + User has never been warned. 用戶從未被警告 - + Failed to collect warning information. 獲取警告信息失敗。 - + Failed to get admin notes. - - + + Success 成功 - + Successfully promoted user. 提升用戶成功。 - + Successfully demoted user. 撤銷用戶成功。 - + + &Invite to Game + + + + + Join my game (#%1): + + + + + Join my game "%1" (#%2): + + + + + Ignore list + + + + + %1 has been added to your ignore list. + + + + Kick Player - + Are you sure you want to kick this player from the game? - - - + + + Failed 失敗 - + Failed to promote user. 提升用戶失敗。 - + Failed to demote user. 撤銷用戶失敗。 - + Copy hash to clipboard 複製哈希到剪貼板 - + Remove this user's messages @@ -9867,61 +13284,61 @@ Please refrain from engaging in this activity or further actions may be taken ag 管理員 - + Moderator 版主 - + Registered user 註冊用戶 - - + + Unregistered user 未註冊用戶 - + Judge 裁判 - + Unknown 未知的 - + The entered password does not match your account. - - - - + + + + Information 信息 - + User information updated. 用戶信息已更新。 - - - - - - + + + + + - - - + + + + Error 錯誤 @@ -9945,283 +13362,292 @@ Please refrain from engaging in this activity or further actions may be taken ag Edit Banner Card + + + Developer + + - + %n Year(s), amount of years (only shown if more than 0) - + %10%n Day(s) %20 amount of years (if more than 0), amount of days, date in local short format - + Enter Password 輸入密碼 - + Password verification is required in order to change your email address - - - + + + An error occurred while trying to update your user information. - + The selected card is blacklisted on this server or another error occurred. - + Banner card removed. - + Banner card updated. - + This server does not permit you to update your user informations. 該伺服器不允許你更新你的用戶信息。 - + Password changed. 密碼已修改。 - + This server does not permit you to change your password. 該伺服器不允許你修改你的密碼。 - + The new password is too short. 新密碼太短。 - + The old password is incorrect. 原密碼不正確。 - + Avatar updated. 頭像已更新。 - + This server does not permit you to update your avatar. 該伺服器不允許你更新你的頭像。 - + An error occured while trying to update your avatar. - - - This server does not permit you to update your user informations. - An error occured while trying to updater your avatar. - 該伺服器不允許你更新你的用戶信息。 - UserInfoPopup - - + + Games - + Chat - + Open private chat - + Profile - + View user profile - + Show this user's games - + + Invite + + + + + Invite to your game + + + + − Buddy - + Remove from buddy list - + + Buddy - + Add to buddy list - + − Ignore - + Remove from ignore list - + + Ignore - + Add to ignore list - + Ban - + Ban from server - + Warn - + Warn user - + Ban log - + View ban history - + Warn log - + View warning history - + Notes - + View admin notes - + − Mod - + Demote from moderator - + + Mod - + Promote to moderator - + − Judge - + Demote from judge - + + Judge - + Promote to judge - + Join game - + Spectate - - + + Loading games… - + Could not load games. - + No active games. @@ -10229,223 +13655,344 @@ Please refrain from engaging in this activity or further actions may be taken ag UserInterfaceSettingsPage - + General interface settings 通用接口設定 - + &Double-click cards to play them (instead of single-click) 雙擊卡牌開始 (而不是單擊開始) - + &Clicking plays all selected cards (instead of just the clicked card) - + &Play all nonlands onto the stack (not the battlefield) by default 默認將所有非地牌加入堆疊(不是戰場) - + Do not delete &arrows inside of subphases - + Close card view window when last card is removed - + Auto focus search bar when card view window is opened - + Annotate card text on tokens 用卡牌信息給衍生物標注 - + Show selection count during drag selection - + Show total selection count - - Show subtype breakdown in selection tally + + + Disabled - + + + Enabled + + + + + + Automatic + + + + + CommanderSpellbook bracket names + + + + + Official Commander bracket names (approximate) + + + + Use tear-off menus, allowing right click menus to persist on screen 使用浮動菜單,允許右鍵單擊菜單保留在屏幕上 - + Keep game chat focused when clicking in game (Note: disables card view search bar) - + Notifications settings 通知設定 - + Enable notifications in taskbar 開啓任務欄提醒 - + Notify in the taskbar for game events while you are spectating 觀看時在任務欄提示遊戲信息 - + Notify in the taskbar when users in your buddy list connect 在任務欄中提示好友連接到伺服器 - + Animation settings 動畫設定 - + + &Enable all animations + + + + + &Disable all animations + + + + &Tap/untap animation 橫置/重置 動畫 - + + &Arrow draw animation + + + + + Life counter flash + + + + + Battlefield flash on damage + + + + Deck editor/storage settings - + Open deck in new tab by default - + + Show card counts in Visual Deck Editor + + + + Use visual deck storage in game lobby - + Use selection animation for Visual Deck Storage - + When adding a tag in the visual deck storage to a .txt deck: - - do nothing + + Do nothing - - ask to convert to .cod + + Ask to convert to .cod - - always convert to .cod + + Always convert to .cod - + Default deck editor type - + Classic Deck Editor - + Visual Deck Editor - + + Visual deck editor startup tab + + + + + Context + + + + + Deck display + + + + + Database display + + + + + Context mode: New decks open on the database display, existing decks open on the deck view. + + + + + CommanderSpellbook integration to estimate commander bracket + + + + + Bracket naming + + + + Replay settings - + Buffer time for backwards skip via shortcut: + + UserListPanelWidget + + + Search users... + + + UserListWidget - + Users connected to server: %1 - + Users in this room: %1 - + Buddies online: %1 / %2 - + Ignored users online: %1 / %2 + + + Buddies + + + + + Online + + + + + Ignored + + + + + %1 %2 (%3/%4) + + + + + %1 %2 (%3) + + UtilityMenu - + Increment all card counters - + &Untap all permanents - + R&oll die... - + Flip coin - + &Create token... - + C&reate another token - + Cr&eate predefined token @@ -10482,22 +14029,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterSaveLoadWidget - + Search filter... - + Save Filter - + Save all currently applied filters to a file - + Enter filename... @@ -10505,72 +14052,72 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayFilterToolbarWidget - + Sort by - + Filter by - + Save and load filters - + Filter by exact card name - + Filter by card main-type - + Filter by card sub-type - + Filter by set - + Filter by format legality - + Save/Load - + Name - + Main Type - + Sub Type - + Sets - + Formats @@ -10672,7 +14219,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayRecentSetFilterSettingsWidget - + Filter to most recent sets @@ -10680,19 +14227,19 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplaySetFilterWidget - + Search sets... - - + + Mode: Exact Match - - + + Mode: Includes @@ -10738,28 +14285,28 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDatabaseDisplayWidget - + Search by card name (or search expressions) - - + + Visual - + Loading database ... - + Clear all filters - + Table @@ -10767,43 +14314,43 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckDisplayOptionsWidget - + Group by: - + Change how cards are divided into categories/groups. - + Sort by: - + Click and drag to change the sort order within the groups - + Configure how cards are sorted within their groups - - + + Toggle Layout: Overlap - + Change how cards are displayed within zones (i.e. overlapped or fully visible.) - + Toggle Layout: Flat @@ -10819,12 +14366,12 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorSampleHandWidget - + Draw a new sample hand - + Sample hand size @@ -10832,17 +14379,17 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckEditorWidget - + Type a card name here for suggestions from the database... - + Quick search and add card - + Search for closest match in the database (with auto-suggestions) and add preferred printing to the deck on pressing enter @@ -10850,7 +14397,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageFolderDisplayWidget - + Deck Storage @@ -10858,52 +14405,57 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageQuickSettingsWidget - + Show Folders - + Show Tag Filter - + Show Color Identity - + Show Tags On Deck Previews - + + Show Upload Time + + + + Show Banner Card Selection Option - + Draw unused Color Identities - + Unused Color Identities Opacity - + Deck tooltip: - + None - + Filepath @@ -10911,7 +14463,7 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSearchWidget - + Search by filename (or search expression) @@ -10919,22 +14471,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageSortWidget - + Sort Alphabetically (Deck Name) - + Sort Alphabetically (Filename) - + Sort by Last Modified - + Sort by Last Loaded @@ -10942,17 +14494,22 @@ Please refrain from engaging in this activity or further actions may be taken ag VisualDeckStorageWidget - + Loading database ... - + Refresh loaded files - + + Select decks to share + + + + Visual Deck Storage Settings @@ -10960,177 +14517,202 @@ Please refrain from engaging in this activity or further actions may be taken ag WarningDialog - + Which warning would you like to send? 你想發出那種警告? - + Redact all messages from this user in all rooms - + &OK 確認 - + &Cancel 取消 - + Warn user for misconduct 警告用戶的不當行為 - - + + Error 錯誤 - + User name to send a warning to can not be blank, please specify a user to warn. 發送警告的用戶名不能為空,請提供用戶名以供警告。 - + Warning to use can not be blank, please select a valid warning to send. 警告不能為空,請選擇警告以供發送。 + + + %1 (IL %2) + + + + + WelcomePage + + + Welcome! + + + + + Let's get you set up. This will only take a minute — we'll grab the card database, pick a look you like, and get you ready to connect to a server. + +You can change any of this later from Settings. + + + + + Language: + + WndSets - + Move selected set to the top 將選擇的系列移到頂端 - + Move selected set up 將選擇的系列上移 - + Move selected set down 將選擇的系列下移 - + Move selected set to the bottom 將選擇的系列移到底部 - - Search by set name, code, or type - 通過系列名、代碼或類型搜索 - - - + Default order 默認順序 - + Restore original art priority order 恢復初始優先順序 - + Enable all sets 啓用所有系列 - + Disable all sets 禁用所有系列 - + Enable selected set(s) 啓用所選系列 - + Disable selected set(s) 禁用所選系列 - + Deck Editor 套牌編輯器 - + Use CTRL+A to select all sets in the view. - + Only cards in enabled sets will appear in the card list of the deck editor. - + Image priority is decided in the following order: - + first the CUSTOM Folder (%1), then the Enabled Sets in this dialog (Top to Bottom) %1 is a link to the wiki - + Include cards rebalanced for Alchemy [requires restart] - + Card Art 牌張風格 - + + Search by set name, code, type, or release date + + + + How to use custom card art 如何使用自定義卡牌圖片 - + Hints 提示 - + Note 注 - + Sorting by column allows you to find a set while not changing set priority. 按列排序允許您在不更改系列優先級的情況下查找系列。 - + To enable ordering again, click the column header until this message disappears. 要再次啓用排序,請單擊列標題,直到此消息消失。 - + Use the current sorting as the set priority instead 使用當前系列的優先級排序 - + Sorts the set priority using the same column 使用同一列系列的優先級排序 - + Manage sets 系列管理 @@ -11138,93 +14720,85 @@ Please refrain from engaging in this activity or further actions may be taken ag ZoneViewWidget - + Search by card name (or search expressions) - + Ungrouped - + Group by Type - + Group by Mana Value - + Group by Color - + Unsorted - + Sort by Name - + Sort by Type - + Sort by Mana Cost - + Sort by Colors - + Sort by P/T - + Sort by Set - + shuffle when closing 當關閉界面時洗牌 - + pile view 柱形圖 - - i18n - - - English - 繁體中文 (Chinese Traditional) - - main - + Connect on startup 啓動連接 - + Debug to file 調試至文件 @@ -12008,6 +15582,16 @@ Please refrain from engaging in this activity or further actions may be taken ag Graveyard 墳墓場 + + + Report Queue + + + + + Moderation + + diff --git a/format.sh b/format.sh index 9e3a6069b..fba76e93a 100755 --- a/format.sh +++ b/format.sh @@ -19,7 +19,8 @@ libcockatrice_* \ "oracle/src" \ "servatrice/src" \ "cmake/pch" \ -"tests") +"tests" \ +"tools/installer-holder-probe") exclude=("libcockatrice_rng/libcockatrice/rng/sfmt/" \ "libcockatrice_utility/libcockatrice/utility/peglib.h" \ "oracle/src/lzma/" \ @@ -32,6 +33,7 @@ cmakefile="CMakeLists.txt" cmakedir="cmake/.*\\.cmake" cmakeinclude=("cmake/gtest-CMakeLists.txt.in") scripts="*.sh" +scriptsrx="[^/]*\\.sh$" color="--" verbosity=0 sep="----------" @@ -253,7 +255,7 @@ if [[ $branch ]]; then if [[ $do_shell ]]; then shell_names=() for name in "${basenames[@]}"; do - filerx="(^|/)$scripts$" + filerx="(^|/)$scriptsrx" if [[ $name =~ $filerx ]]; then shell_names+=("$name") fi diff --git a/libcockatrice_card/CMakeLists.txt b/libcockatrice_card/CMakeLists.txt index b3583522c..d2e6ee83a 100644 --- a/libcockatrice_card/CMakeLists.txt +++ b/libcockatrice_card/CMakeLists.txt @@ -1,7 +1,3 @@ -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - set(HEADERS libcockatrice/card/card_info.h libcockatrice/card/card_info_comparator.h @@ -21,11 +17,9 @@ set(HEADERS libcockatrice/card/relation/card_relation.h ) -qt6_wrap_cpp(MOC_SOURCES ${HEADERS}) - add_library( libcockatrice_card STATIC - ${MOC_SOURCES} + ${HEADERS} libcockatrice/card/card_info.cpp libcockatrice/card/card_info_comparator.cpp libcockatrice/card/card_localization.cpp diff --git a/libcockatrice_card/libcockatrice/card/card_info.cpp b/libcockatrice_card/libcockatrice/card/card_info.cpp index bd33f18e7..90bfd3f44 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.cpp +++ b/libcockatrice_card/libcockatrice/card/card_info.cpp @@ -6,6 +6,7 @@ #include "relation/card_relation.h" #include "set/card_set.h" +#include #include #include #include @@ -19,6 +20,18 @@ const QHash &CardInfo::getPropertiesHash() const return properties.getProperties(); } +void CardInfo::notifyChanged() +{ + const CardInfoPtr self = sharedFromThis(); + Q_ASSERT_X(self, "CardInfo::notifyChanged", "every CardInfo is owned by a QSharedPointer"); + if (!self) { + qCWarning(CardInfoLog) << "notifyChanged() on an unowned CardInfo" << name << "- observers stay stale"; + return; + } + + emit cardInfoChanged(self); +} + void CardInfo::setProperty(const QString &_name, const QString &_value) { bool changed = properties.insert(_name, _value); @@ -26,7 +39,7 @@ void CardInfo::setProperty(const QString &_name, const QString &_value) return; } - emit cardInfoChanged(smartThis); + notifyChanged(); } CardInfo::CardInfo(const QString &_name, @@ -67,10 +80,16 @@ CardInfo::CardInfo(const QString &_name, reverseRelatedCards(_reverseRelatedCards), setsToPrintings(std::move(_sets)), uiAttributes(_uiAttributes), altNames(std::move(_altNames)) { - // propertiesBlob is materialized lazily on first query; simpleName and - // altNames are supplied by the caller (binary cache). Only the set-name - // display list still depends on the current enabled-set state. + // propertiesBlob is materialized lazily on first query. The binary cache supplies simpleName + // and altNames to skip the Unicode normalization and the printing scan; the XML parsers leave + // them empty and get the same derived state the property-hash constructor computes. + if (simpleName.isEmpty()) { + simpleName = simplifyName(name); + } refreshCachedSetNames(); + if (altNames.isEmpty()) { + refreshCachedAltNames(); + } } CardInfoPtr CardInfo::newInstance(const QString &_name) @@ -91,7 +110,6 @@ CardInfoPtr CardInfo::newInstance(const QString &_name, { CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, _properties, _relatedCards, _reverseRelatedCards, _sets, _uiAttributes, std::move(_localizedNames), std::move(_localizedTexts))); - ptr->setSmartPointer(ptr); for (const auto &printings : _sets) { for (const PrintingInfo &printing : printings) { @@ -120,7 +138,6 @@ CardInfoPtr CardInfo::newInstance(const QString &_name, CardInfoPtr ptr(new CardInfo(_name, _text, _isToken, std::move(_propertiesBlob), _relatedCards, _reverseRelatedCards, _sets, _uiAttributes, std::move(_simpleName), std::move(_altNames), std::move(_localizedNames), std::move(_localizedTexts))); - ptr->setSmartPointer(ptr); if (_appendToSets) { for (const auto &printings : _sets) { @@ -163,8 +180,15 @@ bool CardInfo::isLegalInFormat(const QString &format) const void CardInfo::addToSet(const CardSetPtr &_set, const PrintingInfo &_info) { - if (!_set->contains(smartThis)) { - _set->append(smartThis); + const CardInfoPtr self = sharedFromThis(); + Q_ASSERT_X(self, "CardInfo::addToSet", "every CardInfo is owned by a QSharedPointer"); + if (!self) { + qCWarning(CardInfoLog) << "addToSet() on an unowned CardInfo" << name << "- the set will not list it"; + return; + } + + if (!_set->contains(self)) { + _set->append(self); } if (!setsToPrintings[_set->getShortName()].contains(_info)) { setsToPrintings[_set->getShortName()].append(_info); @@ -182,7 +206,7 @@ void CardInfo::combineLegalities(const QHash &props) properties.insert(it.key(), it.value()); } } - emit cardInfoChanged(smartThis); + notifyChanged(); } void CardInfo::refreshCachedSets() @@ -245,7 +269,7 @@ QString CardInfo::simplifyName(const QString &name) QChar CardInfo::getColorChar() const { - QString colors = getColors(); + const QString colors = getColors(); switch (colors.size()) { case 0: return QChar(); @@ -264,6 +288,13 @@ void CardInfo::resetReverseRelatedCards2Me() reverseRelatedCardsToMe = QList(); } +void CardInfo::clearDirectRelations() +{ + relatedCards = QList(); + reverseRelatedCards = QList(); + reverseRelatedCardsToMe = QList(); +} + // Back-compatibility methods. Remove ASAP QString CardInfo::getCardType() const { diff --git a/libcockatrice_card/libcockatrice/card/card_info.h b/libcockatrice_card/libcockatrice/card/card_info.h index 356da0546..5452ef98b 100644 --- a/libcockatrice_card/libcockatrice/card/card_info.h +++ b/libcockatrice_card/libcockatrice/card/card_info.h @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -50,7 +51,7 @@ Q_DECLARE_METATYPE(CardInfoPtr) * Each CardInfo may belong to multiple sets through its printings, and can * be related to other cards through defined relationships. */ -class CardInfo : public QObject +class CardInfo : public QObject, public QEnableSharedFromThis { Q_OBJECT @@ -74,11 +75,10 @@ private: * @anchor PrivateCardProperties */ ///@{ - CardInfoPtr smartThis; ///< Smart pointer to self for safe cross-references. - QString name; ///< Full name of the card. - QString simpleName; ///< Simplified name for fuzzy matching. - QString text; ///< Text description or rules text of the card. - bool isToken; ///< Whether this card is a token or not. + QString name; ///< Full name of the card. + QString simpleName; ///< Simplified name for fuzzy matching. + QString text; ///< Text description or rules text of the card. + bool isToken; ///< Whether this card is a token or not. QMap localizedNames; ///< Localized card names, keyed by language code. QMap localizedTexts; ///< Localized rules text, keyed by language code. @@ -121,14 +121,12 @@ public: QMap _localizedTexts = {}); /** - * @brief Constructs a CardInfo from a cache snapshot with precomputed derived - * state. + * @brief Constructs a CardInfo from a serialized properties blob. * - * Used by the binary cache reader to skip recomputing @p _simpleName and - * @p _altNames (which otherwise require a Unicode normalization and a full - * printing scan). Properties are supplied as a pre-serialized @p _propertiesBlob - * so the QHash is not built at load time (it is materialized on first - * query). + * Properties stay in @p _propertiesBlob so the QHash is not + * built at load time (it is materialized on first query). @p _simpleName and + * @p _altNames are filled in when empty; the binary cache supplies them to + * skip the Unicode normalization and the printing scan. * * @param _name The card name. * @param _text Rules text or description of the card. @@ -138,8 +136,8 @@ public: * @param _reverseRelatedCards Reverse relationships. * @param _sets Printing information per set. * @param _uiAttributes Attributes that affect display and game logic. - * @param _simpleName Precomputed simplified name. - * @param _altNames Precomputed alternate names. + * @param _simpleName Precomputed simplified name; derived from @p _name when empty. + * @param _altNames Precomputed alternate names; derived from the printings when empty. * @param _localizedNames Localized card names, keyed by language code. * @param _localizedTexts Localized rules text, keyed by language code. */ @@ -164,13 +162,14 @@ public: * @param other Another CardInfo to copy. */ CardInfo(const CardInfo &other) - : QObject(other.parent()), name(other.name), simpleName(other.simpleName), text(other.text), - isToken(other.isToken), localizedNames(other.localizedNames), localizedTexts(other.localizedTexts), - properties(other.properties), relatedCards(other.relatedCards), + : QObject(other.parent()), QEnableSharedFromThis(), name(other.name), simpleName(other.simpleName), + text(other.text), isToken(other.isToken), localizedNames(other.localizedNames), + localizedTexts(other.localizedTexts), properties(other.properties), relatedCards(other.relatedCards), reverseRelatedCards(other.reverseRelatedCards), reverseRelatedCardsToMe(other.reverseRelatedCardsToMe), setsToPrintings(other.setsToPrintings), uiAttributes(other.uiAttributes), setsNames(other.setsNames), altNames(other.altNames) { + // The copy deliberately starts unowned: the QSharedPointer that wraps it registers the self reference. } /** @@ -210,20 +209,26 @@ public: QMap _localizedTexts = {}); /** - * @brief Creates a new instance from a cache snapshot with precomputed - * derived state. + * @brief Creates a new instance from a serialized properties blob. + * + * The properties stay in blob form, so the QHash is not + * built at load time (it is materialized on first query). The derived state + * is required so it is never recomputed here, and the overload cannot be a + * candidate for a shorter call that passes a property hash instead of a blob. * * @param _name Name of the card. * @param _text Rules text or description. * @param _isToken Token flag. - * @param _propertiesBlob Pre-serialized properties blob (as written by the cache). + * @param _propertiesBlob Serialized properties blob. * @param _relatedCards Forward relationships. * @param _reverseRelatedCards Reverse relationships. * @param _sets Printing information per set. * @param _uiAttributes Attributes that affect display and game logic. - * @param _simpleName Precomputed simplified name. - * @param _altNames Precomputed alternate names. - * @param _appendToSets When true (default), the card is appended to each of + * @param _simpleName Precomputed simplified name; pass {} to derive it from + * @p _name. + * @param _altNames Precomputed alternate names; pass {} to derive them from + * the printings. + * @param _appendToSets When true, the card is appended to each of * its CardSets. Pass false when building cards in parallel so the * (non-thread-safe) set membership is populated in a later * single-threaded pass. @@ -241,34 +246,20 @@ public: UiAttributes _uiAttributes, QString _simpleName, QSet _altNames, - bool _appendToSets = true, + bool _appendToSets, QMap _localizedNames = {}, QMap _localizedTexts = {}); /** * @brief Clones the current CardInfo instance. * - * Uses the copy constructor and ensures the smart pointer is properly set. + * Uses the copy constructor; the copy is managed by its own shared pointer. * * @return Shared pointer to the cloned CardInfo. */ [[nodiscard]] CardInfoPtr clone() const { - auto newCardInfo = CardInfoPtr(new CardInfo(*this)); - newCardInfo->setSmartPointer(newCardInfo); // Set the smart pointer for the new instance - return newCardInfo; - } - - /** - * @brief Sets the internal smart pointer to self. - * - * Used internally to allow safe cross-references among CardInfo and CardSet. - * - * @param _ptr Shared pointer pointing to this instance. - */ - void setSmartPointer(CardInfoPtr _ptr) - { - smartThis = std::move(_ptr); + return CardInfoPtr(new CardInfo(*this)); } /** @name Basic Properties Accessors */ //@{ @@ -280,7 +271,7 @@ public: { return simpleName; } - const QSet &getAltNames() + const QSet &getAltNames() const { return altNames; } @@ -291,7 +282,7 @@ public: void setText(const QString &_text) { text = _text; - emit cardInfoChanged(smartThis); + notifyChanged(); } /** @@ -354,7 +345,7 @@ public: return; } localizedNames.insert(lang, _localizedName); - emit cardInfoChanged(smartThis); + notifyChanged(); } /** @@ -369,7 +360,7 @@ public: return; } localizedTexts.insert(lang, _localizedText); - emit cardInfoChanged(smartThis); + notifyChanged(); } /** @@ -389,18 +380,34 @@ public: } [[nodiscard]] QStringList getProperties() const { - return getPropertiesHash().keys(); + return properties.keys(); } + + /** + * @brief Returns the properties in their serialized blob form. + * + * Cheap while the properties were never materialized (the cache and the XML parsers pass the blob through). + */ + [[nodiscard]] QByteArray getPropertiesBlob() const + { + return properties.serialized(); + } + + /** + * @brief Returns the materialized property hash, forcing deserialization of the blob. + * + * Prefer getProperty(), hasProperty() or getPropertiesBlob(), which avoid building the hash. + */ [[nodiscard]] const QHash &getPropertiesHash() const; [[nodiscard]] QString getProperty(const QString &propertyName) const { - return getPropertiesHash().value(propertyName); + return properties.value(propertyName); } void setProperty(const QString &_name, const QString &_value); [[nodiscard]] bool hasProperty(const QString &propertyName) const { - return getPropertiesHash().contains(propertyName); + return properties.contains(propertyName); } [[nodiscard]] const SetToPrintingsMap &getSets() const { @@ -433,6 +440,14 @@ public: return result; } void resetReverseRelatedCards2Me(); + /** + * @brief Clears the raw relation lists without deleting the CardRelation objects. + * + * Callers that schedule the relations for deletion (e.g. CardDatabase::removeCard()) + * must use this to drop the now-dangling pointers from this CardInfo, which may + * outlive them through external shared pointers. + */ + void clearDirectRelations(); void addReverseRelatedCards2Me(CardRelation *cardRelation) { reverseRelatedCardsToMe.append(cardRelation); @@ -524,6 +539,14 @@ public: static QString simplifyName(const QString &name); private: + /** + * @brief Emits cardInfoChanged() with a strong reference to this card. + * + * A CardInfo is always owned by a QSharedPointer, so the self reference is always available; a released + * build that somehow finds it missing warns rather than emitting a null card. + */ + void notifyChanged(); + /** * @brief Refreshes the cached, human-readable list of set names. * diff --git a/libcockatrice_card/libcockatrice/card/database/card_database.cpp b/libcockatrice_card/libcockatrice/card/database/card_database.cpp index 7b0d5b2f9..ed097ef3b 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database.cpp @@ -53,13 +53,26 @@ CardDatabase::~CardDatabase() clear(); } +void CardDatabase::detachSetMembership() +{ + for (const auto &set : sets) { + if (set) { + set->clear(); + } + } +} + void CardDatabase::clear() { QMutexLocker locker(clearDatabaseMutex); + // The sets hold strong card references, so empty them before dropping the cards: this breaks the + // CardInfo <-> CardSet cycle and spares the per-card detach over the whole database. + detachSetMembership(); + for (const auto &card : cards.values()) { if (card) { - removeCard(card); + unindexCard(card); } } @@ -156,6 +169,23 @@ void CardDatabase::removeCard(const CardInfoPtr &card) return; } + // The set holds a strong reference back to the card, so detach it: leaving it there keeps both + // objects alive after the database drops the card. newInstance() appends through the first + // printing of every set, so detaching through that same printing mirrors the append. + for (const auto &printings : card->getSets()) { + if (printings.isEmpty()) { + continue; + } + if (const CardSetPtr set = printings.first().getSet()) { + set->removeAll(card); + } + } + + unindexCard(card); +} + +void CardDatabase::unindexCard(const CardInfoPtr &card) +{ for (auto *cardRelation : card->getRelatedCards()) { cardRelation->deleteLater(); } @@ -168,6 +198,10 @@ void CardDatabase::removeCard(const CardInfoPtr &card) cardRelation->deleteLater(); } + // The relations are only scheduled for deletion; the CardInfo may outlive + // them through an external shared pointer, so drop the raw pointers now. + card->clearDirectRelations(); + QMutexLocker locker(removeCardMutex); cards.remove(card->getName()); simpleNameCards.remove(card->getSimpleName()); @@ -271,8 +305,15 @@ void CardDatabase::swapInDatabaseData(CardDatabaseData data) for (auto *rel : card->getReverseRelatedCards2Me()) { rel->deleteLater(); } + // Replacement cards take over the database; old CardInfo objects may + // still be referenced by the UI, so clear their dangling relations. + card->clearDirectRelations(); } + // Break the CardSet <-> CardInfo cycle before replacing the containers, otherwise the previous + // snapshot survives the reload through the sets' strong card lists. + detachSetMembership(); + cards = std::move(data.cards); simpleNameCards = std::move(data.simpleNameCards); sets = std::move(data.sets); diff --git a/libcockatrice_card/libcockatrice/card/database/card_database.h b/libcockatrice_card/libcockatrice/card/database/card_database.h index 9f0d67b67..5c1cb43c1 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database.h +++ b/libcockatrice_card/libcockatrice/card/database/card_database.h @@ -74,6 +74,23 @@ private: */ void refreshCachedReverseRelatedCards(CardNameMap &cardMap); + /** + * @brief Empties every set, dropping the strong card references they hold. + * + * A card keeps a CardSetPtr through its printings while its set keeps a strong CardInfoPtr, so the + * two form a reference cycle. Dropping a whole snapshot has to break that cycle once, up front, + * instead of relying on the per-card detach in removeCard(). + */ + void detachSetMembership(); + + /** + * @brief Drops a card from the database indexes and notifies observers. + * + * Callers are responsible for detaching the card from its sets first. + * @param card Pointer to the card to unindex. + */ + void unindexCard(const CardInfoPtr &card); + /** @brief Mutexes for thread safety. */ QBasicMutex *clearDatabaseMutex = new QBasicMutex(), *addCardMutex = new QBasicMutex(), *removeCardMutex = new QBasicMutex(); diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_cache.cpp b/libcockatrice_card/libcockatrice/card/database/card_database_cache.cpp index 4ab4d1fca..611b24ad8 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_cache.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database_cache.cpp @@ -53,13 +53,10 @@ QString readString(QDataStream &in) // Stores a QHash as a single pre-serialized blob. The reader keeps the // blob as-is and materializes the QHash lazily on first query, which is // what removes the allocation storm from database load (see card_info.cpp / -// printing_info.cpp). -void writeHashBlob(QDataStream &out, const QHash &h) +// printing_info.cpp). Writing the already-serialized blob also keeps cache writes from +// materializing every card's property hash. +void writeBlob(QDataStream &out, const QByteArray &blob) { - QByteArray blob; - QDataStream blobOut(&blob, QIODevice::WriteOnly); - blobOut.setVersion(QDataStream::Qt_6_4); - blobOut << h; out << static_cast(blob.size()); out.writeRawData(blob.constData(), blob.size()); } @@ -156,7 +153,7 @@ CardRelation *readRelation(QDataStream &in) void writePrinting(QDataStream &out, const PrintingInfo &p) { writeString(out, p.getSet() ? p.getSet()->getShortName() : QString()); - writeHashBlob(out, p.getPropertiesHash()); + writeBlob(out, p.getPropertiesBlob()); } PrintingInfo readPrinting(QDataStream &in, const SetNameMap &sets) @@ -198,7 +195,7 @@ void writeCard(QDataStream &out, const CardInfoPtr &card) writeString(out, card->getName()); writeString(out, card->getText()); out << card->getIsToken(); - writeHashBlob(out, card->getPropertiesHash()); + writeBlob(out, card->getPropertiesBlob()); CardInfo::UiAttributes ui = card->getUiAttributes(); out << ui.cipt; @@ -210,7 +207,7 @@ void writeCard(QDataStream &out, const CardInfoPtr &card) // per-printing alt-name scan entirely. writeString(out, card->getSimpleName()); { - const QSet altNames = card->getAltNames(); + const QSet &altNames = card->getAltNames(); out << static_cast(altNames.size()); for (const QString &alt : altNames) { writeString(out, alt); @@ -218,7 +215,7 @@ void writeCard(QDataStream &out, const CardInfoPtr &card) } // setsToPrintings - const SetToPrintingsMap sets = card->getSets(); + const auto &sets = card->getSets(); out << static_cast(sets.size()); for (auto it = sets.constBegin(); it != sets.constEnd(); ++it) { writeString(out, it.key()); diff --git a/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp b/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp index 2fad5212d..8df0d2e20 100644 --- a/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp +++ b/libcockatrice_card/libcockatrice/card/database/card_database_querier.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include CardDatabaseQuerier::CardDatabaseQuerier(QObject *_parent, @@ -52,6 +53,7 @@ CardInfoPtr CardDatabaseQuerier::getCardInfo(const QString &cardName) const QList CardDatabaseQuerier::getCardInfos(const QStringList &cardNames) const { QList cardInfos; + cardInfos.reserve(cardNames.size()); for (const QString &cardName : cardNames) { CardInfoPtr ptr = db->cards.value(cardName); if (ptr) { @@ -90,6 +92,7 @@ CardInfoPtr CardDatabaseQuerier::lookupCardByName(const QString &name) const QList CardDatabaseQuerier::getCards(const QList &cardRefs) const { QList cards; + cards.reserve(cardRefs.size()); for (const auto &cardRef : cardRefs) { ExactCard card = getCard(cardRef); if (card) { @@ -133,6 +136,10 @@ ExactCard CardDatabaseQuerier::getCard(const CardRef &cardRef) const ExactCard CardDatabaseQuerier::guessCard(const CardRef &cardRef) const { auto card = lookupCardByName(cardRef.name); + if (!card) { + return {}; + } + auto printing = cardRef.providerId.isEmpty() ? getPreferredPrinting(card) : findPrintingWithId(card, cardRef.providerId); @@ -145,10 +152,10 @@ ExactCard CardDatabaseQuerier::getRandomCard() const return {}; } - const auto keys = db->cards.keys(); - int randomIndex = QRandomGenerator::global()->bounded(keys.size()); - const QString &randomKey = keys.at(randomIndex); - CardInfoPtr randomCard = getCardInfo(randomKey); + const auto randomIndex = QRandomGenerator::global()->bounded(db->cards.size()); + auto it = db->cards.constBegin(); + std::advance(it, randomIndex); + CardInfoPtr randomCard = it.value(); return ExactCard{randomCard, getPreferredPrinting(randomCard)}; } @@ -173,10 +180,14 @@ ExactCard CardDatabaseQuerier::getCardFromSameSet(const QString &cardName, const * * @param cardInfo The CardInfo to search * @param providerId The uuid to look for - * @return The PrintingInfo, or a default-constructed PrintingInfo if not found. + * @return The PrintingInfo, or a default-constructed PrintingInfo if not found or if cardInfo is null. */ PrintingInfo CardDatabaseQuerier::findPrintingWithId(const CardInfoPtr &cardInfo, const QString &providerId) const { + if (!cardInfo) { + return PrintingInfo(); + } + for (const auto &printings : cardInfo->getSets()) { for (const auto &printing : printings) { if (printing.getUuid() == providerId) { @@ -207,20 +218,23 @@ PrintingInfo CardDatabaseQuerier::getSpecificPrinting(const QString &cardName, return PrintingInfo(nullptr); } - SetToPrintingsMap setMap = cardInfo->getSets(); + const auto &setMap = cardInfo->getSets(); if (setMap.empty()) { return PrintingInfo(nullptr); } for (const auto &printings : setMap) { - for (auto &cardInfoForSet : printings) { + for (const auto &cardInfoForSet : printings) { + const CardSetPtr &set = cardInfoForSet.getSet(); + if (!set) { + continue; + } if (!collectorNumber.isEmpty()) { - if (cardInfoForSet.getSet()->getShortName() == setShortName && - cardInfoForSet.getProperty("num") == collectorNumber) { + if (set->getShortName() == setShortName && cardInfoForSet.getProperty("num") == collectorNumber) { return cardInfoForSet; } } else { - if (cardInfoForSet.getSet()->getShortName() == setShortName) { + if (set->getShortName() == setShortName) { return cardInfoForSet; } } @@ -276,10 +290,10 @@ PrintingInfo CardDatabaseQuerier::getPreferredPrinting(const CardInfoPtr &cardIn const auto &pinnedPrintingProviderId = prefs->getCardPreferenceOverride(cardInfo->getName()); if (!pinnedPrintingProviderId.isEmpty()) { - return getSpecificPrinting({cardInfo->getName(), pinnedPrintingProviderId}); + return findPrintingWithId(cardInfo, pinnedPrintingProviderId); } - SetToPrintingsMap setMap = cardInfo->getSets(); + const auto &setMap = cardInfo->getSets(); if (setMap.empty()) { return PrintingInfo(nullptr); } @@ -289,8 +303,8 @@ PrintingInfo CardDatabaseQuerier::getPreferredPrinting(const CardInfoPtr &cardIn SetPriorityComparator comparator; for (const auto &printings : setMap) { - for (auto &printing : printings) { - CardSetPtr currentSet = printing.getSet(); + for (const auto &printing : printings) { + const CardSetPtr ¤tSet = printing.getSet(); if (!preferredSet || comparator(currentSet, preferredSet)) { preferredSet = currentSet; preferredPrinting = printing; @@ -307,17 +321,18 @@ PrintingInfo CardDatabaseQuerier::getPreferredPrinting(const CardInfoPtr &cardIn QString CardDatabaseQuerier::getPreferredPrintingProviderId(const QString &cardName) const { - PrintingInfo preferredPrinting = getPreferredPrinting(cardName); - QString uuid = preferredPrinting.getUuid(); + CardInfoPtr cardInfo = getCardInfo(cardName); + if (!cardInfo) { + return cardName; + } + + PrintingInfo preferredPrinting = getPreferredPrinting(cardInfo); + const QString &uuid = preferredPrinting.getUuid(); if (!uuid.isEmpty()) { return uuid; } - CardInfoPtr defaultCardInfo = getCardInfo(cardName); - if (defaultCardInfo.isNull()) { - return cardName; - } - return defaultCardInfo->getName(); + return cardInfo->getName(); } QStringList CardDatabaseQuerier::getAllMainCardTypes() const @@ -331,8 +346,8 @@ QMap CardDatabaseQuerier::getAllMainCardTypesWithCount() const // produces an empty map, so the cache is only ever empty when it needs a // (trivially cheap) rebuild. if (mainCardTypeCountsCache.isEmpty()) { - for (const auto &card : db->cards.values()) { - QString type = card->getMainCardType(); + for (const auto &card : db->cards) { + const QString type = card->getMainCardType(); mainCardTypeCountsCache[type]++; } } @@ -343,13 +358,13 @@ QMap CardDatabaseQuerier::getAllMainCardTypesWithCount() const QMap CardDatabaseQuerier::getAllSubCardTypesWithCount() const { if (subCardTypeCountsCache.isEmpty()) { - for (const auto &card : db->cards.values()) { - QString type = card->getCardType(); + for (const auto &card : db->cards) { + const QString type = card->getCardType(); - QStringList parts = type.split(" — "); + const QStringList parts = type.split(" — "); if (parts.size() > 1) { // Ensure there are subtypes - QStringList subtypes = parts[1].split(" ", Qt::SkipEmptyParts); + const QStringList subtypes = parts[1].split(" ", Qt::SkipEmptyParts); for (const QString &subtype : subtypes) { subCardTypeCountsCache[subtype]++; @@ -369,13 +384,13 @@ FormatRulesPtr CardDatabaseQuerier::getFormat(const QString &formatName) const QMap CardDatabaseQuerier::getAllFormatsWithCount() const { if (formatsCountCache.isEmpty()) { - for (const auto &card : db->cards.values()) { - QStringList allProps = card->getProperties(); + static const QString formatPrefix = QStringLiteral("format-"); - for (const QString &prop : allProps) { - if (prop.startsWith("format-")) { - QString formatName = prop.mid(QStringLiteral("format-").size()); - formatsCountCache[formatName]++; + for (const auto &card : db->cards) { + const auto &properties = card->getPropertiesHash(); + for (auto it = properties.constBegin(); it != properties.constEnd(); ++it) { + if (it.key().startsWith(formatPrefix)) { + formatsCountCache[it.key().mid(formatPrefix.size())]++; } } } diff --git a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp index b17eb8b5e..d40c1c063 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_3.cpp @@ -268,7 +268,7 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml) if (attrs.hasAttribute("rarity")) { printingProps.insert("rarity", attrs.value("rarity").toString()); } - PrintingInfo setInfo(set, LazyPropertiesHash(printingProps)); + PrintingInfo setInfo(set, LazyPropertiesHash::serializedFrom(printingProps)); _sets[setName].append(setInfo); } // related cards @@ -325,8 +325,9 @@ void CockatriceXml3Parser::loadCardsFromXml(QXmlStreamReader &xml) .landscapeOrientation = landscapeOrientation, .tableRow = tableRow, .upsideDownArt = upsideDown}; - CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, properties, relatedCards, - reverseRelatedCards, _sets, attributes); + CardInfoPtr newCard = + CardInfo::newInstance(name, text, isToken, LazyPropertiesHash::serialize(properties), relatedCards, + reverseRelatedCards, _sets, attributes, {}, {}, true); if (targetData) { if (auto existing = targetData->cards.value(name)) { for (const auto &printings : newCard->getSets()) { @@ -401,7 +402,7 @@ static QXmlStreamWriter &operator<<(QXmlStreamWriter &xml, const CardInfoPtr &in } // sets - const SetToPrintingsMap setMap = info->getSets(); + const auto &setMap = info->getSets(); for (const auto &printings : setMap) { for (const PrintingInfo &set : printings) { xml.writeStartElement("set"); diff --git a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp index 4d975e533..3ee1e808e 100644 --- a/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp +++ b/libcockatrice_card/libcockatrice/card/database/parser/cockatrice_xml_4.cpp @@ -383,7 +383,7 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) } printingProps.insert(attrName, attr.value().toString()); } - PrintingInfo printingInfo(set, LazyPropertiesHash(printingProps)); + PrintingInfo printingInfo(set, LazyPropertiesHash::serializedFrom(printingProps)); _sets[setName].append(printingInfo); } // related cards @@ -450,9 +450,9 @@ void CockatriceXml4Parser::loadCardsFromXml(QXmlStreamReader &xml) .landscapeOrientation = landscapeOrientation, .tableRow = tableRow, .upsideDownArt = upsideDown}; - CardInfoPtr newCard = - CardInfo::newInstance(name, text, isToken, properties, relatedCards, reverseRelatedCards, _sets, - attributes, std::move(localizedNames), std::move(localizedTexts)); + CardInfoPtr newCard = CardInfo::newInstance(name, text, isToken, LazyPropertiesHash::serialize(properties), + relatedCards, reverseRelatedCards, _sets, attributes, {}, {}, + true, std::move(localizedNames), std::move(localizedTexts)); if (targetData) { // Mirror CardDatabase::addCard: if a card with this name already // exists, merge the new printings into it instead of replacing. diff --git a/libcockatrice_card/libcockatrice/card/lazy_properties_hash.cpp b/libcockatrice_card/libcockatrice/card/lazy_properties_hash.cpp index a12de469b..3798bcd62 100644 --- a/libcockatrice_card/libcockatrice/card/lazy_properties_hash.cpp +++ b/libcockatrice_card/libcockatrice/card/lazy_properties_hash.cpp @@ -1,8 +1,168 @@ #include "lazy_properties_hash.h" #include +#include #include -#include + +namespace +{ +constexpr quint32 NULL_STRING_LENGTH = 0xFFFFFFFFu; + +/** + * @brief Reads a QDataStream-serialized QString length at @p pos, advancing past the length field. + * + * QDataStream writes a QString as a big-endian quint32 byte length followed by UTF-16 code units (also big-endian), + * or 0xFFFFFFFF for a null string. Parsing at the byte level lets us skip and compare properties without + * constructing a QString for every entry. + */ +bool readBlobLength(const QByteArray &blob, int &pos, quint32 &length) +{ + if (pos < 0 || static_cast(pos) + 4 > blob.size()) { + return false; + } + + const auto *bytes = reinterpret_cast(blob.constData()) + pos; + length = (quint32(bytes[0]) << 24) | (quint32(bytes[1]) << 16) | (quint32(bytes[2]) << 8) | quint32(bytes[3]); + pos += 4; + + if (length == NULL_STRING_LENGTH) { + length = 0; + return true; + } + + if (length > static_cast(blob.size() - pos)) { + return false; + } + + return true; +} + +/** + * @brief Compares the serialized bytes at @p pos with @p key without allocating a QString. + */ +bool blobKeyEquals(const QByteArray &blob, int pos, quint32 length, const QString &key) +{ + if (length != static_cast(key.size()) * 2) { + return false; + } + + const auto *bytes = reinterpret_cast(blob.constData()) + pos; + for (int i = 0; i < key.size(); ++i) { + const ushort codeUnit = static_cast((bytes[2 * i] << 8) | bytes[2 * i + 1]); + if (key.at(i).unicode() != codeUnit) { + return false; + } + } + + return true; +} + +/** + * @brief Builds a QString from the serialized UTF-16BE bytes at @p pos. + */ +QString blobString(const QByteArray &blob, int pos, quint32 length) +{ + const int codeUnits = static_cast(length / 2); + QString result; + result.resize(codeUnits); + + const auto *bytes = reinterpret_cast(blob.constData()) + pos; + for (int i = 0; i < codeUnits; ++i) { + result[i] = QChar(static_cast((bytes[2 * i] << 8) | bytes[2 * i + 1])); + } + + return result; +} +} // namespace + +LazyPropertiesHash::BlobReader::BlobReader(const QByteArray &_blob) : blob(_blob) +{ + if (blob.size() < 4) { + return; + } + + const auto *bytes = reinterpret_cast(blob.constData()); + remaining = (quint32(bytes[0]) << 24) | (quint32(bytes[1]) << 16) | (quint32(bytes[2]) << 8) | quint32(bytes[3]); + valid = true; +} + +bool LazyPropertiesHash::BlobReader::next(int &keyPos, quint32 &keyLength, int &valuePos, quint32 &valueLength) +{ + if (!valid || remaining == 0) { + return false; + } + + if (!readBlobLength(blob, pos, keyLength)) { + valid = false; + return false; + } + keyPos = pos; + pos += static_cast(keyLength); + + if (!readBlobLength(blob, pos, valueLength)) { + valid = false; + return false; + } + valuePos = pos; + pos += static_cast(valueLength); + + --remaining; + return true; +} + +quint32 LazyPropertiesHash::BlobReader::entryCount() const +{ + return valid ? remaining : 0; +} + +QString LazyPropertiesHash::blobValue(const QByteArray &blob, const QString &key) +{ + BlobReader reader(blob); + int keyPos = 0; + int valuePos = 0; + quint32 keyLength = 0; + quint32 valueLength = 0; + while (reader.next(keyPos, keyLength, valuePos, valueLength)) { + if (blobKeyEquals(blob, keyPos, keyLength, key)) { + return blobString(blob, valuePos, valueLength); + } + } + + return {}; +} + +bool LazyPropertiesHash::blobContains(const QByteArray &blob, const QString &key) +{ + BlobReader reader(blob); + int keyPos = 0; + int valuePos = 0; + quint32 keyLength = 0; + quint32 valueLength = 0; + while (reader.next(keyPos, keyLength, valuePos, valueLength)) { + if (blobKeyEquals(blob, keyPos, keyLength, key)) { + return true; + } + } + + return false; +} + +QStringList LazyPropertiesHash::blobKeys(const QByteArray &blob) +{ + BlobReader reader(blob); + QStringList result; + result.reserve(static_cast(reader.entryCount())); + + int keyPos = 0; + int valuePos = 0; + quint32 keyLength = 0; + quint32 valueLength = 0; + while (reader.next(keyPos, keyLength, valuePos, valueLength)) { + result.append(blobString(blob, keyPos, keyLength)); + } + + return result; +} LazyPropertiesHash::LazyPropertiesHash() : isMaterialized(true) { @@ -20,15 +180,15 @@ LazyPropertiesHash::LazyPropertiesHash(const QHash &properties LazyPropertiesHash::LazyPropertiesHash(const LazyPropertiesHash &other) { // since we do not allow dematerialization, we only need to lock if not materialized yet - if (other.isMaterialized) { + if (other.isMaterialized.load(std::memory_order_acquire)) { blob = other.blob; properties = other.properties; - isMaterialized = true; + isMaterialized.store(true, std::memory_order_relaxed); } else { QMutexLocker lock(&other.propertiesMutex); blob = other.blob; properties = other.properties; - isMaterialized = false; + isMaterialized.store(other.isMaterialized.load(std::memory_order_relaxed), std::memory_order_relaxed); } } @@ -39,15 +199,15 @@ LazyPropertiesHash &LazyPropertiesHash::operator=(const LazyPropertiesHash &othe } // since we do not allow dematerialization, we only need to lock if not materialized yet - if (other.isMaterialized) { + if (other.isMaterialized.load(std::memory_order_acquire)) { blob = other.blob; properties = other.properties; - isMaterialized = true; + isMaterialized.store(true, std::memory_order_relaxed); } else { QMutexLocker lock(&other.propertiesMutex); blob = other.blob; properties = other.properties; - isMaterialized = false; + isMaterialized.store(false, std::memory_order_relaxed); } return *this; @@ -55,9 +215,15 @@ LazyPropertiesHash &LazyPropertiesHash::operator=(const LazyPropertiesHash &othe void LazyPropertiesHash::ensureMaterialized() const { + // Materialization happens once and is never undone, so the common case can + // skip the mutex entirely once it has happened. + if (isMaterialized.load(std::memory_order_acquire)) { + return; + } + QMutexLocker lock(&propertiesMutex); - if (isMaterialized) { + if (isMaterialized.load(std::memory_order_relaxed)) { return; } @@ -69,13 +235,60 @@ void LazyPropertiesHash::ensureMaterialized() const blob.clear(); - isMaterialized = true; + isMaterialized.store(true, std::memory_order_release); +} + +QByteArray LazyPropertiesHash::serialize(const QHash &properties) +{ + QByteArray result; + QDataStream out(&result, QIODevice::WriteOnly); + out.setVersion(QDataStream::Qt_6_4); + out << properties; + return result; +} + +LazyPropertiesHash LazyPropertiesHash::serializedFrom(const QHash &properties) +{ + return LazyPropertiesHash(serialize(properties)); } QString LazyPropertiesHash::value(const QString &key) const { - ensureMaterialized(); - return properties.value(key); + return read([&key](const QHash &materialized) { return materialized.value(key); }, + [&key](const QByteArray &blob) { return LazyPropertiesHash::blobValue(blob, key); }); +} + +bool LazyPropertiesHash::contains(const QString &key) const +{ + return read([&key](const QHash &materialized) { return materialized.contains(key); }, + [&key](const QByteArray &blob) { return LazyPropertiesHash::blobContains(blob, key); }); +} + +int LazyPropertiesHash::size() const +{ + return read([](const QHash &materialized) { return static_cast(materialized.size()); }, + [](const QByteArray &blob) { return static_cast(BlobReader(blob).entryCount()); }); +} + +bool LazyPropertiesHash::isEmpty() const +{ + return size() == 0; +} + +QStringList LazyPropertiesHash::keys() const +{ + return read([](const QHash &materialized) { return QStringList(materialized.keys()); }, + [](const QByteArray &blob) { return blobKeys(blob); }); +} + +QByteArray LazyPropertiesHash::serialized() const +{ + return read([](const QHash &materialized) { return serialize(materialized); }, + [](const QByteArray &blob) { + // An empty blob is the "unknown/absent" form used by failed cache reads; give callers a + // well-formed serialization of the (empty) property map instead. + return blob.isEmpty() ? serialize({}) : blob; + }); } bool LazyPropertiesHash::insert(const QString &key, const QString &value) diff --git a/libcockatrice_card/libcockatrice/card/lazy_properties_hash.h b/libcockatrice_card/libcockatrice/card/lazy_properties_hash.h index 04f5d0251..c8170b7c2 100644 --- a/libcockatrice_card/libcockatrice/card/lazy_properties_hash.h +++ b/libcockatrice_card/libcockatrice/card/lazy_properties_hash.h @@ -4,24 +4,67 @@ #include #include #include +#include #include +#include +#include /** * @brief A property map that can lazily deserialize blobs to avoid loading overhead. * - * Properties are stored as a pre-serialized blob (cheap to load), and the QString is materialized on the first query, - * so the database load avoids constructing thousands of QString per card. + * Properties are stored as a pre-serialized blob (cheap to load), and individual values are read straight out of that + * blob, so the database load and ordinary queries avoid constructing thousands of QString per card. * - * Once the properties are materialized, it cannot be unmaterialized. - * If you want to reset the properties to an unmaterialized state, you should create a new LazyPropertiesHash. + * A full QHash is only built on the first insert() or getProperties() call. Once materialized, it + * cannot be unmaterialized; create a new LazyPropertiesHash to return to the blob form. */ class LazyPropertiesHash { + /** + * @brief Iterates over the key/value pairs of a serialized QHash blob without materializing it. + * + * The blob layout is the one produced by QDataStream for QHash: a quint32 entry count followed by each entry's + * serialized key and value. + */ + class BlobReader + { + public: + explicit BlobReader(const QByteArray &_blob); - mutable QByteArray blob; ///< Serialized properties (load form). - mutable QHash properties; ///< Materialized properties (query form). - mutable QMutex propertiesMutex; ///< Guards lazy materialization. - mutable bool isMaterialized = false; ///< Whether propertiesCache is valid. + /** + * @brief Reads the next pair's offsets. + * @return False when there are no more pairs or the blob is malformed. + */ + bool next(int &keyPos, quint32 &keyLength, int &valuePos, quint32 &valueLength); + + [[nodiscard]] quint32 entryCount() const; + + private: + const QByteArray &blob; + int pos = 4; + quint32 remaining = 0; + bool valid = false; + }; + + /** + * @brief Looks up @p key in a serialized blob without materializing it. + */ + static QString blobValue(const QByteArray &blob, const QString &key); + + /** + * @brief Checks whether @p key is present in a serialized blob without materializing it. + */ + static bool blobContains(const QByteArray &blob, const QString &key); + + /** + * @brief Lists the keys of a serialized blob without materializing it. + */ + static QStringList blobKeys(const QByteArray &blob); + + mutable QByteArray blob; ///< Serialized properties (load form). + mutable QHash properties; ///< Materialized properties (query form). + mutable QMutex propertiesMutex; ///< Guards lazy materialization. + mutable std::atomic isMaterialized{false}; ///< Whether properties is valid. /** * @brief Materializes properties from blob if not already done. Clears blob afterward. @@ -29,6 +72,32 @@ class LazyPropertiesHash */ void ensureMaterialized() const; + /** + * @brief Runs the accessor for the current storage form. + * + * The common case skips the mutex once the properties are materialized. While they are + * still in blob form, the mutex is taken and @p onBlob reads the values straight out of + * the blob without building the hash, so ordinary queries stay allocation-light. + * + * @param onMaterialized Called with the materialized hash when it is available. + * @param onBlob Called with the blob when it is still the source of truth. + * @return Whatever the invoked accessor returns; both accessors must return the same type. + */ + template + auto read(MaterializedFn &&onMaterialized, BlobFn &&onBlob) const + { + if (isMaterialized.load(std::memory_order_acquire)) { + return onMaterialized(properties); + } + + QMutexLocker lock(&propertiesMutex); + if (isMaterialized.load(std::memory_order_relaxed)) { + return onMaterialized(properties); + } + + return onBlob(blob); + } + public: /** * @brief Default constructor. @@ -52,12 +121,60 @@ public: LazyPropertiesHash &operator=(const LazyPropertiesHash &other); /** - * @brief Gets the value from the materialized properties hash + * @brief Serializes a property map into the blob form understood by this class. + * @param properties The properties to serialize + * @return The serialized blob + */ + static QByteArray serialize(const QHash &properties); + + /** + * @brief Creates an unmaterialized LazyPropertiesHash from a property map. + * + * Serializes @p properties once so queries read them from the blob form instead of first materializing a second + * copy of the hash. + * + * @param properties The properties to serialize + * @return An unmaterialized LazyPropertiesHash holding the serialized properties + */ + static LazyPropertiesHash serializedFrom(const QHash &properties); + + /** + * @brief Gets the value for a key, reading it from the blob when possible. * @param key The key * @return The value, or an empty string if the key is not present */ QString value(const QString &key) const; + /** + * @brief Checks whether a key is present, reading the blob when possible. + * @param key The key + * @return True if the key is present + */ + bool contains(const QString &key) const; + + /** + * @brief Returns the number of properties. + */ + int size() const; + + /** + * @brief Returns whether there are no properties. + */ + bool isEmpty() const; + + /** + * @brief Returns the property names, reading them from the blob when possible. + */ + QStringList keys() const; + + /** + * @brief Returns the properties in serialized blob form. + * + * While the object is unmaterialized this is a cheap copy of the stored blob; after materialization the hash is + * serialized on demand. + */ + QByteArray serialized() const; + /** * @brief Inserts a value into the materialized properties hash * @param key The key @@ -67,7 +184,7 @@ public: bool insert(const QString &key, const QString &value); /** - * @brief Gets a view of the materialized properties hash. + * @brief Gets a view of the materialized properties hash, materializing it if needed. * @return The properties hash */ const QHash &getProperties() const; diff --git a/libcockatrice_card/libcockatrice/card/printing/exact_card.h b/libcockatrice_card/libcockatrice/card/printing/exact_card.h index ea49072fb..83ba97bd0 100644 --- a/libcockatrice_card/libcockatrice/card/printing/exact_card.h +++ b/libcockatrice_card/libcockatrice/card/printing/exact_card.h @@ -47,7 +47,7 @@ public: * * May be null if the ExactCard is empty. */ - [[nodiscard]] CardInfoPtr getCardPtr() const + [[nodiscard]] const CardInfoPtr &getCardPtr() const { return card; } @@ -57,7 +57,7 @@ public: * * May be empty if no specific printing was assigned. */ - [[nodiscard]] PrintingInfo getPrinting() const + [[nodiscard]] const PrintingInfo &getPrinting() const { return printing; } diff --git a/libcockatrice_card/libcockatrice/card/printing/printing_info.cpp b/libcockatrice_card/libcockatrice/card/printing/printing_info.cpp index 654efac5e..9bc62a07c 100644 --- a/libcockatrice_card/libcockatrice/card/printing/printing_info.cpp +++ b/libcockatrice_card/libcockatrice/card/printing/printing_info.cpp @@ -12,15 +12,32 @@ void PrintingInfo::setProperty(const QString &_name, const QString &_value) properties.insert(_name, _value); } +bool PrintingInfo::operator==(const PrintingInfo &other) const +{ + if (set != other.set || properties.size() != other.properties.size()) { + return false; + } + + // Both property maps are usually still in blob form here, so compare key by key instead of + // materializing two full hashes just to compare them. + for (const QString &key : properties.keys()) { + if (!other.properties.contains(key) || properties.value(key) != other.properties.value(key)) { + return false; + } + } + + return true; +} + /** * Gets the uuid property of the printing, or an empty string if the property isn't present */ QString PrintingInfo::getUuid() const { - return getPropertiesHash().value("uuid"); + return getProperty("uuid"); } QString PrintingInfo::getFlavorName() const { - return getPropertiesHash().value("flavorName"); + return getProperty("flavorName"); } diff --git a/libcockatrice_card/libcockatrice/card/printing/printing_info.h b/libcockatrice_card/libcockatrice/card/printing/printing_info.h index 038960e86..dcfcc846b 100644 --- a/libcockatrice_card/libcockatrice/card/printing/printing_info.h +++ b/libcockatrice_card/libcockatrice/card/printing/printing_info.h @@ -54,10 +54,7 @@ public: * @param other Another PrintingInfo to compare against. * @return True if both set and properties are equal, otherwise false. */ - bool operator==(const PrintingInfo &other) const - { - return this->set == other.set && this->getPropertiesHash() == other.getPropertiesHash(); - } + bool operator==(const PrintingInfo &other) const; /** * @brief check if the info is empty, as if default constructed. @@ -66,7 +63,7 @@ public: */ bool isEmpty() const { - return set == nullptr && getPropertiesHash().isEmpty(); + return set == nullptr && properties.isEmpty(); } private: @@ -91,9 +88,22 @@ public: */ [[nodiscard]] QStringList getProperties() const { - return getPropertiesHash().keys(); + return properties.keys(); } + /** + * @brief Returns the printing properties in their serialized blob form. + */ + [[nodiscard]] QByteArray getPropertiesBlob() const + { + return properties.serialized(); + } + + /** + * @brief Returns the materialized property hash, forcing deserialization of the blob. + * + * Prefer getProperty() or getPropertiesBlob(), which avoid building the hash. + */ [[nodiscard]] const QHash &getPropertiesHash() const { return properties.getProperties(); diff --git a/libcockatrice_card/libcockatrice/card/set/card_set.cpp b/libcockatrice_card/libcockatrice/card/set/card_set.cpp index e873eeeab..a0f0f4578 100644 --- a/libcockatrice_card/libcockatrice/card/set/card_set.cpp +++ b/libcockatrice_card/libcockatrice/card/set/card_set.cpp @@ -27,17 +27,20 @@ CardSetPtr CardSet::newInstance(ICardSetPriorityController *_priorityController, const Priority _priority) { CardSetPtr ptr(new CardSet(_priorityController, _shortName, _longName, _setType, _releaseDate, _priority)); - // ptr->setSmartPointer(ptr); return ptr; } QString CardSet::getCorrectedShortName() const { // For Windows machines. - QSet invalidFileNames; - invalidFileNames << "CON" << "PRN" << "AUX" << "NUL" << "COM1" << "COM2" << "COM3" << "COM4" << "COM5" << "COM6" - << "COM7" << "COM8" << "COM9" << "LPT1" << "LPT2" << "LPT3" << "LPT4" << "LPT5" << "LPT6" << "LPT7" - << "LPT8" << "LPT9"; + static const QSet invalidFileNames = { + QStringLiteral("CON"), QStringLiteral("PRN"), QStringLiteral("AUX"), QStringLiteral("NUL"), + QStringLiteral("COM1"), QStringLiteral("COM2"), QStringLiteral("COM3"), QStringLiteral("COM4"), + QStringLiteral("COM5"), QStringLiteral("COM6"), QStringLiteral("COM7"), QStringLiteral("COM8"), + QStringLiteral("COM9"), QStringLiteral("LPT1"), QStringLiteral("LPT2"), QStringLiteral("LPT3"), + QStringLiteral("LPT4"), QStringLiteral("LPT5"), QStringLiteral("LPT6"), QStringLiteral("LPT7"), + QStringLiteral("LPT8"), QStringLiteral("LPT9"), + }; return invalidFileNames.contains(shortName) ? shortName + "_" : shortName; } diff --git a/libcockatrice_card/libcockatrice/card/set/card_set.h b/libcockatrice_card/libcockatrice/card/set/card_set.h index b9ac74e2d..b995ac88e 100644 --- a/libcockatrice_card/libcockatrice/card/set/card_set.h +++ b/libcockatrice_card/libcockatrice/card/set/card_set.h @@ -239,7 +239,7 @@ public: */ [[nodiscard]] bool getIsKnownIgnored() const { - return longName.length() + setType.length() + releaseDate.toString().length() == 0; + return longName.isEmpty() && setType.isEmpty() && !releaseDate.isValid(); } }; diff --git a/libcockatrice_deck_list/CMakeLists.txt b/libcockatrice_deck_list/CMakeLists.txt index e61a5702c..3e7d522c2 100644 --- a/libcockatrice_deck_list/CMakeLists.txt +++ b/libcockatrice_deck_list/CMakeLists.txt @@ -1,34 +1,32 @@ -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - set(HEADERS libcockatrice/deck_list/tree/abstract_deck_list_card_node.h libcockatrice/deck_list/tree/abstract_deck_list_node.h libcockatrice/deck_list/tree/deck_list_card_node.h libcockatrice/deck_list/tree/inner_deck_list_node.h + libcockatrice/deck_list/deck_file_format.h libcockatrice/deck_list/deck_list.h libcockatrice/deck_list/deck_list_history_manager.h libcockatrice/deck_list/deck_list_node_tree.h libcockatrice/deck_list/deck_list_memento.h libcockatrice/deck_list/deck_list_plain_text_parser.h + libcockatrice/deck_list/loaded_deck.h libcockatrice/deck_list/playmat_resolver.h libcockatrice/deck_list/sideboard_plan.h ) -qt6_wrap_cpp(MOC_SOURCES ${HEADERS}) - add_library( libcockatrice_deck_list STATIC - ${MOC_SOURCES} + ${HEADERS} libcockatrice/deck_list/tree/abstract_deck_list_card_node.cpp libcockatrice/deck_list/tree/abstract_deck_list_node.cpp libcockatrice/deck_list/tree/deck_list_card_node.cpp libcockatrice/deck_list/tree/inner_deck_list_node.cpp + libcockatrice/deck_list/deck_file_format.cpp libcockatrice/deck_list/deck_list.cpp libcockatrice/deck_list/deck_list_history_manager.cpp libcockatrice/deck_list/deck_list_node_tree.cpp libcockatrice/deck_list/deck_list_plain_text_parser.cpp + libcockatrice/deck_list/loaded_deck.cpp libcockatrice/deck_list/playmat_resolver.cpp libcockatrice/deck_list/sideboard_plan.cpp ) diff --git a/cockatrice/src/interface/deck_loader/deck_file_format.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/deck_file_format.cpp similarity index 100% rename from cockatrice/src/interface/deck_loader/deck_file_format.cpp rename to libcockatrice_deck_list/libcockatrice/deck_list/deck_file_format.cpp diff --git a/cockatrice/src/interface/deck_loader/deck_file_format.h b/libcockatrice_deck_list/libcockatrice/deck_list/deck_file_format.h similarity index 100% rename from cockatrice/src/interface/deck_loader/deck_file_format.h rename to libcockatrice_deck_list/libcockatrice/deck_list/deck_file_format.h diff --git a/cockatrice/src/interface/deck_loader/loaded_deck.cpp b/libcockatrice_deck_list/libcockatrice/deck_list/loaded_deck.cpp similarity index 100% rename from cockatrice/src/interface/deck_loader/loaded_deck.cpp rename to libcockatrice_deck_list/libcockatrice/deck_list/loaded_deck.cpp diff --git a/cockatrice/src/interface/deck_loader/loaded_deck.h b/libcockatrice_deck_list/libcockatrice/deck_list/loaded_deck.h similarity index 100% rename from cockatrice/src/interface/deck_loader/loaded_deck.h rename to libcockatrice_deck_list/libcockatrice/deck_list/loaded_deck.h diff --git a/libcockatrice_filters/CMakeLists.txt b/libcockatrice_filters/CMakeLists.txt index 2d45b8e5b..070c04637 100644 --- a/libcockatrice_filters/CMakeLists.txt +++ b/libcockatrice_filters/CMakeLists.txt @@ -1,16 +1,10 @@ -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - set(HEADERS libcockatrice/filters/filter_card.h libcockatrice/filters/filter_string.h libcockatrice/filters/filter_tree.h ) -qt6_wrap_cpp(MOC_SOURCES ${HEADERS}) - add_library( - libcockatrice_filters STATIC ${MOC_SOURCES} libcockatrice/filters/filter_card.cpp - libcockatrice/filters/filter_string.cpp libcockatrice/filters/filter_tree.cpp + libcockatrice_filters STATIC ${HEADERS} libcockatrice/filters/filter_card.cpp libcockatrice/filters/filter_string.cpp + libcockatrice/filters/filter_tree.cpp ) add_dependencies(libcockatrice_filters libcockatrice_card) diff --git a/libcockatrice_filters/libcockatrice/filters/filter_card.h b/libcockatrice_filters/libcockatrice/filters/filter_card.h index e6a37ee40..dbeffd18f 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_card.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_card.h @@ -54,7 +54,7 @@ private: enum Attr a; public: - CardFilter(QString &term, Type type, Attr attr) : trm(term), t(type), a(attr) + CardFilter(const QString &term, Type type, Attr attr) : trm(term), t(type), a(attr) { } diff --git a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp index bc137b0f6..081ada824 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_string.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_string.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -43,31 +44,31 @@ SomewhatComplexQueryPart <- [(] QueryPartList [)] / QueryPart QueryPart <- NotQuery / SetQuery / RarityQuery / CMCQuery / FormatQuery / PowerQuery / ToughnessQuery / ColorQuery / TypeQuery / OracleQuery / FieldQuery / GenericQuery NotQuery <- ('NOT' ws/'-') SomewhatComplexQueryPart -SetQuery <- ('e'/'set') SetQueryValue -SetQueryValue <- ([=:] FlexStringValue) / (<[!][=]?> FlexStringValue) / SetExpression -OracleQuery <- 'o' [:] MatcherString +SetQuery <- ('e'/'set') ws? SetQueryValue +SetQueryValue <- ([=:] ws? FlexStringValue) / (<[!][=]?> ws? FlexStringValue) / SetExpression +OracleQuery <- 'o' ws? [:] ws? MatcherString CMCQuery <- ('cmc'/'mv') ws? NumericExpression PowerQuery <- [Pp] 'ow' 'er'? ws? NumericExpression ToughnessQuery <- [Tt] 'ou' 'ghness'? ws? NumericExpression -RarityQuery <- [rR] ':' Rarity +RarityQuery <- [rR] ws? ':' ws? Rarity Rarity <- [Cc] 'ommon'? / [Uu] 'ncommon'? / [Rr] 'are'? / [Mm] 'ythic'? / [Ss] 'pecial'? / [a-zA-Z] [a-z]* -FormatQuery <- 'f' ':' Format / Legality ':' Format +FormatQuery <- 'f' ws? ':' ws? Format / Legality ws? ':' ws? Format Format <- [a-zA-Z] [a-z]* Legality <- [Ll] 'egal'? / [Bb] 'anned'? / [Rr] 'estricted' -TypeQuery <- [tT] 'ype'? [:] StringValue +TypeQuery <- [tT] 'ype'? ws? [:] ws? StringValue Color <- < [Ww] 'hite'? / [Uu] / [Bb] 'lack'? / [Rr] 'ed'? / [Gg] 'reen'? / [Bb] 'lue'? > ColorEx <- Color / [mc] -ColorQuery <- [cC] 'olor'? <[iI]?> <[:!]> ColorEx* +ColorQuery <- [cC] 'olor'? <[iI]?> ws? <[:!]> ws? ColorEx* -FieldQuery <- String [:] MatcherString / String ws? NumericExpression +FieldQuery <- String ws? [:] ws? MatcherString / String ws? NumericExpression NonDoubleQuoteUnlessEscaped <- '\\\"'. / !["]. NonSingleQuoteUnlessEscaped <- "\\\'". / ![']. @@ -161,30 +162,28 @@ static void setupParserRules() if (sv.choice() == 0) { auto matcher = std::any_cast(sv[0]); return [=](const CardData &x) -> bool { - QList sets = x->getSets().keys(); - - auto matchesSet = [&matcher](const QString &set) { return matcher(set); }; - return std::any_of(sets.begin(), sets.end(), matchesSet); + const auto &sets = x->getSets(); + return std::any_of(sets.keyValueBegin(), sets.keyValueEnd(), + [&matcher](const auto &set) { return matcher(set.first); }); }; } if (sv.choice() == 1) { auto matcher = std::any_cast(sv[0]); return [=](const CardData &x) -> bool { - QList sets = x->getSets().keys(); - - auto matchesSet = [&matcher](const QString &set) { return matcher(set); }; - return std::none_of(sets.begin(), sets.end(), matchesSet); + const auto &sets = x->getSets(); + return std::none_of(sets.keyValueBegin(), sets.keyValueEnd(), + [&matcher](const auto &set) { return matcher(set.first); }); }; } auto matcher = std::any_cast(sv[0]); return [=](const CardData &x) -> bool { - const auto &sets = x->getSets().values(); - auto matchesSet = [&](const PrintingInfo &printing) { - return printing.getSet()->getEnabled() && matcher(printing.getSet()->getReleaseDate().toJulianDay()); - }; - return std::any_of(sets.begin(), sets.end(), [&](const auto &printings) { - return std::any_of(printings.begin(), printings.end(), matchesSet); + const auto &sets = x->getSets(); + return std::any_of(sets.constBegin(), sets.constEnd(), [&matcher](const auto &printings) { + return std::any_of(printings.begin(), printings.end(), [&matcher](const PrintingInfo &printing) { + const CardSetPtr set = printing.getSet(); + return set && set->getEnabled() && matcher(set->getReleaseDate().toJulianDay()); + }); }); }; }; @@ -207,15 +206,14 @@ static void setupParserRules() search["RarityQuery"] = [](const peg::SemanticValues &sv) -> Filter { const auto rarity = std::any_cast(sv[0]); return [=](const CardData &x) -> bool { - QList infos; for (const auto &printings : x->getSets()) { for (const auto &printing : printings) { - infos.append(printing); + if (rarity == printing.getProperty("rarity")) { + return true; + } } } - - auto matchesRarity = [&rarity](const PrintingInfo &info) { return rarity == info.getProperty("rarity"); }; - return std::any_of(infos.begin(), infos.end(), matchesRarity); + return false; }; }; @@ -411,7 +409,7 @@ static void setupParserRules() const bool identity = sv.tokens[0].empty() || sv.tokens[0][0] != 'i'; if (sv.tokens[1][0] == ':') { return [=](const CardData &x) { - QString match = identity ? x->getColors() : x->getProperty("coloridentity"); + const QString match = identity ? x->getColors() : x->getProperty("coloridentity"); if (parts.contains("m") && match.length() < 2) { return false; } @@ -429,7 +427,7 @@ static void setupParserRules() } return [=](const CardData &x) { - QString match = identity ? x->getColors() : x->getProperty("colorIdentity"); + const QString match = identity ? x->getColors() : x->getProperty("colorIdentity"); if (parts.contains("m") && match.length() < 2) { return false; } @@ -455,25 +453,37 @@ static void setupParserRules() }; search["PowerQuery"] = [](const peg::SemanticValues &sv) -> Filter { const auto matcher = std::any_cast(sv[0]); - return [=](const CardData &x) -> bool { return matcher(x->getPowTough().split("/")[0].toInt()); }; + return [=](const CardData &x) -> bool { + const QString &powTough = x->getPowTough(); + const qsizetype slash = powTough.indexOf('/'); + return matcher(slash < 0 ? powTough.toInt() : QStringView(powTough).left(slash).toInt()); + }; }; search["ToughnessQuery"] = [](const peg::SemanticValues &sv) -> Filter { const auto matcher = std::any_cast(sv[0]); return [=](const CardData &x) -> bool { - auto parts = x->getPowTough().split("/"); - return matcher(parts.length() == 2 ? parts[1].toInt() : 0); + const QString &powTough = x->getPowTough(); + const qsizetype slash = powTough.indexOf('/'); + return matcher(slash < 0 ? 0 : QStringView(powTough).mid(slash + 1).toInt()); }; }; search["FieldQuery"] = [](const peg::SemanticValues &sv) -> Filter { const auto field = std::any_cast(sv[0]); if (sv.choice() == 0) { const auto matcher = std::any_cast(sv[1]); - return [=](const CardData &x) -> bool { return x->hasProperty(field) && matcher(x->getProperty(field)); }; + return [=](const CardData &x) -> bool { + const auto &properties = x->getPropertiesHash(); + const auto it = properties.constFind(field); + return it != properties.constEnd() && matcher(it.value()); + }; } const auto matcher = std::any_cast(sv[1]); - return - [=](const CardData &x) -> bool { return x->hasProperty(field) && matcher(x->getProperty(field).toInt()); }; + return [=](const CardData &x) -> bool { + const auto &properties = x->getPropertiesHash(); + const auto it = properties.constFind(field); + return it != properties.constEnd() && matcher(it.value().toInt()); + }; }; search["GenericQuery"] = [](const peg::SemanticValues &sv) -> Filter { const auto matcher = std::any_cast(sv[0]); diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp index 742b44e97..f12f87181 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp +++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.cpp @@ -9,9 +9,16 @@ #include #include #include +#include #include #include +namespace +{ +/** Separator between the main and sub types of a card type ("Creature — Elf"). */ +const QString MAIN_SUB_TYPE_SEPARATOR = QStringLiteral(" — "); +} // namespace + template FilterTreeNode *FilterTreeBranch::nodeAt(int i) const { return (childNodes.size() > i) ? childNodes.at(i) : nullptr; @@ -108,7 +115,7 @@ FilterTreeNode *FilterItemList::termNode(const QString &term) return childNodes.at(i); } -bool FilterItemList::testTypeAnd(const CardInfoPtr info, +bool FilterItemList::testTypeAnd(const CardInfoPtr &info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const { @@ -125,7 +132,7 @@ bool FilterItemList::testTypeAnd(const CardInfoPtr info, return true; } -bool FilterItemList::testTypeAndNot(const CardInfoPtr info, +bool FilterItemList::testTypeAndNot(const CardInfoPtr &info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const { @@ -133,7 +140,7 @@ bool FilterItemList::testTypeAndNot(const CardInfoPtr info, return !testTypeOr(info, attr, searchLanguage); } -bool FilterItemList::testTypeOr(const CardInfoPtr info, +bool FilterItemList::testTypeOr(const CardInfoPtr &info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const { @@ -156,7 +163,7 @@ bool FilterItemList::testTypeOr(const CardInfoPtr info, return noChildEnabledChild; } -bool FilterItemList::testTypeOrNot(const CardInfoPtr info, +bool FilterItemList::testTypeOrNot(const CardInfoPtr &info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const { @@ -164,7 +171,118 @@ bool FilterItemList::testTypeOrNot(const CardInfoPtr info, return !testTypeAnd(info, attr, searchLanguage); } -bool FilterItem::acceptName(const CardInfoPtr info, const CardSearchLanguage &searchLanguage) const +FilterItem::FilterItem(QString trm, FilterItemList *parent) : p(parent), term(std::move(trm)) +{ + lowercaseTerm = term.toLower(); + + switch (attr()) { + case CardFilter::AttrColor: { + QString converted = term.trimmed(); + + converted.replace("green", "g", Qt::CaseInsensitive); + converted.replace("grn", "g", Qt::CaseInsensitive); + converted.replace("blue", "u", Qt::CaseInsensitive); + converted.replace("blu", "u", Qt::CaseInsensitive); + converted.replace("black", "b", Qt::CaseInsensitive); + converted.replace("blk", "b", Qt::CaseInsensitive); + converted.replace("red", "r", Qt::CaseInsensitive); + converted.replace("white", "w", Qt::CaseInsensitive); + converted.replace("wht", "w", Qt::CaseInsensitive); + converted.replace("colorless", "c", Qt::CaseInsensitive); + converted.replace("colourless", "c", Qt::CaseInsensitive); + converted.replace("none", "c", Qt::CaseInsensitive); + + converted.replace(QString(" "), QString(""), Qt::CaseInsensitive); + + convertedColorTerm = converted; + break; + } + case CardFilter::AttrRarity: { + QString converted = term.trimmed(); + + /* + * The purpose of this loop is to only apply one of the replacement + * policies and then escape. If we attempt to layer them on top of + * each other, we will get awkward results (i.e. comythic rare mythic rareon) + * Conditional statement will exit once a case is successful in + * replacement OR we go through all possible cases. + * Will also need to replace just "mythic" + */ + for (int i = 0; converted.length() <= 3 && i <= 6; i++) { + switch (i) { + case 0: + converted.replace("mr", "mythic", Qt::CaseInsensitive); + break; + case 1: + converted.replace("m r", "mythic", Qt::CaseInsensitive); + break; + case 2: + converted.replace("m", "mythic", Qt::CaseInsensitive); + break; + case 3: + converted.replace("c", "common", Qt::CaseInsensitive); + break; + case 4: + converted.replace("u", "uncommon", Qt::CaseInsensitive); + break; + case 5: + converted.replace("r", "rare", Qt::CaseInsensitive); + break; + case 6: + converted.replace("s", "special", Qt::CaseInsensitive); + break; + default: + break; + } + } + + convertedRarityTerm = converted; + break; + } + case CardFilter::AttrManaCost: + sortedManaCostTerm = term.toUpper(); + // Sort the mana cost so it will be easy to find + std::sort(sortedManaCostTerm.begin(), sortedManaCostTerm.end()); + break; + case CardFilter::AttrLoyalty: + uppercaseLoyaltyTerm = term.trimmed().toUpper(); + break; + default: + break; + } + + // Parse the numeric relation used by cmc, power, toughness and loyalty once. + // If the term is not numeric, it probably starts with an operator. + const QString trimmedTerm = term.trimmed(); + bool converted = false; + const int value = trimmedTerm.toInt(&converted); + if (converted) { + numericOperator = NumericOperator::Equal; + numericOperand = value; + } else if (trimmedTerm.size() > 1) { + if (trimmedTerm.at(1) == '=') { + numericOperand = trimmedTerm.mid(2).toInt(); + if (trimmedTerm.startsWith('<')) { + numericOperator = NumericOperator::LessEqual; + } else if (trimmedTerm.startsWith('>')) { + numericOperator = NumericOperator::GreaterEqual; + } else { + numericOperator = NumericOperator::Equal; + } + } else { + numericOperand = trimmedTerm.mid(1).toInt(); + if (trimmedTerm.startsWith('<')) { + numericOperator = NumericOperator::Less; + } else if (trimmedTerm.startsWith('>')) { + numericOperator = NumericOperator::Greater; + } else if (trimmedTerm.startsWith('=')) { + numericOperator = NumericOperator::Equal; + } + } + } +} + +bool FilterItem::acceptName(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const { const QString &englishName = info->getName(); const QString &localizedName = info->getLocalizedName(searchLanguage.language); @@ -187,7 +305,7 @@ bool FilterItem::acceptName(const CardInfoPtr info, const CardSearchLanguage &se return false; } -bool FilterItem::acceptNameExact(const CardInfoPtr info, const CardSearchLanguage &searchLanguage) const +bool FilterItem::acceptNameExact(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const { const QString &englishName = info->getName(); const QString &localizedName = info->getLocalizedName(searchLanguage.language); @@ -210,44 +328,42 @@ bool FilterItem::acceptNameExact(const CardInfoPtr info, const CardSearchLanguag return false; } -bool FilterItem::acceptType(const CardInfoPtr info) const +bool FilterItem::acceptType(const CardInfoPtr &info) const { return info->getCardType().contains(term, Qt::CaseInsensitive); } -bool FilterItem::acceptMainType(const CardInfoPtr info) const +bool FilterItem::acceptMainType(const CardInfoPtr &info) const { - const QStringList typeParts = info->getCardType().split(" — "); - return !typeParts.isEmpty() && typeParts[0].contains(term, Qt::CaseInsensitive); + const QString type = info->getCardType(); + const qsizetype separator = type.indexOf(MAIN_SUB_TYPE_SEPARATOR); + if (separator >= 0) { + return QStringView(type).left(separator).contains(term, Qt::CaseInsensitive); + } + return type.contains(term, Qt::CaseInsensitive); } -bool FilterItem::acceptSubType(const CardInfoPtr info) const +bool FilterItem::acceptSubType(const CardInfoPtr &info) const { - const QStringList typeParts = info->getCardType().split(" — "); - return typeParts.size() > 1 && typeParts[1].contains(term, Qt::CaseInsensitive); + const QString type = info->getCardType(); + const qsizetype separator = type.indexOf(MAIN_SUB_TYPE_SEPARATOR); + if (separator < 0) { + return false; + } + + const qsizetype subTypeStart = separator + MAIN_SUB_TYPE_SEPARATOR.size(); + const qsizetype nextSeparator = type.indexOf(MAIN_SUB_TYPE_SEPARATOR, subTypeStart); + const QStringView subType = nextSeparator < 0 ? QStringView(type).mid(subTypeStart) + : QStringView(type).mid(subTypeStart, nextSeparator - subTypeStart); + return subType.contains(term, Qt::CaseInsensitive); } -bool FilterItem::acceptColor(const CardInfoPtr info) const +bool FilterItem::acceptColor(const CardInfoPtr &info) const { - QString converted_term = term.trimmed(); - - converted_term.replace("green", "g", Qt::CaseInsensitive); - converted_term.replace("grn", "g", Qt::CaseInsensitive); - converted_term.replace("blue", "u", Qt::CaseInsensitive); - converted_term.replace("blu", "u", Qt::CaseInsensitive); - converted_term.replace("black", "b", Qt::CaseInsensitive); - converted_term.replace("blk", "b", Qt::CaseInsensitive); - converted_term.replace("red", "r", Qt::CaseInsensitive); - converted_term.replace("white", "w", Qt::CaseInsensitive); - converted_term.replace("wht", "w", Qt::CaseInsensitive); - converted_term.replace("colorless", "c", Qt::CaseInsensitive); - converted_term.replace("colourless", "c", Qt::CaseInsensitive); - converted_term.replace("none", "c", Qt::CaseInsensitive); - - converted_term.replace(QString(" "), QString(""), Qt::CaseInsensitive); + const QString colors = info->getColors(); // Colorless card filter - if (converted_term.toLower() == "c" && info->getColors().length() < 1) { + if (convertedColorTerm.toLower() == "c" && colors.length() < 1) { return true; } @@ -256,16 +372,16 @@ bool FilterItem::acceptColor(const CardInfoPtr info) const * then we should match all of them to the card's colors */ int match_count = 0; - for (auto &it : converted_term) { - if (info->getColors().contains(it, Qt::CaseInsensitive)) { + for (const QChar &color : convertedColorTerm) { + if (colors.contains(color, Qt::CaseInsensitive)) { match_count++; } } - return match_count == converted_term.length(); + return match_count == convertedColorTerm.length(); } -bool FilterItem::acceptText(const CardInfoPtr info, const CardSearchLanguage &searchLanguage) const +bool FilterItem::acceptText(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const { const QString &englishText = info->getText(); const QString &localizedText = info->getLocalizedText(searchLanguage.language); @@ -288,51 +404,47 @@ bool FilterItem::acceptText(const CardInfoPtr info, const CardSearchLanguage &se return false; } -bool FilterItem::acceptSet(const CardInfoPtr info) const +bool FilterItem::acceptSet(const CardInfoPtr &info) const { - bool status = false; for (const auto &printings : info->getSets()) { - for (const auto &set : printings) { - if (set.getSet()->getShortName().compare(term, Qt::CaseInsensitive) == 0 || - set.getSet()->getLongName().compare(term, Qt::CaseInsensitive) == 0) { - status = true; - break; + for (const auto &printing : printings) { + const CardSetPtr set = printing.getSet(); + if (set && (set->getShortName().compare(term, Qt::CaseInsensitive) == 0 || + set->getLongName().compare(term, Qt::CaseInsensitive) == 0)) { + return true; } } } - return status; + return false; } -bool FilterItem::acceptManaCost(const CardInfoPtr info) const +bool FilterItem::acceptManaCost(const CardInfoPtr &info) const { - QString partialCost = term.toUpper(); - - // Sort the mana cost so it will be easy to find - std::sort(partialCost.begin(), partialCost.end()); - // Try to seperate the mana cost in case it's a split card // if it's not a split card the loop will run only once - for (QString fullManaCost : info->getManaCost().split("//")) { + for (const QString &manaCostPart : info->getManaCost().split("//")) { + QString fullManaCost = manaCostPart; std::sort(fullManaCost.begin(), fullManaCost.end()); // If the partial is found in the full, return true - if (fullManaCost.contains(partialCost)) { + if (fullManaCost.contains(sortedManaCostTerm)) { return true; } } return false; } -bool FilterItem::acceptCmc(const CardInfoPtr info) const +bool FilterItem::acceptCmc(const CardInfoPtr &info) const { + const QString cmc = info->getCmc(); bool convertSuccess; - int cmcInt = info->getCmc().toInt(&convertSuccess); + int cmcInt = cmc.toInt(&convertSuccess); // if conversion failed, check for the "//" separator used in split cards if (!convertSuccess) { int cmcSum = 0; - for (const QString &cmc : info->getCmc().split("//")) { - cmcInt = cmc.toInt(); + for (const QString &cmcPart : cmc.split("//")) { + cmcInt = cmcPart.toInt(); cmcSum += cmcInt; if (relationCheck(cmcInt)) { return true; @@ -344,39 +456,36 @@ bool FilterItem::acceptCmc(const CardInfoPtr info) const } } -bool FilterItem::acceptFormat(const CardInfoPtr info) const +bool FilterItem::acceptFormat(const CardInfoPtr &info) const { - return info->getLegalityProp(term.toLower()) == "legal"; + return info->getLegalityProp(lowercaseTerm) == "legal"; } -bool FilterItem::acceptLoyalty(const CardInfoPtr info) const +bool FilterItem::acceptLoyalty(const CardInfoPtr &info) const { - if (info->getLoyalty().isEmpty()) { + const QString loyaltyString = info->getLoyalty(); + if (loyaltyString.isEmpty()) { return false; - } else { - bool success; - // if loyalty can't be converted to "int" it must be "X" - int loyalty = info->getLoyalty().toInt(&success); - if (success) { - return relationCheck(loyalty); - } else { - return term.trimmed().toUpper() == info->getLoyalty(); - } } + + bool success; + // if loyalty can't be converted to "int" it must be "X" + int loyalty = loyaltyString.toInt(&success); + if (success) { + return relationCheck(loyalty); + } + return uppercaseLoyaltyTerm == loyaltyString; } -bool FilterItem::acceptPowerToughness(const CardInfoPtr info, CardFilter::Attr attr) const +bool FilterItem::acceptPowerToughness(const CardInfoPtr &info, CardFilter::Attr attr) const { - int slash = info->getPowTough().indexOf("/"); + const QString powTough = info->getPowTough(); + const qsizetype slash = powTough.indexOf("/"); if (slash == -1) { return false; } - QString valueString; - if (attr == CardFilter::AttrPow) { - valueString = info->getPowTough().mid(0, slash); - } else { - valueString = info->getPowTough().mid(slash + 1); - } + + QString valueString = attr == CardFilter::AttrPow ? powTough.mid(0, slash) : powTough.mid(slash + 1); if (term == valueString) { return true; } @@ -386,49 +495,11 @@ bool FilterItem::acceptPowerToughness(const CardInfoPtr info, CardFilter::Attr a return conversion ? relationCheck(value) : false; } -bool FilterItem::acceptRarity(const CardInfoPtr info) const +bool FilterItem::acceptRarity(const CardInfoPtr &info) const { - QString converted_term = term.trimmed(); - - /* - * The purpose of this loop is to only apply one of the replacement - * policies and then escape. If we attempt to layer them on top of - * each other, we will get awkward results (i.e. comythic rare mythic rareon) - * Conditional statement will exit once a case is successful in - * replacement OR we go through all possible cases. - * Will also need to replace just "mythic" - */ - for (int i = 0; converted_term.length() <= 3 && i <= 6; i++) { - switch (i) { - case 0: - converted_term.replace("mr", "mythic", Qt::CaseInsensitive); - break; - case 1: - converted_term.replace("m r", "mythic", Qt::CaseInsensitive); - break; - case 2: - converted_term.replace("m", "mythic", Qt::CaseInsensitive); - break; - case 3: - converted_term.replace("c", "common", Qt::CaseInsensitive); - break; - case 4: - converted_term.replace("u", "uncommon", Qt::CaseInsensitive); - break; - case 5: - converted_term.replace("r", "rare", Qt::CaseInsensitive); - break; - case 6: - converted_term.replace("s", "special", Qt::CaseInsensitive); - break; - default: - break; - } - } - for (const auto &printings : info->getSets()) { for (const auto &printing : printings) { - if (printing.getProperty("rarity").compare(converted_term, Qt::CaseInsensitive) == 0) { + if (printing.getProperty("rarity").compare(convertedRarityTerm, Qt::CaseInsensitive) == 0) { return true; } } @@ -438,41 +509,25 @@ bool FilterItem::acceptRarity(const CardInfoPtr info) const bool FilterItem::relationCheck(int cardInfo) const { - bool result, conversion; - - // if int conversion fails, there's probably an operator at the start - result = (cardInfo == term.toInt(&conversion)); - if (!conversion) { - // leading whitespaces could cause indexing to fail - QString trimmedTerm = term.trimmed(); - // check whether it's a 2 char operator (<=, >=, or ==) - if (trimmedTerm[1] == '=') { - int termInt = trimmedTerm.mid(2).toInt(); - if (trimmedTerm.startsWith('<')) { - result = (cardInfo <= termInt); - } else if (trimmedTerm.startsWith('>')) { - result = (cardInfo >= termInt); - } else { - result = (cardInfo == termInt); - } - } else { - int termInt = trimmedTerm.mid(1).toInt(); - if (trimmedTerm.startsWith('<')) { - result = (cardInfo < termInt); - } else if (trimmedTerm.startsWith('>')) { - result = (cardInfo > termInt); - } else if (trimmedTerm.startsWith("=")) { - result = (cardInfo == termInt); - } else { - // the int conversion hasn't failed due to an operator at the start - result = false; - } - } + switch (numericOperator) { + case NumericOperator::Equal: + return cardInfo == numericOperand; + case NumericOperator::Less: + return cardInfo < numericOperand; + case NumericOperator::LessEqual: + return cardInfo <= numericOperand; + case NumericOperator::Greater: + return cardInfo > numericOperand; + case NumericOperator::GreaterEqual: + return cardInfo >= numericOperand; + case NumericOperator::Invalid: + return false; } - return result; + + return false; } -bool FilterItem::acceptCardAttr(const CardInfoPtr info, +bool FilterItem::acceptCardAttr(const CardInfoPtr &info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const { @@ -556,7 +611,7 @@ FilterTreeNode *FilterTree::termNode(const CardFilter *f) return termNode(f->attr(), f->type(), f->term()); } -bool FilterTree::testAttr(const CardInfoPtr info, const LogicMap *lm, const CardSearchLanguage &searchLanguage) const +bool FilterTree::testAttr(const CardInfoPtr &info, const LogicMap *lm, const CardSearchLanguage &searchLanguage) const { const FilterItemList *fil; bool status = true; @@ -589,7 +644,7 @@ bool FilterTree::testAttr(const CardInfoPtr info, const LogicMap *lm, const Card return status; } -bool FilterTree::acceptsCard(const CardInfoPtr info, const CardSearchLanguage &searchLanguage) const +bool FilterTree::acceptsCard(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const { for (auto i = childNodes.constBegin(); i != childNodes.constEnd(); i++) { if ((*i)->isEnabled() && !testAttr(info, *i, searchLanguage)) { diff --git a/libcockatrice_filters/libcockatrice/filters/filter_tree.h b/libcockatrice_filters/libcockatrice/filters/filter_tree.h index 0defd466b..85a57304d 100644 --- a/libcockatrice_filters/libcockatrice/filters/filter_tree.h +++ b/libcockatrice_filters/libcockatrice/filters/filter_tree.h @@ -174,13 +174,13 @@ public: } [[nodiscard]] bool - testTypeAnd(CardInfoPtr info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const; + testTypeAnd(const CardInfoPtr &info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const; [[nodiscard]] bool - testTypeAndNot(CardInfoPtr info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const; + testTypeAndNot(const CardInfoPtr &info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const; [[nodiscard]] bool - testTypeOr(CardInfoPtr info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const; + testTypeOr(const CardInfoPtr &info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const; [[nodiscard]] bool - testTypeOrNot(CardInfoPtr info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const; + testTypeOrNot(const CardInfoPtr &info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const; }; class FilterItem : public FilterTreeNode @@ -188,12 +188,33 @@ class FilterItem : public FilterTreeNode private: FilterItemList *const p; + /** + * Values derived from the filter term that never change while this node + * exists. Computing them in the constructor keeps them out of the per-card + * evaluation path. + */ + QString convertedColorTerm; + QString convertedRarityTerm; + QString sortedManaCostTerm; + QString uppercaseLoyaltyTerm; + QString lowercaseTerm; + + enum class NumericOperator + { + Invalid, + Equal, + Less, + LessEqual, + Greater, + GreaterEqual, + }; + NumericOperator numericOperator = NumericOperator::Invalid; + int numericOperand = 0; + public: const QString term; - FilterItem(QString trm, FilterItemList *parent) : p(parent), term(std::move(trm)) - { - } + FilterItem(QString trm, FilterItemList *parent); virtual ~FilterItem() = default; [[nodiscard]] CardFilter::Attr attr() const @@ -217,22 +238,22 @@ public: return true; } - [[nodiscard]] bool acceptName(CardInfoPtr info, const CardSearchLanguage &searchLanguage) const; - [[nodiscard]] bool acceptNameExact(CardInfoPtr info, const CardSearchLanguage &searchLanguage) const; - [[nodiscard]] bool acceptType(CardInfoPtr info) const; - [[nodiscard]] bool acceptMainType(CardInfoPtr info) const; - [[nodiscard]] bool acceptSubType(CardInfoPtr info) const; - [[nodiscard]] bool acceptColor(CardInfoPtr info) const; - [[nodiscard]] bool acceptText(CardInfoPtr info, const CardSearchLanguage &searchLanguage) const; - [[nodiscard]] bool acceptSet(CardInfoPtr info) const; - [[nodiscard]] bool acceptManaCost(CardInfoPtr info) const; - [[nodiscard]] bool acceptCmc(CardInfoPtr info) const; - [[nodiscard]] bool acceptPowerToughness(CardInfoPtr info, CardFilter::Attr attr) const; - [[nodiscard]] bool acceptLoyalty(CardInfoPtr info) const; - [[nodiscard]] bool acceptRarity(CardInfoPtr info) const; + [[nodiscard]] bool acceptName(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const; + [[nodiscard]] bool acceptNameExact(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const; + [[nodiscard]] bool acceptType(const CardInfoPtr &info) const; + [[nodiscard]] bool acceptMainType(const CardInfoPtr &info) const; + [[nodiscard]] bool acceptSubType(const CardInfoPtr &info) const; + [[nodiscard]] bool acceptColor(const CardInfoPtr &info) const; + [[nodiscard]] bool acceptText(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const; + [[nodiscard]] bool acceptSet(const CardInfoPtr &info) const; + [[nodiscard]] bool acceptManaCost(const CardInfoPtr &info) const; + [[nodiscard]] bool acceptCmc(const CardInfoPtr &info) const; + [[nodiscard]] bool acceptPowerToughness(const CardInfoPtr &info, CardFilter::Attr attr) const; + [[nodiscard]] bool acceptLoyalty(const CardInfoPtr &info) const; + [[nodiscard]] bool acceptRarity(const CardInfoPtr &info) const; [[nodiscard]] bool - acceptCardAttr(CardInfoPtr info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const; - [[nodiscard]] bool acceptFormat(CardInfoPtr info) const; + acceptCardAttr(const CardInfoPtr &info, CardFilter::Attr attr, const CardSearchLanguage &searchLanguage) const; + [[nodiscard]] bool acceptFormat(const CardInfoPtr &info) const; [[nodiscard]] bool relationCheck(int cardInfo) const; }; @@ -251,7 +272,7 @@ private: LogicMap *attrLogicMap(CardFilter::Attr attr); FilterItemList *attrTypeList(CardFilter::Attr attr, CardFilter::Type type); - bool testAttr(CardInfoPtr info, const LogicMap *lm, const CardSearchLanguage &searchLanguage) const; + bool testAttr(const CardInfoPtr &info, const LogicMap *lm, const CardSearchLanguage &searchLanguage) const; void nodeChanged() const override { @@ -290,7 +311,7 @@ public: return 0; } - [[nodiscard]] bool acceptsCard(CardInfoPtr info, const CardSearchLanguage &searchLanguage) const; + [[nodiscard]] bool acceptsCard(const CardInfoPtr &info, const CardSearchLanguage &searchLanguage) const; void removeFiltersByAttr(CardFilter::Attr filterType); void removeFilter(const CardFilter *toRemove); void clear(); diff --git a/libcockatrice_game/CMakeLists.txt b/libcockatrice_game/CMakeLists.txt new file mode 100644 index 000000000..ce68c5ea6 --- /dev/null +++ b/libcockatrice_game/CMakeLists.txt @@ -0,0 +1,77 @@ +set(HEADERS + libcockatrice/game/abstract_game.h + libcockatrice/game/board/arrow_data.h + libcockatrice/game/board/card_list.h + libcockatrice/game/board/card_state.h + libcockatrice/game/board/counter_state.h + libcockatrice/game/game.h + libcockatrice/game/game_event_handler.h + libcockatrice/game/game_meta_info.h + libcockatrice/game/game_state.h + libcockatrice/game/phase.h + libcockatrice/game/player/card_menu_action_type.h + libcockatrice/game/player/card_move_request.h + libcockatrice/game/player/event_processing_options.h + libcockatrice/game/player/move_top_cards_until_options.h + libcockatrice/game/player/player_actions.h + libcockatrice/game/player/player_event_handler.h + libcockatrice/game/player/player_info.h + libcockatrice/game/player/player_logic.h + libcockatrice/game/player/player_manager.h + libcockatrice/game/player/token_info.h + libcockatrice/game/replay.h + libcockatrice/game/translation.h + libcockatrice/game/zones/card_zone_algorithms.h + libcockatrice/game/zones/card_zone_logic.h + libcockatrice/game/zones/hand_zone_logic.h + libcockatrice/game/zones/pile_zone_logic.h + libcockatrice/game/zones/stack_zone_logic.h + libcockatrice/game/zones/table_zone_logic.h + libcockatrice/game/zones/view_zone_logic.h +) + +qt6_wrap_cpp(MOC_SOURCES ${HEADERS}) + +add_library( + libcockatrice_game STATIC + ${MOC_SOURCES} + libcockatrice/game/abstract_game.cpp + libcockatrice/game/board/arrow_data.cpp + libcockatrice/game/board/card_list.cpp + libcockatrice/game/board/card_state.cpp + libcockatrice/game/board/counter_state.cpp + libcockatrice/game/game.cpp + libcockatrice/game/game_event_handler.cpp + libcockatrice/game/game_meta_info.cpp + libcockatrice/game/game_state.cpp + libcockatrice/game/phase.cpp + libcockatrice/game/player/player_actions.cpp + libcockatrice/game/player/player_event_handler.cpp + libcockatrice/game/player/player_info.cpp + libcockatrice/game/player/player_logic.cpp + libcockatrice/game/player/player_manager.cpp + libcockatrice/game/replay.cpp + libcockatrice/game/zones/card_zone_logic.cpp + libcockatrice/game/zones/hand_zone_logic.cpp + libcockatrice/game/zones/pile_zone_logic.cpp + libcockatrice/game/zones/stack_zone_logic.cpp + libcockatrice/game/zones/table_zone_logic.cpp + libcockatrice/game/zones/view_zone_logic.cpp +) + +target_include_directories(libcockatrice_game PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +target_link_libraries( + libcockatrice_game + PUBLIC libcockatrice_card + libcockatrice_deck_list + libcockatrice_filters + libcockatrice_interfaces + libcockatrice_network + libcockatrice_protocol + libcockatrice_settings + libcockatrice_utility + ${QT_CORE_MODULE} + ${COCKATRICE_QT_VERSION_NAME}::Concurrent + ${COCKATRICE_QT_VERSION_NAME}::Gui +) diff --git a/cockatrice/src/game/abstract_game.cpp b/libcockatrice_game/libcockatrice/game/abstract_game.cpp similarity index 88% rename from cockatrice/src/game/abstract_game.cpp rename to libcockatrice_game/libcockatrice/game/abstract_game.cpp index efc575eff..a93f44018 100644 --- a/cockatrice/src/game/abstract_game.cpp +++ b/libcockatrice_game/libcockatrice/game/abstract_game.cpp @@ -18,8 +18,6 @@ AbstractGame::AbstractGame(QObject *_parent) : QObject(_parent) { gameMetaInfo = new GameMetaInfo(this); gameEventHandler = new GameEventHandler(this); - - activeCard = nullptr; } bool AbstractGame::isHost() const @@ -48,12 +46,7 @@ void AbstractGame::loadReplay(const GameReplay *replay) gameMetaInfo->setSpectatorsOmniscient(true); } -void AbstractGame::setActiveCard(CardItem *card) -{ - activeCard = card; -} - -CardItem *AbstractGame::getCard(int playerId, const QString &zoneName, int cardId) const +CardState *AbstractGame::getCard(int playerId, const QString &zoneName, int cardId) const { PlayerLogic *player = playerManager->getPlayer(playerId); if (!player) { diff --git a/cockatrice/src/game/abstract_game.h b/libcockatrice_game/libcockatrice/game/abstract_game.h similarity index 82% rename from cockatrice/src/game/abstract_game.h rename to libcockatrice_game/libcockatrice/game/abstract_game.h index 326af8ac9..00a95a3b8 100644 --- a/cockatrice/src/game/abstract_game.h +++ b/libcockatrice_game/libcockatrice/game/abstract_game.h @@ -11,14 +11,14 @@ #include #include -class CardItem; class AbstractClient; +class CardState; class GameEventHandler; class GameMetaInfo; class GameReplay; class GameState; class PlayerManager; - +class CardState; class AbstractGame : public QObject { Q_OBJECT @@ -30,7 +30,6 @@ public: GameState *gameState; GameEventHandler *gameEventHandler; PlayerManager *playerManager; - CardItem *activeCard; GameMetaInfo *getGameMetaInfo() { @@ -58,13 +57,7 @@ public: void loadReplay(const GameReplay *replay); - CardItem *getCard(int playerId, const QString &zoneName, int cardId) const; - - void setActiveCard(CardItem *card); - CardItem *getActiveCard() const - { - return activeCard; - } + CardState *getCard(int playerId, const QString &zoneName, int cardId) const; }; #endif // COCKATRICE_ABSTRACT_GAME_H diff --git a/cockatrice/src/game/board/arrow_data.cpp b/libcockatrice_game/libcockatrice/game/board/arrow_data.cpp similarity index 100% rename from cockatrice/src/game/board/arrow_data.cpp rename to libcockatrice_game/libcockatrice/game/board/arrow_data.cpp diff --git a/cockatrice/src/game/board/arrow_data.h b/libcockatrice_game/libcockatrice/game/board/arrow_data.h similarity index 100% rename from cockatrice/src/game/board/arrow_data.h rename to libcockatrice_game/libcockatrice/game/board/arrow_data.h diff --git a/cockatrice/src/game/board/card_list.cpp b/libcockatrice_game/libcockatrice/game/board/card_list.cpp similarity index 70% rename from cockatrice/src/game/board/card_list.cpp rename to libcockatrice_game/libcockatrice/game/board/card_list.cpp index 94bae67cd..361c51502 100644 --- a/cockatrice/src/game/board/card_list.cpp +++ b/libcockatrice_game/libcockatrice/game/board/card_list.cpp @@ -1,7 +1,6 @@ #include "card_list.h" -#include "../../game_graphics/board/card_item.h" -#include "libcockatrice/card/printing/exact_card.h" +#include "card_state.h" #include #include @@ -11,26 +10,26 @@ #include #include -CardList::CardList(bool _contentsKnown) : QList(), contentsKnown(_contentsKnown) +CardList::CardList(bool _contentsKnown) : QList(), contentsKnown(_contentsKnown) { } /** - * @brief Finds the CardItem with the given id in the list. + * @brief Finds the CardState with the given id in the list. * If contentsKnown is false, then this just returns the first element of the list. * * @param cardId The id of the card to find. * - * @returns A pointer to the CardItem, or a nullptr if not found. + * @returns A pointer to the CardState, or a nullptr if not found. */ -CardItem *CardList::findCard(const int cardId) const +CardState *CardList::findCard(const int cardId) const { if (!contentsKnown && !empty()) { return at(0); } else { - for (auto *cardItem : *this) { - if (cardItem->getId() == cardId) { - return cardItem; + for (auto *cardState : *this) { + if (cardState->getId() == cardId) { + return cardState; } } } @@ -38,7 +37,7 @@ CardItem *CardList::findCard(const int cardId) const } /** - * @brief sorts the list by using string comparison on properties extracted from the CardItem + * @brief sorts the list by using string comparison on properties extracted from the CardState * The cards are compared using each property in order. * If two cards have the same value for a property, then the next property in the list is used. * @@ -51,7 +50,7 @@ void CardList::sortBy(const QList &option) return; } - auto comparator = [&option](CardItem *a, CardItem *b) { + auto comparator = [&option](CardState *a, CardState *b) { for (auto prop : option) { auto extractor = getExtractorFor(prop); QString t1 = extractor(a); @@ -116,26 +115,26 @@ static QString getColorSortString(const CardInfo &c, bool appendAtEnd) } /** - * @brief returns the function that extracts the given property from the CardItem. + * @brief returns the function that extracts the given property from the CardState. */ -std::function CardList::getExtractorFor(SortOption option) +std::function CardList::getExtractorFor(SortOption option) { switch (option) { case NoSort: - return [](CardItem *) { return ""; }; + return [](CardState *) { return ""; }; case SortByMainType: - return [](CardItem *c) { return c->getCardInfo().getMainCardType(); }; + return [](CardState *c) { return c->getCardInfo().getMainCardType(); }; case SortByManaValue: // getCmc returns the int as a string. We pad with 0's so that string comp also works on it - return [](CardItem *c) { return c->getCard() ? c->getCardInfo().getCmc().rightJustified(4, '0') : ""; }; + return [](CardState *c) { return c->getCard() ? c->getCardInfo().getCmc().rightJustified(4, '0') : ""; }; case SortByColorGrouping: - return [](CardItem *c) { return c->getCard() ? getColorSortString(c->getCardInfo(), false) : ""; }; + return [](CardState *c) { return c->getCard() ? getColorSortString(c->getCardInfo(), false) : ""; }; case SortByName: - return [](CardItem *c) { return c->getName(); }; + return [](CardState *c) { return c->getName(); }; case SortByType: - return [](CardItem *c) { return c->getCardInfo().getCardType(); }; + return [](CardState *c) { return c->getCardInfo().getCardType(); }; case SortByManaCost: - return [](CardItem *c) { + return [](CardState *c) { if (!c->getCard()) { return QString(); } @@ -147,18 +146,18 @@ std::function CardList::getExtractorFor(SortOption option) return QString("%1%2").arg(info.getCmc(), 4, QChar('0')).arg(info.getManaCost()); }; case SortByColors: - return [](CardItem *c) { return c->getCard() ? getColorSortString(c->getCardInfo(), true) : ""; }; + return [](CardState *c) { return c->getCard() ? getColorSortString(c->getCardInfo(), true) : ""; }; case SortByPt: // do the same padding trick as above return - [](CardItem *c) { return c->getCard() ? c->getCardInfo().getPowTough().rightJustified(10, '0') : ""; }; + [](CardState *c) { return c->getCard() ? c->getCardInfo().getPowTough().rightJustified(10, '0') : ""; }; case SortBySet: - return [](CardItem *c) { return c->getCardInfo().getSetsNames(); }; + return [](CardState *c) { return c->getCardInfo().getSetsNames(); }; case SortByPrinting: - return [](CardItem *c) { return c->getProviderId(); }; + return [](CardState *c) { return c->getProviderId(); }; } // this line should never be reached qCWarning(CardListLog) << "cardlist.cpp: Could not find extractor for SortOption" << option; - return [](CardItem *) { return ""; }; + return [](CardState *) { return ""; }; } \ No newline at end of file diff --git a/cockatrice/src/game/board/card_list.h b/libcockatrice_game/libcockatrice/game/board/card_list.h similarity index 84% rename from cockatrice/src/game/board/card_list.h rename to libcockatrice_game/libcockatrice/game/board/card_list.h index b5427df0d..b3037d0f6 100644 --- a/cockatrice/src/game/board/card_list.h +++ b/libcockatrice_game/libcockatrice/game/board/card_list.h @@ -14,9 +14,9 @@ inline Q_LOGGING_CATEGORY(CardListLog, "card_list"); -class CardItem; +class CardState; -class CardList : public QList +class CardList : public QList { protected: bool contentsKnown; @@ -43,7 +43,7 @@ public: SortByPrinting }; explicit CardList(bool _contentsKnown); - CardItem *findCard(const int cardId) const; + CardState *findCard(const int cardId) const; bool getContentsKnown() const { return contentsKnown; @@ -51,7 +51,7 @@ public: void sortBy(const QList &options); - static std::function getExtractorFor(SortOption option); + static std::function getExtractorFor(SortOption option); }; #endif diff --git a/libcockatrice_game/libcockatrice/game/board/card_state.cpp b/libcockatrice_game/libcockatrice/game/board/card_state.cpp new file mode 100644 index 000000000..7e8c079a4 --- /dev/null +++ b/libcockatrice_game/libcockatrice/game/board/card_state.cpp @@ -0,0 +1,240 @@ +#include "card_state.h" + +#include "../zones/card_zone_logic.h" + +#include +#include +#include +#include + +void CardState::resetState(bool keepAnnotations) +{ + attacking = false; + counters.clear(); + pt.clear(); + if (!keepAnnotations) { + annotation.clear(); + } + attachedTo = nullptr; + attachedCards.clear(); + setTapped(false, false); + setDoesntUntap(false); + emit stateReset(); +} + +void CardState::setColor(const QString &_color) +{ + color = _color; + emit colorChanged(); +} + +void CardState::deleteView() +{ + emit viewDeleteRequested(); +} + +void CardState::setCardRef(const CardRef &_cardRef) +{ + if (cardRef == _cardRef) { + return; + } + + const CardRef oldCardRef = cardRef; + if (exactCard) { + disconnect(exactCard.getCardPtr().data(), nullptr, this, nullptr); + } + cardRef = _cardRef; + + emit cardRefChanged(oldCardRef, cardRef); + refreshCardInfo(); +} + +void CardState::refreshCardInfo() +{ + exactCard = CardDatabaseManager::query()->getCard(cardRef); + + if (!exactCard && !cardRef.name.isEmpty()) { + CardInfo::UiAttributes attributes = {.tableRow = -1}; + auto info = CardInfo::newInstance(cardRef.name, "", true, {}, {}, {}, {}, attributes); + exactCard = ExactCard(info); + } + if (exactCard) { + connect(exactCard.getCardPtr().data(), &CardInfo::pixmapUpdated, this, &CardState::cardPixmapUpdated); + } + + emit cardInfoChanged(); +} + +void CardState::setTapped(bool _tapped, bool canAnimate) +{ + if (tapped == _tapped) { + return; + } + + tapped = _tapped; + emit tappedChanged(tapped, canAnimate); + emit stateChanged(); +} + +void CardState::setFaceDown(bool _facedown) +{ + facedown = _facedown; + emit facedownChanged(facedown); + emit stateChanged(); +} + +QVariantList CardState::parsePT(const QString &pt) +{ + QVariantList ptList = QVariantList(); + if (!pt.isEmpty()) { + int sep = pt.indexOf('/'); + if (sep == 0) { + ptList.append(QVariant(pt.mid(1))); // cut off starting '/' and take full string + } else { + int start = 0; + for (;;) { + QString item = pt.mid(start, sep - start); + if (item.isEmpty()) { + ptList.append(QVariant(QString())); + } else if (item[0] == '+') { + ptList.append(QVariant(item.mid(1).toInt())); // add as int + } else if (item[0] == '-') { + ptList.append(QVariant(item.toInt())); // add as int + } else { + ptList.append(QVariant(item)); // add as qstring + } + if (sep == -1) { + break; + } + start = sep + 1; + sep = pt.indexOf('/', start); + } + } + } + return ptList; +} + +void CardState::setZone(CardZoneLogic *_zone) +{ + if (zone == _zone) { + return; + } + + zone = _zone; + emit zoneChanged(this, zone); + emit stateChanged(); +} + +void CardState::setAttacking(bool _attacking) +{ + if (attacking == _attacking) { + return; + } + attacking = _attacking; + emit attackingChanged(_attacking); + emit stateChanged(); +} + +void CardState::insertCounter(int id, int value) +{ + counters.insert(id, value); + + emit countersChanged(counters); + emit stateChanged(); +} + +void CardState::setCounter(int id, int value) +{ + if (value) { + counters[id] = value; + } else { + counters.remove(id); + } + + emit countersChanged(counters); + emit stateChanged(); +} + +void CardState::clearCounters() +{ + counters.clear(); + emit countersChanged(counters); + emit stateChanged(); +} + +void CardState::setAnnotation(const QString &_annotation) +{ + if (annotation == _annotation) { + return; + } + annotation = _annotation; + emit annotationChanged(annotation); + emit stateChanged(); +} + +void CardState::setPT(const QString &_pt) +{ + if (pt == _pt) { + return; + } + pt = _pt; + emit ptChanged(pt); + emit stateChanged(); +} + +void CardState::setDoesntUntap(bool _doesntUntap) +{ + if (doesntUntap == _doesntUntap) { + return; + } + doesntUntap = _doesntUntap; + emit doesntUntapChanged(_doesntUntap); + emit stateChanged(); +} + +void CardState::setDestroyOnZoneChange(bool _destroyOnZoneChange) +{ + if (destroyOnZoneChange == _destroyOnZoneChange) { + return; + } + + destroyOnZoneChange = _destroyOnZoneChange; + emit destroyOnZoneChangeChanged(_destroyOnZoneChange); + emit stateChanged(); +} + +void CardState::setAttachedTo(CardState *_attachedTo) +{ + if (attachedTo == _attachedTo) { + return; + } + + if (attachedTo != nullptr) { + attachedTo->removeAttachedCard(this); + } + + setGridPoint(QPoint(-1, getGridPoint().y())); + attachedTo = _attachedTo; + emit attachedToChanged(_attachedTo); + emit stateChanged(); + + if (attachedTo != nullptr) { + // If the zone is being torn down, it might already be null by the time a card tries to un-attach all its + // attached cards. The graphics item that owns this state handles the teardown in that case. + CardZoneLogic *parentZone = attachedTo->getZone(); + if (parentZone == nullptr) { + return; + } + emit parentZone->cardAdded(this); + attachedTo->addAttachedCard(this); + if (getZone() != parentZone) { + parentZone->reorganizeCards(); + } + } else if (getZone() != nullptr) { + emit getZone() -> cardAdded(this); + } + + if (getZone() != nullptr) { + getZone()->reorganizeCards(); + } +} diff --git a/libcockatrice_game/libcockatrice/game/board/card_state.h b/libcockatrice_game/libcockatrice/game/board/card_state.h new file mode 100644 index 000000000..fa1e81393 --- /dev/null +++ b/libcockatrice_game/libcockatrice/game/board/card_state.h @@ -0,0 +1,243 @@ +#ifndef COCKATRICE_CARD_STATE_H +#define COCKATRICE_CARD_STATE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class CardZoneLogic; +class CardState : public QObject +{ + Q_OBJECT + +private: + int id = -1; + CardRef cardRef; + ExactCard exactCard; + bool tapped = false; + bool facedown = false; + QPoint gridPoint; + + bool attacking = false; + QMap counters; + QString annotation; + QString pt; + bool doesntUntap = false; + bool destroyOnZoneChange = false; + QString color; + + CardState *attachedTo = nullptr; + QList attachedCards; + CardZoneLogic *zone = nullptr; + +signals: + void stateChanged(); + + void cardRefChanged(const CardRef &oldCardRef, const CardRef &newCardRef); + void cardInfoChanged(); + void cardPixmapUpdated(); + void tappedChanged(bool newTapped, bool canAnimate); + void facedownChanged(bool newFaceDown); + void stateReset(); + void colorChanged(); + + void attackingChanged(bool newValue); + void countersChanged(const QMap &newCounters); + void annotationChanged(const QString &newAnnotation); + void ptChanged(const QString &newPt); + void doesntUntapChanged(bool newValue); + void destroyOnZoneChangeChanged(bool newValue); + void attachedToChanged(CardState *newAttachedTo); + void zoneChanged(CardState *changedCard, CardZoneLogic *newZone); + + void viewDeleteRequested(); + +public: + explicit CardState(QObject *parent, CardZoneLogic *_zone = nullptr, const CardRef &_cardRef = {}, int _id = -1) + : QObject(parent), id(_id), cardRef(_cardRef), zone(_zone) + { + } + + void resetState(bool keepAnnotations); + + /** + * @brief Asks the card view owning this state to tear itself down. + * + * The state never holds a card item directly, so it cannot delete one. It asks instead and lets the view run + * its own teardown, which detaches attachments, drops it from the active card slot and unregisters its + * animations before the graphics object goes away. + */ + void deleteView(); + + int getId() const + { + return id; + } + + void setId(int _id) + { + id = _id; + } + + const CardRef &getCardRef() const + { + return cardRef; + } + + void setCardRef(const CardRef &_cardRef); + + QString getName() const + { + return cardRef.name; + } + + QString getProviderId() const + { + return cardRef.providerId; + } + + ExactCard getCard() const + { + return exactCard; + } + + const CardInfo &getCardInfo() const + { + return exactCard.getInfo(); + } + + void refreshCardInfo(); + + bool getTapped() const + { + return tapped; + } + + void setTapped(bool _tapped, bool canAnimate = false); + + bool getFaceDown() const + { + return facedown; + } + + void setFaceDown(bool _facedown); + + const QPoint &getGridPoint() const + { + return gridPoint; + } + + void setGridPoint(const QPoint &_gridPoint) + { + gridPoint = _gridPoint; + } + + CardZoneLogic *getZone() const + { + return zone; + } + + void setZone(CardZoneLogic *_zone); + + bool getAttacking() const + { + return attacking; + } + void setAttacking(bool _attacking); + + const QMap &getCounters() const + { + return counters; + } + + void insertCounter(int id, int value); + + void setCounter(int id, int value); + + void clearCounters(); + + QString getAnnotation() const + { + return annotation; + } + + void setAnnotation(const QString &_annotation); + + QString getPT() const + { + return pt; + } + + void setPT(const QString &_pt); + + /** + * @brief Parses a string representing a p/t in order to extract the values from it. + * + * If the string contains '/', the string will be split at the '/' and each side will be parsed separately, + * which means the result list will have two elements. + * + * If '/' is not found, then the entire string is parsed together, which means the result list will + * have a single element. + * + * If either side of the split is empty, there will also only be a single element in the result list. + * + * This function will attempt to parse each substring as an int first, handling plus and minus prefixes. + * If successful, it will put the parsed value into the QVariant as an int. + * If failed, it will just put the substring into the QVariant as a QString. + * + * @param pt The p/t string + * @return A QVariantList that can contain one or two elements, where each QVariant can be either int or QString + */ + static QVariantList parsePT(const QString &pt); + + [[nodiscard]] QString getColor() const + { + return color; + } + + void setColor(const QString &_color); + + bool getDoesntUntap() const + { + return doesntUntap; + } + + void setDoesntUntap(bool _doesntUntap); + + bool getDestroyOnZoneChange() const + { + return destroyOnZoneChange; + } + + void setDestroyOnZoneChange(bool _destroyOnZoneChange); + + CardState *getAttachedTo() const + { + return attachedTo; + } + + void setAttachedTo(CardState *_attachedTo); + + void addAttachedCard(CardState *card) + { + attachedCards.append(card); + } + + void removeAttachedCard(CardState *card) + { + attachedCards.removeOne(card); + } + + const QList &getAttachedCards() const + { + return attachedCards; + } +}; + +#endif // COCKATRICE_CARD_STATE_H diff --git a/cockatrice/src/game/board/counter_state.cpp b/libcockatrice_game/libcockatrice/game/board/counter_state.cpp similarity index 100% rename from cockatrice/src/game/board/counter_state.cpp rename to libcockatrice_game/libcockatrice/game/board/counter_state.cpp diff --git a/cockatrice/src/game/board/counter_state.h b/libcockatrice_game/libcockatrice/game/board/counter_state.h similarity index 100% rename from cockatrice/src/game/board/counter_state.h rename to libcockatrice_game/libcockatrice/game/board/counter_state.h diff --git a/cockatrice/src/game/game.cpp b/libcockatrice_game/libcockatrice/game/game.cpp similarity index 75% rename from cockatrice/src/game/game.cpp rename to libcockatrice_game/libcockatrice/game/game.cpp index 7343d052a..a1a4a31a8 100644 --- a/cockatrice/src/game/game.cpp +++ b/libcockatrice_game/libcockatrice/game/game.cpp @@ -16,13 +16,16 @@ Game::Game(QObject *_parent, bool isLocalGame, QList &_clients, const Event_GameJoined &event, - const QMap &_roomGameTypes) + const QMap &_roomGameTypes, + IInterfaceSettingsProvider *interfaceSettings, + ICardPreferenceProvider *cardPreferences) : AbstractGame(_parent) { gameMetaInfo->setFromProto(event.game_info()); gameMetaInfo->setRoomGameTypes(_roomGameTypes); gameState = new GameState(this, 0, event.host_id(), isLocalGame, _clients, false, event.resuming(), -1, false); connect(gameMetaInfo, &GameMetaInfo::startedChanged, gameState, &GameState::onStartedChanged); - playerManager = new PlayerManager(this, event.player_id(), event.judge(), event.spectator()); + playerManager = new PlayerManager(this, event.player_id(), event.judge(), event.spectator(), interfaceSettings, + cardPreferences); gameMetaInfo->setStarted(false); } diff --git a/cockatrice/src/game/game.h b/libcockatrice_game/libcockatrice/game/game.h similarity index 69% rename from cockatrice/src/game/game.h rename to libcockatrice_game/libcockatrice/game/game.h index 6a309cfb2..f80b75ea5 100644 --- a/cockatrice/src/game/game.h +++ b/libcockatrice_game/libcockatrice/game/game.h @@ -18,6 +18,9 @@ class QString; template class QMap; template class QList; +class ICardPreferenceProvider; +class IInterfaceSettingsProvider; + class Game : public AbstractGame { Q_OBJECT @@ -27,7 +30,9 @@ public: bool isLocalGame, QList &_clients, const Event_GameJoined &event, - const QMap &_roomGameTypes); + const QMap &_roomGameTypes, + IInterfaceSettingsProvider *interfaceSettings = nullptr, + ICardPreferenceProvider *cardPreferences = nullptr); }; #endif // COCKATRICE_GAME_H diff --git a/cockatrice/src/game/game_event_handler.cpp b/libcockatrice_game/libcockatrice/game/game_event_handler.cpp similarity index 89% rename from cockatrice/src/game/game_event_handler.cpp rename to libcockatrice_game/libcockatrice/game/game_event_handler.cpp index 7b08af47c..856475dfa 100644 --- a/cockatrice/src/game/game_event_handler.cpp +++ b/libcockatrice_game/libcockatrice/game/game_event_handler.cpp @@ -28,12 +28,20 @@ #include #include #include +#include #include +#include #include #include #include +#include #include #include +#include +#include +#include +#include +#include #include #include #include @@ -48,6 +56,7 @@ #include #include #include +#include #include #include @@ -223,6 +232,89 @@ void GameEventHandler::handleActivePhaseChanged(int phase) sendGameCommand(cmd); } +void GameEventHandler::handleUntapAll() +{ + Command_SetCardAttr cmd; + cmd.set_zone(ZoneNames::TABLE); + cmd.set_attribute(AttrTapped); + cmd.set_attr_value("0"); + + sendGameCommand(cmd); +} + +void GameEventHandler::handleDrawCard() +{ + Command_DrawCards cmd; + cmd.set_number(1); + + sendGameCommand(cmd); +} + +PendingCommand *GameEventHandler::prepareDeckSelect(const QString &deckList) +{ + Command_DeckSelect cmd; + cmd.set_deck(deckList.toStdString()); + + return prepareGameCommand(cmd); +} + +PendingCommand *GameEventHandler::prepareDeckSelectId(int deckId) +{ + Command_DeckSelect cmd; + cmd.set_deck_id(deckId); + + return prepareGameCommand(cmd); +} + +PendingCommand *GameEventHandler::prepareSetPlaymat(const PlaymatInfo &playmat) +{ + Command_SetPlaymat cmd; + auto *pp = cmd.mutable_playmat_params(); + pp->set_card_name(playmat.card.name.toStdString()); + pp->set_card_provider_id(playmat.card.providerId.toStdString()); + pp->set_margin_pct_l(playmat.params.marginPctL); + pp->set_margin_pct_r(playmat.params.marginPctR); + pp->set_vertical_offset(playmat.params.verticalOffset); + pp->set_zoom(playmat.params.zoom); + + return prepareGameCommand(cmd); +} + +void GameEventHandler::sendReadyStart(bool ready, int playerId) +{ + Command_ReadyStart cmd; + cmd.set_ready(ready); + + sendGameCommand(cmd, playerId); +} + +void GameEventHandler::sendForceStart(int playerId) +{ + Command_ReadyStart cmd; + cmd.set_force_start(true); + cmd.set_ready(true); + + sendGameCommand(cmd, playerId); +} + +void GameEventHandler::sendSetSideboardLock(bool locked, int playerId) +{ + Command_SetSideboardLock cmd; + cmd.set_locked(locked); + + sendGameCommand(cmd, playerId); +} + +void GameEventHandler::sendSetSideboardPlan(const QList &plan, int playerId) +{ + Command_SetSideboardPlan cmd; + for (const auto &zone : plan) { + cmd.add_move_list()->CopyFrom(zone); + } + + sendGameCommand(cmd, playerId); +} + void GameEventHandler::handleGameLeft() { sendGameCommand(Command_LeaveGame()); diff --git a/cockatrice/src/game/game_event_handler.h b/libcockatrice_game/libcockatrice/game/game_event_handler.h similarity index 83% rename from cockatrice/src/game/game_event_handler.h rename to libcockatrice_game/libcockatrice/game/game_event_handler.h index 30021d295..f7b11780b 100644 --- a/cockatrice/src/game/game_event_handler.h +++ b/libcockatrice_game/libcockatrice/game/game_event_handler.h @@ -29,13 +29,14 @@ // GameEventContext, ServerInfo_Player and ServerInfo_User are passed by value // to signals and slots, so the moc-generated code needs the complete types. #include +#include #include #include +#include class AbstractClient; class AbstractGame; class GameEventContainer; -class GameEventContext; class PendingCommand; class PlayerLogic; class Response; @@ -127,6 +128,12 @@ public: */ void handleActivePhaseChanged(int phase); + /** @brief Request untapping every permanent on the table. */ + void handleUntapAll(); + + /** @brief Request drawing a single card. */ + void handleDrawCard(); + /** @brief Leave the current game session. */ void handleGameLeft(); @@ -145,6 +152,70 @@ public: void handleArrowDeletion(int creatorId, int arrowId); void handleArrowDeletionFinished(const Response &response, int creatorId, int arrowId); + /** + * @brief Prepare a deck selection from its native deck list text. + * + * The returned command is not sent yet, so the caller can connect to + * PendingCommand::finished before handing it to sendGameCommand(). + * + * @param deckList Serialized deck list. + * @return The pending command awaiting the deck download response. + */ + PendingCommand *prepareDeckSelect(const QString &deckList); + + /** + * @brief Prepare a deck selection for a deck stored on the server. + * + * The returned command is not sent yet, so the caller can connect to + * PendingCommand::finished before handing it to sendGameCommand(). + * + * @param deckId Server-side deck identifier. + * @return The pending command awaiting the deck download response. + */ + PendingCommand *prepareDeckSelectId(int deckId); + + /** + * @brief Build the playmat command for a player without sending it. + * + * The returned command is not sent yet, so the caller can connect to + * PendingCommand::finished before handing it to sendGameCommand(). + * + * @param playmat Resolved playmat card and positioning parameters. + * @return The pending command awaiting the playmat response. + */ + PendingCommand *prepareSetPlaymat(const PlaymatInfo &playmat); + + /** + * @brief Set a player's ready state. + * + * @param ready Whether the player is ready. + * @param playerId Player the state applies to. + */ + void sendReadyStart(bool ready, int playerId); + + /** + * @brief Force-start the game, kicking every player who is not ready. + * + * @param playerId Player issuing the force start. + */ + void sendForceStart(int playerId); + + /** + * @brief Lock or unlock a player's sideboard. + * + * @param locked Whether the sideboard is locked. + * @param playerId Player the lock applies to. + */ + void sendSetSideboardLock(bool locked, int playerId); + + /** + * @brief Send a player's sideboard plan. + * + * @param plan Cards to move between zones when leaving sideboarding. + * @param playerId Player the plan belongs to. + */ + void sendSetSideboardPlan(const QList &plan, int playerId); + /** @} */ /** @name Incoming event processing diff --git a/cockatrice/src/game/game_meta_info.cpp b/libcockatrice_game/libcockatrice/game/game_meta_info.cpp similarity index 100% rename from cockatrice/src/game/game_meta_info.cpp rename to libcockatrice_game/libcockatrice/game/game_meta_info.cpp diff --git a/cockatrice/src/game/game_meta_info.h b/libcockatrice_game/libcockatrice/game/game_meta_info.h similarity index 100% rename from cockatrice/src/game/game_meta_info.h rename to libcockatrice_game/libcockatrice/game/game_meta_info.h diff --git a/cockatrice/src/game/game_state.cpp b/libcockatrice_game/libcockatrice/game/game_state.cpp similarity index 100% rename from cockatrice/src/game/game_state.cpp rename to libcockatrice_game/libcockatrice/game/game_state.cpp diff --git a/cockatrice/src/game/game_state.h b/libcockatrice_game/libcockatrice/game/game_state.h similarity index 100% rename from cockatrice/src/game/game_state.h rename to libcockatrice_game/libcockatrice/game/game_state.h diff --git a/cockatrice/src/game/phase.cpp b/libcockatrice_game/libcockatrice/game/phase.cpp similarity index 100% rename from cockatrice/src/game/phase.cpp rename to libcockatrice_game/libcockatrice/game/phase.cpp diff --git a/cockatrice/src/game/phase.h b/libcockatrice_game/libcockatrice/game/phase.h similarity index 100% rename from cockatrice/src/game/phase.h rename to libcockatrice_game/libcockatrice/game/phase.h diff --git a/cockatrice/src/game_graphics/player/card_menu_action_type.h b/libcockatrice_game/libcockatrice/game/player/card_menu_action_type.h similarity index 95% rename from cockatrice/src/game_graphics/player/card_menu_action_type.h rename to libcockatrice_game/libcockatrice/game/player/card_menu_action_type.h index 4cae22716..e5c4cd84e 100644 --- a/cockatrice/src/game_graphics/player/card_menu_action_type.h +++ b/libcockatrice_game/libcockatrice/game/player/card_menu_action_type.h @@ -1,6 +1,6 @@ /** * @file card_menu_action_type.h - * @ingroup GameMenusPlayers + * @ingroup GameLogicPlayers */ //! \todo Document this file. diff --git a/libcockatrice_game/libcockatrice/game/player/card_move_request.h b/libcockatrice_game/libcockatrice/game/player/card_move_request.h new file mode 100644 index 000000000..bed46ab60 --- /dev/null +++ b/libcockatrice_game/libcockatrice/game/player/card_move_request.h @@ -0,0 +1,22 @@ +/** + * @file card_move_request.h + * @ingroup GameLogicActions + */ +//! \todo Document this file. + +#ifndef COCKATRICE_CARD_MOVE_REQUEST_H +#define COCKATRICE_CARD_MOVE_REQUEST_H + +#include + +/** + * @brief Describes one card a view wants moved, in logic terms rather than wire terms. + */ +struct CardMoveRequest +{ + int cardId = -1; + bool faceDown = false; + QString pt = {}; +}; + +#endif // COCKATRICE_CARD_MOVE_REQUEST_H diff --git a/cockatrice/src/game/player/event_processing_options.h b/libcockatrice_game/libcockatrice/game/player/event_processing_options.h similarity index 100% rename from cockatrice/src/game/player/event_processing_options.h rename to libcockatrice_game/libcockatrice/game/player/event_processing_options.h diff --git a/libcockatrice_game/libcockatrice/game/player/move_top_cards_until_options.h b/libcockatrice_game/libcockatrice/game/player/move_top_cards_until_options.h new file mode 100644 index 000000000..da8028b0f --- /dev/null +++ b/libcockatrice_game/libcockatrice/game/player/move_top_cards_until_options.h @@ -0,0 +1,19 @@ +/** + * @file move_top_cards_until_options.h + * @ingroup GameLogicPlayers + */ +//! \todo Document this file. + +#ifndef COCKATRICE_MOVE_TOP_CARDS_UNTIL_OPTIONS_H +#define COCKATRICE_MOVE_TOP_CARDS_UNTIL_OPTIONS_H + +#include + +struct MoveTopCardsUntilOptions +{ + QStringList exprs = {}; + int numberOfHits = 1; + bool autoPlay = false; +}; + +#endif // COCKATRICE_MOVE_TOP_CARDS_UNTIL_OPTIONS_H diff --git a/cockatrice/src/game/player/player_actions.cpp b/libcockatrice_game/libcockatrice/game/player/player_actions.cpp similarity index 84% rename from cockatrice/src/game/player/player_actions.cpp rename to libcockatrice_game/libcockatrice/game/player/player_actions.cpp index 35682ede8..90823fec6 100644 --- a/cockatrice/src/game/player/player_actions.cpp +++ b/libcockatrice_game/libcockatrice/game/player/player_actions.cpp @@ -1,13 +1,8 @@ #include "player_actions.h" -#include "../../client/settings/cache_settings.h" -#include "../../game_graphics/board/card_item.h" -#include "../../game_graphics/dialogs/dlg_create_token.h" -#include "../../game_graphics/dialogs/dlg_move_top_cards_until.h" -#include "../../game_graphics/player/card_menu_action_type.h" -#include "../../game_graphics/zones/table_zone.h" -#include "../../interface/deck_loader/loaded_deck.h" #include "../abstract_game.h" +#include "../board/arrow_data.h" +#include "../board/card_state.h" #include "../board/counter_state.h" #include "../game_event_handler.h" #include "../zones/card_zone_logic.h" @@ -31,8 +26,6 @@ #include #include #include -#include -#include #include #include #include @@ -51,10 +44,15 @@ #include #include #include +#include +#include +#include #include #include +#include #include #include +#include #include #include #include @@ -64,11 +62,12 @@ #include #include #include +#include #include #include -#include -#include +#include #include +#include #include #include #include @@ -80,8 +79,11 @@ // milliseconds in between triggers of the move top cards until action static constexpr int MOVE_TOP_CARD_UNTIL_INTERVAL = 100; -PlayerActions::PlayerActions(PlayerLogic *_player) - : QObject(_player), player(_player), lastTokenTableRow(0), movingCardsUntil(false) +PlayerActions::PlayerActions(PlayerLogic *_player, + IInterfaceSettingsProvider *_interfaceSettings, + ICardPreferenceProvider *_cardPreferences) + : QObject(_player), player(_player), interfaceSettings(_interfaceSettings), cardPreferences(_cardPreferences), + lastTokenTableRow(0), movingCardsUntil(false) { connect(this, &PlayerActions::requestZoneViewToggle, player, &PlayerLogic::onRequestZoneViewToggle); @@ -91,7 +93,27 @@ PlayerActions::PlayerActions(PlayerLogic *_player) connect(moveTopCardTimer, &QTimer::timeout, [this]() { actMoveTopCardToPlay(); }); } -void PlayerActions::playCard(CardItem *card, bool faceDown) +bool PlayerActions::getPlayToStack() const +{ + return interfaceSettings ? interfaceSettings->getPlayToStack() : true; +} + +int PlayerActions::getStartingHandSize() const +{ + return interfaceSettings ? interfaceSettings->getStartingHandSize() : 7; +} + +bool PlayerActions::getAnnotateTokens() const +{ + return interfaceSettings ? interfaceSettings->getAnnotateTokens() : false; +} + +QString PlayerActions::getCardPreferenceOverride(const QString &cardName) const +{ + return cardPreferences ? cardPreferences->getCardPreferenceOverride(cardName) : QString(); +} + +void PlayerActions::playCard(CardState *card, bool faceDown) { if (card == nullptr) { return; @@ -112,7 +134,7 @@ void PlayerActions::playCard(CardItem *card, bool faceDown) const CardInfo &info = exactCard.getInfo(); int tableRow = info.getUiAttributes().tableRow; - bool playToStack = SettingsCache::instance().userInterface().getPlayToStack(); + bool playToStack = getPlayToStack(); QString currentZone = card->getZone()->getName(); if (!faceDown && currentZone == ZoneNames::STACK && tableRow == 3) { cmd.set_target_zone(ZoneNames::GRAVE); @@ -125,7 +147,7 @@ void PlayerActions::playCard(CardItem *card, bool faceDown) cmd.set_y(0); } else { tableRow = faceDown ? 2 : info.getUiAttributes().tableRow; - QPoint gridPoint = QPoint(-1, TableZone::tableRowToGridY(tableRow)); + QPoint gridPoint = QPoint(-1, TableZoneLogic::tableRowToGridY(tableRow)); cardToMove->set_face_down(faceDown); if (!faceDown) { cardToMove->set_pt(info.getPowTough().toStdString()); @@ -144,7 +166,7 @@ void PlayerActions::playCard(CardItem *card, bool faceDown) * Like {@link PlayerActions::playCard}, but forces the card to be played to the table zone. * Cards with tablerow 3 (the stack) will be played to tablerow 1 (the noncreatures row). */ -void PlayerActions::playCardToTable(const CardItem *card, bool faceDown) +void PlayerActions::playCardToTable(const CardState *card, bool faceDown) { if (card == nullptr) { return; @@ -165,7 +187,7 @@ void PlayerActions::playCardToTable(const CardItem *card, bool faceDown) const CardInfo &info = exactCard.getInfo(); int tableRow = faceDown ? 2 : info.getUiAttributes().tableRow; - QPoint gridPoint = QPoint(-1, TableZone::tableRowToGridY(tableRow)); + QPoint gridPoint = QPoint(-1, TableZoneLogic::tableRowToGridY(tableRow)); cardToMove->set_face_down(faceDown); if (!faceDown) { cardToMove->set_pt(info.getPowTough().toStdString()); @@ -177,6 +199,60 @@ void PlayerActions::playCardToTable(const CardItem *card, bool faceDown) sendGameCommand(cmd); } +void PlayerActions::attachCards(CardState *startCard, const CardState *targetCard) +{ + if (targetCard->getAttachedTo() || targetCard->getZone()->getName() != ZoneNames::TABLE) { + return; + } + + // move card onto table first if attaching from some other zone + if (startCard->getZone()->getName() != ZoneNames::TABLE) { + playCardToTable(startCard, false); + } + + Command_AttachCard cmd; + cmd.set_start_zone(ZoneNames::TABLE); + cmd.set_card_id(startCard->getId()); + cmd.set_target_player_id(targetCard->getZone()->getPlayer()->getPlayerInfo()->getId()); + cmd.set_target_zone(targetCard->getZone()->getName().toStdString()); + cmd.set_target_card_id(targetCard->getId()); + sendGameCommand(cmd); +} + +void PlayerActions::createArrow(CardState *startCard, const ArrowData &arrow, int deleteInPhase) +{ + Command_CreateArrow cmd; + cmd.mutable_arrow_color()->CopyFrom(convertQColorToColor(arrow.color)); + cmd.set_start_player_id(arrow.startPlayerId); + cmd.set_start_zone(arrow.startZone.toStdString()); + cmd.set_start_card_id(arrow.startCardId); + cmd.set_target_player_id(arrow.targetPlayerId); + + if (!arrow.isPlayerTargeted()) { + cmd.set_target_zone(arrow.targetZone.toStdString()); + cmd.set_target_card_id(arrow.targetCardId); + } + + // if the card is in hand then it has just been moved to the stack or table as part of drawing the arrow + if (arrow.startZone == ZoneNames::HAND) { + CardInfoPtr ci = startCard->getCard().getCardPtr(); + bool playToStack = getPlayToStack(); + if (ci && ((!playToStack && ci->getUiAttributes().tableRow == 3) || + (playToStack && ci->getUiAttributes().tableRow != 0 && + startCard->getZone()->getName() != ZoneNames::STACK))) { + cmd.set_start_zone(ZoneNames::STACK); + } else { + cmd.set_start_zone(playToStack ? ZoneNames::STACK : ZoneNames::TABLE); + } + } + + if (deleteInPhase != 0) { + cmd.set_delete_in_phase(deleteInPhase); + } + + sendGameCommand(cmd); +} + void PlayerActions::actViewLibrary() { emit requestZoneViewToggle(ZoneNames::DECK, -1); @@ -355,7 +431,7 @@ void PlayerActions::actDrawCard() void PlayerActions::actRequestMulliganDialog() { - int startSize = SettingsCache::instance().userInterface().getStartingHandSize(); + int startSize = getStartingHandSize(); int handSize = player->getHandZone()->getCards().size(); int deckSize = player->getDeckZone()->getCards().size() + handSize; @@ -371,7 +447,7 @@ void PlayerActions::actMulligan(int number) } doMulligan(number); - SettingsCache::instance().userInterface().setStartingHandSize(number); + emit startingHandSizeChosen(number); } void PlayerActions::actMulliganSameSize() @@ -549,7 +625,7 @@ void PlayerActions::moveTopCardsUntil(const QString &expr, MoveTopCardsUntilOpti } } -void PlayerActions::moveOneCardUntil(CardItem *card) +void PlayerActions::moveOneCardUntil(CardState *card) { moveTopCardTimer->stop(); @@ -741,61 +817,6 @@ void PlayerActions::actMoveBottomCardToTop() sendGameCommand(cmd); } -/** - * Selects all cards in the given zone. - * - * @param zone The zone to select from - * @param filter A predicate to filter which cards are selected. Defaults to always returning true. - */ -static void selectCardsInZone( - const CardZoneLogic *zone, - std::function filter = [](const CardItem *) { return true; }) -{ - if (!zone) { - return; - } - - for (auto &cardItem : zone->getCards()) { - if (cardItem && filter(cardItem)) { - cardItem->setSelected(true); - } - } -} - -void PlayerActions::actSelectAll() -{ - const CardItem *card = player->getGame()->getActiveCard(); - if (!card) { - return; - } - - selectCardsInZone(card->getZone()); -} - -void PlayerActions::actSelectRow() -{ - const CardItem *card = player->getGame()->getActiveCard(); - if (!card) { - return; - } - - auto isSameRow = [card](const CardItem *cardItem) { - return qAbs(card->scenePos().y() - cardItem->scenePos().y()) < 50; - }; - selectCardsInZone(card->getZone(), isSameRow); -} - -void PlayerActions::actSelectColumn() -{ - const CardItem *card = player->getGame()->getActiveCard(); - if (!card) { - return; - } - - auto isSameColumn = [card](const CardItem *cardItem) { return cardItem->x() == card->x(); }; - selectCardsInZone(card->getZone(), isSameColumn); -} - void PlayerActions::actDrawBottomCard() { if (player->getDeckZone()->getCards().empty()) { @@ -929,7 +950,7 @@ void PlayerActions::actCreateToken(TokenInfo tokenToCreate) if (correctedCard) { lastTokenInfo.name = correctedCard.getName(); int tableRow = lastTokenInfo.faceDown ? 2 : correctedCard.getInfo().getUiAttributes().tableRow; - lastTokenTableRow = TableZone::tableRowToGridY(tableRow); + lastTokenTableRow = TableZoneLogic::tableRowToGridY(tableRow); if (lastTokenInfo.pt.isEmpty()) { lastTokenInfo.pt = correctedCard.getInfo().getPowTough(); } @@ -975,15 +996,14 @@ void PlayerActions::setLastTokenInfo(CardInfoPtr cardInfo) return; } - lastTokenInfo = { - .name = cardInfo->getName(), - .color = cardInfo->getColors().isEmpty() ? QString() : cardInfo->getColors().left(1).toLower(), - .pt = cardInfo->getPowTough(), - .annotation = SettingsCache::instance().userInterface().getAnnotateTokens() ? cardInfo->getText() : "", - .destroy = true, - .providerId = SettingsCache::instance().cardOverrides().getCardPreferenceOverride(cardInfo->getName())}; + lastTokenInfo = {.name = cardInfo->getName(), + .color = cardInfo->getColors().isEmpty() ? QString() : cardInfo->getColors().left(1).toLower(), + .pt = cardInfo->getPowTough(), + .annotation = getAnnotateTokens() ? cardInfo->getText() : "", + .destroy = true, + .providerId = getCardPreferenceOverride(cardInfo->getName())}; - lastTokenTableRow = TableZone::tableRowToGridY(cardInfo->getUiAttributes().tableRow); + lastTokenTableRow = TableZoneLogic::tableRowToGridY(cardInfo->getUiAttributes().tableRow); emit requestEnableAndSetCreateAnotherTokenAction(lastTokenInfo.name); } @@ -1001,25 +1021,20 @@ void PlayerActions::actCreatePredefinedToken() actCreateAnotherToken(); } -void PlayerActions::actCreateRelatedCard() +void PlayerActions::actCreateRelatedCard(const CardState *sourceCard, int index) { - const CardItem *sourceCard = player->getGame()->getActiveCard(); - if (!sourceCard) { return; } - auto *action = static_cast(sender()); - // If there is a better way of passing a CardRelation through a QAction, please add it here. auto relatedCards = sourceCard->getCardInfo().getAllRelatedCards(); - CardRelation *cardRelation = relatedCards.at(action->data().toInt()); + CardRelation *cardRelation = relatedCards.at(index); actRequestCreateRelatedFromRelationDialog(sourceCard, cardRelation); } -void PlayerActions::actCreateAllRelatedCards() +void PlayerActions::actCreateAllRelatedCards(const CardState *sourceCard) { - const CardItem *sourceCard = player->getGame()->getActiveCard(); if (!sourceCard) { return; } @@ -1105,13 +1120,13 @@ void PlayerActions::actCreateAllRelatedCards() } } -void PlayerActions::actRequestCreateRelatedFromRelationDialog(const CardItem *sourceCard, +void PlayerActions::actRequestCreateRelatedFromRelationDialog(const CardState *sourceCard, const CardRelation *cardRelation) { emit requestCreateRelatedFromRelationDialog(sourceCard, cardRelation); } -bool PlayerActions::createRelatedFromRelation(const CardItem *sourceCard, +bool PlayerActions::createRelatedFromRelation(const CardState *sourceCard, const CardRelation *cardRelation, int variableCount) { @@ -1163,7 +1178,7 @@ bool PlayerActions::createRelatedFromRelation(const CardItem *sourceCard, return true; } -void PlayerActions::onRelatedCardCreated(const CardItem *sourceCard, const CardRelation *cardRelation) +void PlayerActions::onRelatedCardCreated(const CardState *sourceCard, const CardRelation *cardRelation) { if (sourceCard == nullptr || cardRelation == nullptr) { return; @@ -1183,7 +1198,7 @@ void PlayerActions::onRelatedCardCreated(const CardItem *sourceCard, const CardR setLastToken(relatedCard.getCardPtr()); } -void PlayerActions::createCard(const CardItem *sourceCard, +void PlayerActions::createCard(const CardState *sourceCard, const QString &dbCardName, CardRelationType attachType, bool persistent, @@ -1195,7 +1210,7 @@ void PlayerActions::createCard(const CardItem *sourceCard, return; } - QPoint gridPoint = QPoint(-1, TableZone::tableRowToGridY(cardInfo->getUiAttributes().tableRow)); + QPoint gridPoint = QPoint(-1, TableZoneLogic::tableRowToGridY(cardInfo->getUiAttributes().tableRow)); // create the token for the related card Command_CreateToken cmd; @@ -1214,7 +1229,7 @@ void PlayerActions::createCard(const CardItem *sourceCard, } cmd.set_pt(cardInfo->getPowTough().toStdString()); - if (SettingsCache::instance().userInterface().getAnnotateTokens()) { + if (getAnnotateTokens()) { cmd.set_annotation(cardInfo->getText().toStdString()); } else { cmd.set_annotation(""); @@ -1269,7 +1284,7 @@ void PlayerActions::actRequestMoveCardXCardsFromTopDialog() emit requestMoveCardXCardsFromTopDialog(defaultNumberTopCardsToPlaceBelow, deckSize); } -void PlayerActions::actMoveCardXCardsFromTop(QList selectedCards, int number) +void PlayerActions::actMoveCardXCardsFromTop(QList selectedCards, int number) { defaultNumberTopCardsToPlaceBelow = number; @@ -1303,14 +1318,14 @@ void PlayerActions::actMoveCardXCardsFromTop(QList selectedCards, in } } -void PlayerActions::actIncPT(QList selectedCards, int deltaP, int deltaT) +void PlayerActions::actIncPT(QList selectedCards, int deltaP, int deltaT) { int playerid = player->getPlayerInfo()->getId(); QList commandList; for (auto card : selectedCards) { QString pt = card->getPT(); - const auto ptList = CardItem::parsePT(pt); + const auto ptList = CardState::parsePT(pt); QString newpt; if (ptList.isEmpty()) { newpt = QString::number(deltaP) + (deltaT ? "/" + QString::number(deltaT) : ""); @@ -1336,7 +1351,7 @@ void PlayerActions::actIncPT(QList selectedCards, int deltaP, int de player->getGame()->getGameEventHandler()->sendGameCommand(prepareGameCommand(commandList), playerid); } -void PlayerActions::actResetPT(QList selectedCards) +void PlayerActions::actResetPT(QList selectedCards) { int playerid = player->getPlayerInfo()->getId(); QList commandList; @@ -1369,7 +1384,7 @@ void PlayerActions::actResetPT(QList selectedCards) } } -void PlayerActions::actRequestSetPTDialog(QList selectedCards) +void PlayerActions::actRequestSetPTDialog(QList selectedCards) { QString oldPT; @@ -1382,11 +1397,11 @@ void PlayerActions::actRequestSetPTDialog(QList selectedCards) emit requestSetPTDialog(oldPT); } -void PlayerActions::actSetPT(QList selectedCards, const QString &pt) +void PlayerActions::actSetPT(QList selectedCards, const QString &pt) { int playerid = player->getPlayerInfo()->getId(); - const auto ptList = CardItem::parsePT(pt); + const auto ptList = CardState::parsePT(pt); bool empty = ptList.isEmpty(); QList commandList; @@ -1394,7 +1409,7 @@ void PlayerActions::actSetPT(QList selectedCards, const QString &pt) auto *cmd = new Command_SetCardAttr; QString newpt = QString(); if (!empty) { - const auto oldpt = CardItem::parsePT(card->getPT()); + const auto oldpt = CardState::parsePT(card->getPT()); int ptIter = 0; for (const auto &_item : ptList) { if (_item.typeId() == QMetaType::Type::Int) { @@ -1422,55 +1437,65 @@ void PlayerActions::actSetPT(QList selectedCards, const QString &pt) player->getGame()->getGameEventHandler()->sendGameCommand(prepareGameCommand(commandList), playerid); } -void PlayerActions::actDrawArrow() -{ - auto *card = player->getGame()->getActiveCard(); - if (card) { - card->drawArrow(Qt::red); - } -} - -void PlayerActions::actIncP(QList selectedCards) +void PlayerActions::actIncP(QList selectedCards) { actIncPT(selectedCards, 1, 0); } -void PlayerActions::actDecP(QList selectedCards) +void PlayerActions::actDecP(QList selectedCards) { actIncPT(selectedCards, -1, 0); } -void PlayerActions::actIncT(QList selectedCards) +void PlayerActions::actIncT(QList selectedCards) { actIncPT(selectedCards, 0, 1); } -void PlayerActions::actDecT(QList selectedCards) +void PlayerActions::actDecT(QList selectedCards) { actIncPT(selectedCards, 0, -1); } -void PlayerActions::actIncPT(QList selectedCards) +void PlayerActions::actIncPT(QList selectedCards) { actIncPT(selectedCards, 1, 1); } -void PlayerActions::actDecPT(QList selectedCards) +void PlayerActions::actDecPT(QList selectedCards) { actIncPT(selectedCards, -1, -1); } -void PlayerActions::actFlowP(QList selectedCards) +void PlayerActions::actFlowP(QList selectedCards) { actIncPT(selectedCards, 1, -1); } -void PlayerActions::actFlowT(QList selectedCards) +void PlayerActions::actFlowT(QList selectedCards) { actIncPT(selectedCards, -1, 1); } -void PlayerActions::actReduceLifeByPower(QList selectedCards) +void PlayerActions::actIncrementCounter(int counterId, int delta) +{ + Command_IncCounter cmd; + cmd.set_counter_id(counterId); + cmd.set_delta(delta); + + sendGameCommand(cmd); +} + +void PlayerActions::actSetCounter(int counterId, int value) +{ + Command_SetCounter cmd; + cmd.set_counter_id(counterId); + cmd.set_value(value); + + sendGameCommand(cmd); +} + +void PlayerActions::actReduceLifeByPower(QList selectedCards) { // find life counter auto lifeCounter = player->getLifeCounter(); @@ -1481,7 +1506,7 @@ void PlayerActions::actReduceLifeByPower(QList selectedCards) // calculate total power; int total = 0; for (auto card : selectedCards) { - QVariantList parsed = CardItem::parsePT(card->getPT()); + QVariantList parsed = CardState::parsePT(card->getPT()); if (!parsed.isEmpty()) { int power = parsed.first().toInt(); // toInt will default to 0 if it's not an int total += qMax(power, 0); @@ -1495,17 +1520,7 @@ void PlayerActions::actReduceLifeByPower(QList selectedCards) sendGameCommand(prepareGameCommand(cmd)); } -void AnnotationDialog::keyPressEvent(QKeyEvent *event) -{ - if (event->key() == Qt::Key_Return && event->modifiers() & Qt::ControlModifier) { - event->accept(); - accept(); - return; - } - QInputDialog::keyPressEvent(event); -} - -void PlayerActions::actRequestSetAnnotationDialog(QList selectedCards) +void PlayerActions::actRequestSetAnnotationDialog(QList selectedCards) { QString oldAnnotation; for (auto card : selectedCards) { @@ -1517,7 +1532,7 @@ void PlayerActions::actRequestSetAnnotationDialog(QList selectedCard emit requestSetAnnotationDialog(oldAnnotation); } -void PlayerActions::actSetAnnotation(QList selectedCards, const QString &annotation) +void PlayerActions::actSetAnnotation(QList selectedCards, const QString &annotation) { QList commandList; for (auto card : selectedCards) { @@ -1531,17 +1546,7 @@ void PlayerActions::actSetAnnotation(QList selectedCards, const QStr sendGameCommand(prepareGameCommand(commandList)); } -void PlayerActions::actAttach() -{ - auto *card = player->getGame()->getActiveCard(); - if (!card) { - return; - } - - card->drawAttachArrow(); -} - -void PlayerActions::actUnattach(QList selectedCards) +void PlayerActions::actUnattach(QList selectedCards) { QList commandList; for (auto card : selectedCards) { @@ -1557,17 +1562,17 @@ void PlayerActions::actUnattach(QList selectedCards) sendGameCommand(prepareGameCommand(commandList)); } -void PlayerActions::actAddCardCounter(QList selectedCards, int counterId) +void PlayerActions::actAddCardCounter(QList selectedCards, int counterId) { offsetCardCounter(selectedCards, counterId, 1); } -void PlayerActions::actRemoveCardCounter(QList selectedCards, int counterId) +void PlayerActions::actRemoveCardCounter(QList selectedCards, int counterId) { offsetCardCounter(selectedCards, counterId, -1); } -void PlayerActions::offsetCardCounter(QList selectedCards, int counterId, int offset) +void PlayerActions::offsetCardCounter(QList selectedCards, int counterId, int offset) { QList commandList; for (auto card : selectedCards) { @@ -1593,7 +1598,7 @@ void PlayerActions::offsetCardCounter(QList selectedCards, int count sendGameCommand(prepareGameCommand(commandList)); } -void PlayerActions::actRequestSetCardCounterDialog(QList selectedCards, int counterId) +void PlayerActions::actRequestSetCardCounterDialog(QList selectedCards, int counterId) { // If a single card is selected, we show the old value in the dialog. Otherwise, we show "x" QString oldValueForDlg = "x"; @@ -1605,7 +1610,7 @@ void PlayerActions::actRequestSetCardCounterDialog(QList selectedCar emit requestSetCardCounterDialog(counterId, oldValueForDlg); } -void PlayerActions::actSetCardCounter(QList selectedCards, int counterId, const QString &counterValue) +void PlayerActions::actSetCardCounter(QList selectedCards, int counterId, const QString &counterValue) { QList commandList; for (auto card : selectedCards) { @@ -1626,11 +1631,13 @@ void PlayerActions::actSetCardCounter(QList selectedCards, int count sendGameCommand(prepareGameCommand(commandList)); } -void PlayerActions::actIncrementAllCardCounters(QList cardsToUpdate) +void PlayerActions::actIncrementAllCardCounters(QList cardsToUpdate) { if (cardsToUpdate.isEmpty()) { // If no cards selected, update all cards on table - cardsToUpdate = static_cast>(player->getTableZone()->getCards()); + for (auto *state : player->getTableZone()->getCards()) { + cardsToUpdate.append(state); + } } QList commandList; @@ -1673,7 +1680,7 @@ static bool isUnwritableRevealZone(CardZoneLogic *zone) return false; } -void PlayerActions::playSelectedCards(QList selectedCards, const bool faceDown) +void PlayerActions::playSelectedCards(QList selectedCards, const bool faceDown) { // CardIds will get shuffled downwards when cards leave the deck. // We need to iterate through the cards in reverse order so cardIds don't get changed out from under us as we play @@ -1688,27 +1695,26 @@ void PlayerActions::playSelectedCards(QList selectedCards, const boo } } -void PlayerActions::actPlay(QList selectedCards) +void PlayerActions::actPlay(QList selectedCards) { playSelectedCards(selectedCards, false); } -void PlayerActions::actPlayFacedown(QList selectedCards) +void PlayerActions::actPlayFacedown(QList selectedCards) { playSelectedCards(selectedCards, true); } -void PlayerActions::actHide(QList selectedCards) +void PlayerActions::actHide(QList selectedCards) { - for (const auto &item : selectedCards) { - auto *card = static_cast(item); - if (card && isUnwritableRevealZone(card->getZone())) { + for (const auto &card : selectedCards) { + if (isUnwritableRevealZone(card->getZone())) { card->getZone()->removeCard(card); } } } -void PlayerActions::actReveal(QList selectedCards, QAction *action) +void PlayerActions::actReveal(QList selectedCards, QAction *action) { const int otherPlayerId = action->data().toInt(); @@ -1799,9 +1805,9 @@ void PlayerActions::actRevealRandomGraveyardCard(int revealToPlayerId) sendGameCommand(cmd); } -void PlayerActions::cardMenuAction(QList selectedCards, CardMenuActionType type) +void PlayerActions::cardMenuAction(QList selectedCards, CardMenuActionType type) { - QList cardList = selectedCards; + QList cardList = selectedCards; QList commandList; if (type <= cmClone) { @@ -1991,7 +1997,7 @@ void PlayerActions::cardMenuAction(QList selectedCards, CardMenuActi ctm->set_tapped(info.getUiAttributes().cipt); } - cmd->set_y(TableZone::tableRowToGridY(tableRow)); + cmd->set_y(TableZoneLogic::tableRowToGridY(tableRow)); commandList.append(cmd); } break; @@ -2040,6 +2046,76 @@ PendingCommand *PlayerActions::prepareGameCommand(const QList &cards) +{ + if (!startZone || !targetZone) { + return; + } + + Command_MoveCard cmd; + cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId()); + cmd.set_start_zone(startZone->getName().toStdString()); + cmd.set_target_player_id(targetZone->getPlayer()->getPlayerInfo()->getId()); + cmd.set_target_zone(targetZone->getName().toStdString()); + cmd.set_x(x); + cmd.set_y(y); + + if (auto *viewLogic = qobject_cast(targetZone)) { + cmd.set_is_reversed(viewLogic->getIsReversed()); + } + + for (const CardMoveRequest &card : cards) { + auto *cardToMove = cmd.mutable_cards_to_move()->add_card(); + cardToMove->set_card_id(card.cardId); + if (card.faceDown) { + cardToMove->set_face_down(true); + } + if (!card.pt.isEmpty()) { + cardToMove->set_pt(card.pt.toStdString()); + } + } + + sendGameCommand(cmd); +} + +PendingCommand *PlayerActions::prepareZoneDump(CardZoneLogic *zone, int numberCards) +{ + if (!zone) { + return nullptr; + } + + Command_DumpZone cmd; + cmd.set_player_id(zone->getPlayer()->getPlayerInfo()->getId()); + cmd.set_zone_name(zone->getName().toStdString()); + cmd.set_number_cards(numberCards); + if (auto *viewLogic = qobject_cast(zone)) { + cmd.set_is_reversed(viewLogic->getIsReversed()); + } + + return prepareGameCommand(cmd); +} + +void PlayerActions::actToggleTapped(const QString &zoneName, const QList &cards, bool tapped) +{ + QList cmdList; + for (CardState *card : cards) { + if (card->getTapped() != tapped) { + auto *cmd = new Command_SetCardAttr; + cmd->set_zone(zoneName.toStdString()); + cmd->set_card_id(card->getId()); + cmd->set_attribute(AttrTapped); + cmd->set_attr_value(tapped ? "1" : "0"); + cmdList.append(cmd); + } + } + + sendGameCommand(prepareGameCommand(cmdList)); +} + void PlayerActions::sendGameCommand(const google::protobuf::Message &command) { if (player->getPlayerInfo()->getJudge() && !player->getPlayerInfo()->getLocal()) { diff --git a/cockatrice/src/game/player/player_actions.h b/libcockatrice_game/libcockatrice/game/player/player_actions.h similarity index 54% rename from cockatrice/src/game/player/player_actions.h rename to libcockatrice_game/libcockatrice/game/player/player_actions.h index 8fff81635..7ed52b9a1 100644 --- a/cockatrice/src/game/player/player_actions.h +++ b/libcockatrice_game/libcockatrice/game/player/player_actions.h @@ -8,12 +8,13 @@ #ifndef COCKATRICE_PLAYER_ACTIONS_H #define COCKATRICE_PLAYER_ACTIONS_H -#include "../../game_graphics/board/card_item.h" // IWYU pragma: keep - moc instantiates the metatype for QList, which Qt 6.4 needs complete -#include "../../game_graphics/dialogs/dlg_create_token.h" -#include "../../game_graphics/dialogs/dlg_move_top_cards_until.h" -#include "../../game_graphics/player/card_menu_action_type.h" #include "../board/card_list.h" +#include "../board/card_state.h" +#include "card_menu_action_type.h" +#include "card_move_request.h" #include "libcockatrice/card/card_info.h" +#include "move_top_cards_until_options.h" +#include "token_info.h" #include #include @@ -22,7 +23,6 @@ #include #include -class CardItem; class CardRelation; class QAction; class QTimer; @@ -36,10 +36,16 @@ class Message; } } // namespace google +class CardState; +class CardZoneLogic; class Command_MoveCard; +class GameEventContext; +class ICardPreferenceProvider; +class IInterfaceSettingsProvider; class PendingCommand; class PlayerLogic; - +class QAction; +struct ArrowData; class PlayerActions : public QObject { Q_OBJECT @@ -50,7 +56,9 @@ public: RANDOM_CARD_FROM_ZONE = -2 }; - explicit PlayerActions(PlayerLogic *player); + explicit PlayerActions(PlayerLogic *player, + IInterfaceSettingsProvider *interfaceSettings = nullptr, + ICardPreferenceProvider *cardPreferences = nullptr); void sendGameCommand(PendingCommand *pend); void sendGameCommand(const google::protobuf::Message &command); @@ -58,7 +66,52 @@ public: PendingCommand *prepareGameCommand(const ::google::protobuf::Message &cmd); PendingCommand *prepareGameCommand(const QList &cmdList); - void moveOneCardUntil(CardItem *card); + /** + * @brief Moves the requested cards between zones on behalf of a view. + * @param startZone Zone the cards are dragged out of. + * @param targetZone Zone the cards are dropped onto. + * @param x Horizontal target index within the zone. + * @param y Vertical target index within the zone. + * @param cards The cards to move, as chosen by the view. + */ + void + moveCards(CardZoneLogic *startZone, CardZoneLogic *targetZone, int x, int y, const QList &cards); + + /** + * @brief Prepares a dump of the given zone, for the caller to connect to and send. + * @param zone Zone to dump. + * @param numberCards How many cards to dump, -1 for all. + */ + PendingCommand *prepareZoneDump(CardZoneLogic *zone, int numberCards); + + /** + * @brief Sets the tapped state of the cards that do not already have it. + * @param zoneName Zone the cards belong to. + * @param cards Cards the view wants toggled. + * @param tapped Desired tapped state. + */ + void actToggleTapped(const QString &zoneName, const QList &cards, bool tapped); + + /** + * @brief Attaches one card to another on the table on behalf of a view. + * @param startCard Card being attached. + * @param targetCard Card being attached to. + */ + void attachCards(CardState *startCard, const CardState *targetCard); + + /** + * @brief Draws an arrow from a card to another card or a player on behalf of a view. + * + * The view resolves the endpoints before playing the card out of hand, so the + * arrow still records the zone the card started in. + * + * @param startCard Card the arrow starts from. + * @param arrow Arrow endpoints and color, as resolved by the view. + * @param deleteInPhase Phase the arrow expires in, 0 to keep it until removed. + */ + void createArrow(CardState *startCard, const ArrowData &arrow, int deleteInPhase); + + void moveOneCardUntil(CardState *card); void stopMoveTopCardsUntil(); [[nodiscard]] bool isMovingCardsUntil() const @@ -87,7 +140,7 @@ signals: void requestDrawBottomCardsDialog(int defaultNumberBottomCards, int maxCards); void requestRollDieDialog(); void requestCreateTokenDialog(const QStringList &predefinedTokens); - void requestCreateRelatedFromRelationDialog(const CardItem *sourceCard, const CardRelation *cardRelation); + void requestCreateRelatedFromRelationDialog(const CardState *sourceCard, const CardRelation *cardRelation); void requestMoveCardXCardsFromTopDialog(int defaultNumberTopCardsToPlaceBelow, int deckSize); void requestSetPTDialog(const QString &oldPT); void requestSetAnnotationDialog(const QString &oldAnnotation); @@ -96,12 +149,18 @@ signals: void requestSortHand(const QList &options); void requestEnableAndSetCreateAnotherTokenAction(const QString &lastTokenName); void requestSetLastToken(CardInfoPtr lastToken); + /** + * @brief The player chose a mulligan size to remember as the starting hand size. + * + * Game logic must not write client settings itself, so persistence is left to the application. + */ + void startingHandSizeChosen(int number); public slots: void setLastToken(CardInfoPtr cardInfo); void setLastTokenInfo(CardInfoPtr cardInfo); - void playCard(CardItem *c, bool faceDown); - void playCardToTable(const CardItem *c, bool faceDown); + void playCard(CardState *c, bool faceDown); + void playCardToTable(const CardState *c, bool faceDown); void actUntapAll(); void actRequestRollDieDialog(); @@ -110,9 +169,9 @@ public slots: void actRequestCreateTokenDialog(const QStringList &predefinedTokens); void actCreateToken(TokenInfo tokenToCreate); void actCreateAnotherToken(); - void actRequestCreateRelatedFromRelationDialog(const CardItem *sourceCard, const CardRelation *cardRelation); - bool createRelatedFromRelation(const CardItem *sourceCard, const CardRelation *cardRelation, int variableCount); - void onRelatedCardCreated(const CardItem *sourceCard, const CardRelation *cardRelation); + void actRequestCreateRelatedFromRelationDialog(const CardState *sourceCard, const CardRelation *cardRelation); + bool createRelatedFromRelation(const CardState *sourceCard, const CardRelation *cardRelation, int variableCount); + void onRelatedCardCreated(const CardState *sourceCard, const CardRelation *cardRelation); void setLastRelatedCreationSucceeded(bool succeeded) { lastRelatedCreationSucceeded = succeeded; @@ -132,9 +191,9 @@ public slots: void actMulliganMinusOne(); void doMulligan(int number); - void actPlay(QList selectedCards); - void actPlayFacedown(QList selectedCards); - void actHide(QList selectedCards); + void actPlay(QList selectedCards); + void actPlayFacedown(QList selectedCards); + void actHide(QList selectedCards); void actMoveTopCardToPlay(); void actMoveTopCardToPlayFaceDown(); @@ -164,10 +223,6 @@ public slots: void actRequestMoveBottomCardsToDialog(const QString &targetZone, const QString &zoneDisplayName, bool faceDown); void moveBottomCardsTo(int number, const QString &targetZone, bool faceDown); - void actSelectAll(); - void actSelectRow(); - void actSelectColumn(); - void actViewLibrary(); void actViewHand(); void actRequestViewTopCardsDialog(); @@ -187,47 +242,55 @@ public slots: void actOpenDeckInDeckEditor(); void actCreatePredefinedToken(); - void actCreateRelatedCard(); - void actCreateAllRelatedCards(); + void actCreateRelatedCard(const CardState *sourceCard, int index); + void actCreateAllRelatedCards(const CardState *sourceCard); void actRequestMoveCardXCardsFromTopDialog(); - void actMoveCardXCardsFromTop(QList selectedCards, int number); - void actRemoveCardCounter(QList selectedCards, int counterId); - void actAddCardCounter(QList selectedCards, int counterId); - void actRequestSetCardCounterDialog(QList selectedCards, int counterId); - void actSetCardCounter(QList selectedCards, int counterId, const QString &counterValue); - void actIncrementAllCardCounters(QList cardsToUpdate); - void actAttach(); - void actUnattach(QList selectedCards); - void actDrawArrow(); - void actIncPT(QList selectedCards, int deltaP, int deltaT); - void actResetPT(QList selectedCards); - void actRequestSetPTDialog(QList selectedCards); - void actSetPT(QList selectedCards, const QString &pt); - void actIncP(QList selectedCards); - void actDecP(QList selectedCards); - void actIncT(QList selectedCards); - void actDecT(QList selectedCards); - void actIncPT(QList selectedCards); - void actDecPT(QList selectedCards); - void actFlowP(QList selectedCards); - void actFlowT(QList selectedCards); + void actMoveCardXCardsFromTop(QList selectedCards, int number); + void actRemoveCardCounter(QList selectedCards, int counterId); + void actAddCardCounter(QList selectedCards, int counterId); + void actRequestSetCardCounterDialog(QList selectedCards, int counterId); + void actSetCardCounter(QList selectedCards, int counterId, const QString &counterValue); + void actIncrementAllCardCounters(QList cardsToUpdate); + void actUnattach(QList selectedCards); + void actIncPT(QList selectedCards, int deltaP, int deltaT); + void actResetPT(QList selectedCards); + void actRequestSetPTDialog(QList selectedCards); + void actSetPT(QList selectedCards, const QString &pt); + void actIncP(QList selectedCards); + void actDecP(QList selectedCards); + void actIncT(QList selectedCards); + void actDecT(QList selectedCards); + void actIncPT(QList selectedCards); + void actDecPT(QList selectedCards); + void actFlowP(QList selectedCards); + void actFlowT(QList selectedCards); - void actReduceLifeByPower(QList selectedCards); + /** @brief Adjusts a player counter by a signed delta. */ + void actIncrementCounter(int counterId, int delta); - void actRequestSetAnnotationDialog(QList selectedCards); - void actSetAnnotation(QList selectedCards, const QString &annotation); - void actReveal(QList selectedCards, QAction *action); + /** @brief Sets a player counter to an absolute value. */ + void actSetCounter(int counterId, int value); + + void actReduceLifeByPower(QList selectedCards); + + void actRequestSetAnnotationDialog(QList selectedCards); + void actSetAnnotation(QList selectedCards, const QString &annotation); + void actReveal(QList selectedCards, QAction *action); void actRevealHand(int revealToPlayerId); void actRevealRandomHandCard(int revealToPlayerId); void actRevealLibrary(int revealToPlayerId); void actSortHand(); - void cardMenuAction(QList selectedCards, CardMenuActionType type); + void cardMenuAction(QList selectedCards, CardMenuActionType type); private: PlayerLogic *player; + // Borrowed settings providers (owned by the application). Null outside the client, in which case the compiled-in + // defaults below apply. + IInterfaceSettingsProvider *interfaceSettings; + ICardPreferenceProvider *cardPreferences; int defaultNumberTopCards = 1; int defaultNumberTopCardsToPlaceBelow = 1; @@ -245,18 +308,23 @@ private: bool lastRelatedCreationSucceeded = false; - void createCard(const CardItem *sourceCard, + [[nodiscard]] bool getPlayToStack() const; + [[nodiscard]] int getStartingHandSize() const; + [[nodiscard]] bool getAnnotateTokens() const; + [[nodiscard]] QString getCardPreferenceOverride(const QString &cardName) const; + + void createCard(const CardState *sourceCard, const QString &dbCardName, CardRelationType attach = CardRelationType::DoesNotAttach, bool persistent = false, bool faceDown = false); - void playSelectedCards(QList selectedCards, bool faceDown = false); + void playSelectedCards(QList selectedCards, bool faceDown = false); void cmdSetTopCard(Command_MoveCard &cmd); void cmdSetBottomCard(Command_MoveCard &cmd); - void offsetCardCounter(QList selectedCards, int counterId, int offset); + void offsetCardCounter(QList selectedCards, int counterId, int offset); }; #endif // COCKATRICE_PLAYER_ACTIONS_H diff --git a/cockatrice/src/game/player/player_event_handler.cpp b/libcockatrice_game/libcockatrice/game/player/player_event_handler.cpp similarity index 88% rename from cockatrice/src/game/player/player_event_handler.cpp rename to libcockatrice_game/libcockatrice/game/player/player_event_handler.cpp index 18f6573df..6588547da 100644 --- a/cockatrice/src/game/player/player_event_handler.cpp +++ b/libcockatrice_game/libcockatrice/game/player/player_event_handler.cpp @@ -1,14 +1,14 @@ #include "player_event_handler.h" -#include "../../game_graphics/board/card_item.h" -#include "../../game_graphics/zones/view_zone.h" #include "../abstract_game.h" #include "../board/arrow_data.h" #include "../board/card_list.h" +#include "../board/card_state.h" #include "../board/counter_state.h" #include "../zones/card_zone_logic.h" #include "../zones/hand_zone_logic.h" #include "../zones/pile_zone_logic.h" +#include "../zones/view_zone_logic.h" #include "libcockatrice/card/card_info.h" #include "libcockatrice/card/printing/exact_card.h" #include "libcockatrice/protocol/pb/card_attributes.pb.h" @@ -27,6 +27,9 @@ #include #include #include +#include +#include +#include #include #include #include @@ -49,6 +52,7 @@ #include #include #include +#include #include #include #include @@ -76,12 +80,12 @@ void PlayerEventHandler::eventShuffle(const Event_Shuffle &event) } // close all views that contain shuffled cards - for (ZoneViewZone *view : zone->getViews()) { - if (view != nullptr) { - int length = view->getLogic()->getCards().length(); + for (auto *viewLogic : zone->getViews()) { + if (viewLogic != nullptr) { + int length = viewLogic->getCards().length(); // we want to close empty views as well if (length == 0 || length > absStart) { // note this assumes views always start at the top of the library - view->close(); + viewLogic->requestClose(); } } else { qWarning() << zone->getName() << "of" << player->getPlayerInfo()->getName() << "holds empty zoneview!"; @@ -155,23 +159,29 @@ void PlayerEventHandler::eventCreateToken(const Event_CreateToken &event) } CardRef cardRef = {QString::fromStdString(event.card_name()), QString::fromStdString(event.card_provider_id())}; - CardItem *card = new CardItem(player, nullptr, cardRef, event.card_id()); // use db PT if not provided in event and not face-down - if (!QString::fromStdString(event.pt()).isEmpty()) { - card->setPT(QString::fromStdString(event.pt())); - } else if (!event.face_down()) { - ExactCard dbCard = card->getCard(); + QString pt = QString::fromStdString(event.pt()); + if (pt.isEmpty() && !event.face_down()) { + ExactCard dbCard = CardDatabaseManager::query()->getCard(cardRef); if (dbCard) { - card->setPT(dbCard.getInfo().getPowTough()); + pt = dbCard.getInfo().getPowTough(); } } - card->setColor(QString::fromStdString(event.color())); - card->setAnnotation(QString::fromStdString(event.annotation())); - card->setDestroyOnZoneChange(event.destroy_on_zone_change()); - card->setFaceDown(event.face_down()); - emit logCreateToken(player, card->getName(), card->getPT(), card->getFaceDown()); - zone->addCard(card, true, event.x(), event.y()); + emit logCreateToken(player, cardRef.name, pt, event.face_down()); + + ServerInfo_Card cardInfo; + cardInfo.set_id(event.card_id()); + cardInfo.set_name(event.card_name()); + cardInfo.set_provider_id(event.card_provider_id()); + cardInfo.set_pt(pt.toStdString()); + cardInfo.set_color(event.color()); + cardInfo.set_annotation(event.annotation()); + cardInfo.set_destroy_on_zone_change(event.destroy_on_zone_change()); + cardInfo.set_face_down(event.face_down()); + cardInfo.set_x(event.x()); + cardInfo.set_y(event.y()); + emit zone->createCardRequested(cardInfo, true); } void PlayerEventHandler::eventSetCardAttr(const Event_SetCardAttr &event, @@ -193,7 +203,7 @@ void PlayerEventHandler::eventSetCardAttr(const Event_SetCardAttr &event, emit logSetTapped(player, nullptr, event.attr_value() == "1"); } } else { - CardItem *card = zone->getCard(event.card_id()); + CardState *card = zone->getCard(event.card_id()); if (!card) { qWarning() << "PlayerEventHandler::eventSetCardAttr: card id=" << event.card_id() << "not found"; return; @@ -203,7 +213,7 @@ void PlayerEventHandler::eventSetCardAttr(const Event_SetCardAttr &event, } void PlayerEventHandler::setCardAttrHelper(const GameEventContext &context, - CardItem *card, + CardState *card, CardAttribute attribute, const QString &avalue, bool allCards, @@ -264,7 +274,7 @@ void PlayerEventHandler::eventSetCardCounter(const Event_SetCardCounter &event) return; } - CardItem *card = zone->getCard(event.card_id()); + CardState *card = zone->getCard(event.card_id()); if (!card) { return; } @@ -341,13 +351,13 @@ void PlayerEventHandler::eventMoveCard(const Event_MoveCard &event, const GameEv if (x == -1) { x = 0; } - CardItem *card = startZone->takeCard(position, event.card_id(), startZone != targetZone); + CardState *card = startZone->takeCard(position, event.card_id(), startZone != targetZone); if (card == nullptr) { return; } - if (startZone != targetZone) { - card->deleteCardInfoPopup(); - } + const bool zoneChanged = startZone != targetZone; + const bool ownerChanged = zoneChanged && startZone->getPlayer() != targetZone->getPlayer(); + emit cardViewRefreshRequested(card, zoneChanged, ownerChanged ? targetZone->getPlayer() : nullptr); if (event.has_card_name()) { QString name = QString::fromStdString(event.card_name()); QString providerId = @@ -356,27 +366,19 @@ void PlayerEventHandler::eventMoveCard(const Event_MoveCard &event, const GameEv } if (card->getAttachedTo() && (startZone != targetZone)) { - CardItem *parentCard = card->getAttachedTo(); + CardState *parentCard = card->getAttachedTo(); + // CardState::setAttachedTo removes the card from the old parent's attachedCards list and emits the zone events. card->setAttachedTo(nullptr); parentCard->getZone()->reorganizeCards(); } - card->deleteDragItem(); - card->setId(event.new_card_id()); card->setFaceDown(event.face_down()); if (startZone != targetZone) { - card->setBeingPointedAt(false); - card->setHovered(false); - - const QList &attachedCards = card->getAttachedCards(); + const QList &attachedCards = card->getAttachedCards(); for (auto attachedCard : attachedCards) { emit targetZone->cardAdded(attachedCard); } - - if (startZone->getPlayer() != targetZone->getPlayer()) { - card->setOwner(targetZone->getPlayer()); - } } // The log event has to be sent before the card is added to the target zone @@ -404,7 +406,7 @@ void PlayerEventHandler::eventFlipCard(const Event_FlipCard &event) if (!zone) { return; } - CardItem *card = zone->getCard(event.card_id()); + CardState *card = zone->getCard(event.card_id()); if (!card) { return; } @@ -427,20 +429,28 @@ void PlayerEventHandler::eventDestroyCard(const Event_DestroyCard &event) return; } - CardItem *card = zone->getCard(event.card_id()); + CardState *card = zone->getCard(event.card_id()); if (!card) { return; } - QList attachedCards = card->getAttachedCards(); + QList attachedCards = card->getAttachedCards(); // This list is always empty except for buggy server implementations. for (auto &attachedCard : attachedCards) { + card->removeAttachedCard(attachedCard); + attachedCard->setGridPoint(QPoint(-1, attachedCard->getGridPoint().y())); attachedCard->setAttachedTo(nullptr); + if (attachedCard->getZone() == nullptr) { + attachedCard->deleteView(); + } else { + emit attachedCard->getZone()->cardAdded(attachedCard); + attachedCard->getZone()->reorganizeCards(); + } } emit logDestroyCard(player, card->getName()); zone->takeCard(-1, event.card_id(), true); - card->deleteLater(); + card->deleteView(); } void PlayerEventHandler::eventAttachCard(const Event_AttachCard &event) @@ -448,7 +458,7 @@ void PlayerEventHandler::eventAttachCard(const Event_AttachCard &event) const QMap &playerList = player->getGame()->getPlayerManager()->getPlayers(); PlayerLogic *targetPlayer = nullptr; CardZoneLogic *targetZone = nullptr; - CardItem *targetCard = nullptr; + CardState *targetCard = nullptr; if (event.has_target_player_id()) { targetPlayer = playerList.value(event.target_player_id(), 0); if (targetPlayer) { @@ -464,12 +474,12 @@ void PlayerEventHandler::eventAttachCard(const Event_AttachCard &event) return; } - CardItem *startCard = startZone->getCard(event.card_id()); + CardState *startCard = startZone->getCard(event.card_id()); if (!startCard) { return; } - CardItem *oldParent = startCard->getAttachedTo(); + CardState *oldParent = startCard->getAttachedTo(); startCard->setAttachedTo(targetCard); @@ -498,7 +508,7 @@ void PlayerEventHandler::eventDrawCards(const Event_DrawCards &event) if (listSize) { for (int i = 0; i < listSize; ++i) { const ServerInfo_Card &cardInfo = event.cards(i); - CardItem *card = _deck->takeCard(0, cardInfo.id()); + CardState *card = _deck->takeCard(0, cardInfo.id()); QString cardName = QString::fromStdString(cardInfo.name()); QString providerId = QString::fromStdString(cardInfo.provider_id()); card->setCardRef({cardName, providerId}); @@ -546,7 +556,7 @@ void PlayerEventHandler::eventRevealCards(const Event_RevealCards &event, EventP for (const auto &card : cardList) { QString cardName = QString::fromStdString(card->name()); QString providerId = QString::fromStdString(card->provider_id()); - CardItem *cardItem = zone->getCard(card->id()); + CardState *cardItem = zone->getCard(card->id()); if (!cardItem) { continue; } diff --git a/cockatrice/src/game/player/player_event_handler.h b/libcockatrice_game/libcockatrice/game/player/player_event_handler.h similarity index 89% rename from cockatrice/src/game/player/player_event_handler.h rename to libcockatrice_game/libcockatrice/game/player/player_event_handler.h index 88254119b..d253deef0 100644 --- a/cockatrice/src/game/player/player_event_handler.h +++ b/libcockatrice_game/libcockatrice/game/player/player_event_handler.h @@ -27,7 +27,7 @@ #include #include -class CardItem; +class CardState; class CardZoneLogic; class PlayerLogic; class Event_AttachCard; @@ -61,7 +61,7 @@ template class QList; * Design notes: * - All event handlers assume events are authoritative and already validated * by the server. - * - Most handlers mutate both logical state (CardItem, CardZoneLogic, counters) + * - Most handlers mutate both logical state (CardState, CardZoneLogic, counters) * and visual/UI state (views, arrows, menus). * - Logging signals are emitted *after* or *during* state mutation, depending * on whether later mutations would invalidate log data. @@ -238,7 +238,7 @@ signals: void logUndoDraw(PlayerLogic *player, QString cardName); void logUndoDrawFailed(PlayerLogic *player); void logMoveCard(PlayerLogic *player, - CardItem *card, + CardState *card, CardZoneLogic *startZone, int oldX, CardZoneLogic *targetZone, @@ -248,11 +248,11 @@ signals: void logAttachCard(PlayerLogic *player, QString cardName, PlayerLogic *targetPlayer, QString targetCardName); void logUnattachCard(PlayerLogic *player, QString cardName); void logSetCardCounter(PlayerLogic *player, QString cardName, int counterId, int value, int oldValue); - void logSetTapped(PlayerLogic *player, CardItem *card, bool tapped); + void logSetTapped(PlayerLogic *player, CardState *card, bool tapped); void logSetCounter(PlayerLogic *player, QString counterName, int value, int oldValue); - void logSetDoesntUntap(PlayerLogic *player, CardItem *card, bool doesntUntap); - void logSetPT(PlayerLogic *player, CardItem *card, QString newPT); - void logSetAnnotation(PlayerLogic *player, CardItem *card, QString newAnnotation); + void logSetDoesntUntap(PlayerLogic *player, CardState *card, bool doesntUntap); + void logSetPT(PlayerLogic *player, CardState *card, QString newPT); + void logSetAnnotation(PlayerLogic *player, CardState *card, QString newAnnotation); void logDumpZone(PlayerLogic *player, CardZoneLogic *zone, int numberCards, bool isReversed = false); void logRevealCards(PlayerLogic *player, CardZoneLogic *zone, @@ -266,15 +266,23 @@ signals: void logAlwaysLookAtTopCard(PlayerLogic *player, CardZoneLogic *zone, bool reveal); /** @} */ - void cardZoneChanged(CardItem *card, bool sameZone); - void requestCardMenuUpdate(const CardItem *card); + void cardZoneChanged(CardState *card, bool sameZone); + void requestCardMenuUpdate(CardState *card); + /** + * @brief A card was just moved by the server and its view needs refreshing. + * + * @param card The card that moved. + * @param zoneChanged Whether the card crossed a zone boundary. + * @param newOwner The player the card now belongs to, or nullptr to leave the owner alone. + */ + void cardViewRefreshRequested(CardState *card, bool zoneChanged, PlayerLogic *newOwner); private: /** Owning player instance. */ PlayerLogic *player; void setCardAttrHelper(const GameEventContext &context, - CardItem *card, + CardState *card, CardAttribute attribute, const QString &avalue, bool allCards, diff --git a/cockatrice/src/game/player/player_info.cpp b/libcockatrice_game/libcockatrice/game/player/player_info.cpp similarity index 100% rename from cockatrice/src/game/player/player_info.cpp rename to libcockatrice_game/libcockatrice/game/player/player_info.cpp diff --git a/cockatrice/src/game/player/player_info.h b/libcockatrice_game/libcockatrice/game/player/player_info.h similarity index 100% rename from cockatrice/src/game/player/player_info.h rename to libcockatrice_game/libcockatrice/game/player/player_info.h diff --git a/cockatrice/src/game/player/player_logic.cpp b/libcockatrice_game/libcockatrice/game/player/player_logic.cpp similarity index 89% rename from cockatrice/src/game/player/player_logic.cpp rename to libcockatrice_game/libcockatrice/game/player/player_logic.cpp index b545b7088..96bd07164 100644 --- a/cockatrice/src/game/player/player_logic.cpp +++ b/libcockatrice_game/libcockatrice/game/player/player_logic.cpp @@ -1,8 +1,9 @@ #include "player_logic.h" -#include "../../game_graphics/board/card_item.h" #include "../abstract_game.h" #include "../board/arrow_data.h" +#include "../board/card_list.h" +#include "../board/card_state.h" #include "../board/counter_state.h" #include "../game_meta_info.h" #include "../zones/hand_zone_logic.h" @@ -33,10 +34,17 @@ #include #include -PlayerLogic::PlayerLogic(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent) +PlayerLogic::PlayerLogic(const ServerInfo_User &info, + int _id, + bool _local, + bool _judge, + AbstractGame *_parent, + IInterfaceSettingsProvider *interfaceSettings, + ICardPreferenceProvider *cardPreferences) : QObject(_parent), game(_parent), playerInfo(new PlayerInfo(info, _id, _local, _judge)), - playerEventHandler(new PlayerEventHandler(this)), playerActions(new PlayerActions(this)), active(false), - conceded(false), zoneId(0), dialogSemaphore(false) + playerEventHandler(new PlayerEventHandler(this)), + playerActions(new PlayerActions(this, interfaceSettings, cardPreferences)), active(false), conceded(false), + zoneId(0), dialogSemaphore(false) { initializeZones(); } @@ -172,22 +180,20 @@ void PlayerLogic::processPlayerInfo(const ServerInfo_Player &info) const int cardListSize = zoneInfo.card_list_size(); if (!cardListSize) { for (int j = 0; j < zoneInfo.card_count(); ++j) { - zone->addCard(new CardItem(this), false, -1); + emit zone->createCardRequested(ServerInfo_Card(), false); } } else { for (int j = 0; j < cardListSize; ++j) { - const ServerInfo_Card &cardInfo = zoneInfo.card_list(j); - auto *card = new CardItem(this); - card->processCardInfo(cardInfo); + ServerInfo_Card cardInfo = zoneInfo.card_list(j); // Zones without coordinates (hand, piles, stack) preserve the order // they arrive in on the server in the positions of their cards list. // The x coordinate of such cards is always 0, so inserting at it // would reverse the list on reconnect. Append instead. - if (zoneInfo.with_coords()) { - zone->addCard(card, false, cardInfo.x(), cardInfo.y()); - } else { - zone->addCard(card, false, -1); + if (!zoneInfo.with_coords()) { + cardInfo.set_x(-1); + cardInfo.set_y(-1); } + emit zone->createCardRequested(cardInfo, false); } } if (zoneInfo.has_always_reveal_top_card()) { @@ -219,8 +225,8 @@ void PlayerLogic::processCardAttachment(const ServerInfo_Player &info) for (int j = 0; j < cardListSize; ++j) { const ServerInfo_Card &cardInfo = zoneInfo.card_list(j); if (cardInfo.has_attach_player_id()) { - CardItem *startCard = zone->getCard(cardInfo.id()); - CardItem *targetCard = + CardState *startCard = zone->getCard(cardInfo.id()); + CardState *targetCard = game->getCard(cardInfo.attach_player_id(), QString::fromStdString(cardInfo.attach_zone()), cardInfo.attach_card_id()); if (!targetCard) { @@ -239,19 +245,19 @@ void PlayerLogic::processCardAttachment(const ServerInfo_Player &info) } } -void PlayerLogic::addCard(CardItem *card) +void PlayerLogic::addCard(AbstractCardItem *card) { emit newCardAdded(card); } -void PlayerLogic::deleteCard(CardItem *card) +void PlayerLogic::deleteCard(CardState *card) { if (card == nullptr) { return; } else if (dialogSemaphore) { cardsToDelete.append(card); } else { - card->deleteLater(); + card->deleteView(); } } @@ -332,7 +338,7 @@ bool PlayerLogic::clearCardsToDelete() for (auto &i : cardsToDelete) { if (i != nullptr) { - i->deleteLater(); + i->deleteView(); } } cardsToDelete.clear(); diff --git a/cockatrice/src/game/player/player_logic.h b/libcockatrice_game/libcockatrice/game/player/player_logic.h similarity index 88% rename from cockatrice/src/game/player/player_logic.h rename to libcockatrice_game/libcockatrice/game/player/player_logic.h index 59da7cc32..d6c4f5187 100644 --- a/cockatrice/src/game/player/player_logic.h +++ b/libcockatrice_game/libcockatrice/game/player/player_logic.h @@ -7,6 +7,8 @@ #ifndef PLAYER_H #define PLAYER_H +#include "../board/arrow_data.h" +#include "../board/counter_state.h" #include "../zones/card_zone_logic.h" #include "../zones/hand_zone_logic.h" #include "../zones/pile_zone_logic.h" @@ -17,12 +19,16 @@ #include "libcockatrice/utility/playmat_params.h" #include -#include #include #include #include #include #include +#include +#include +#include +#include +#include #include #include @@ -31,7 +37,6 @@ // signatures, which the moc-generated code needs to be complete. #include -class CardItem; class CounterState; class QWidget; class ServerInfo_PlayerProperties; @@ -42,6 +47,8 @@ template class QSharedPointer; inline Q_LOGGING_CATEGORY(PlayerLog, "player"); class AbstractCardItem; class AbstractGame; +class ICardPreferenceProvider; +class IInterfaceSettingsProvider; class PlayerInfo; class PlayerEventHandler; class PlayerActions; @@ -67,7 +74,7 @@ signals: /** @brief Emitted when the remote playmat (card/params) is updated from player properties. */ void playmatChanged(); void newCardAdded(AbstractCardItem *card); - void requestCardMenuUpdate(const CardItem *card); + void requestCardMenuUpdate(CardState *card); void counterAdded(CounterState *state); void counterRemoved(int counterId); void rearrangeCounters(); @@ -87,7 +94,13 @@ public slots: void onRequestZoneViewToggle(const QString &zoneName, int numberCards, bool isReversed); public: - PlayerLogic(const ServerInfo_User &info, int _id, bool _local, bool _judge, AbstractGame *_parent); + PlayerLogic(const ServerInfo_User &info, + int _id, + bool _local, + bool _judge, + AbstractGame *_parent, + IInterfaceSettingsProvider *interfaceSettings = nullptr, + ICardPreferenceProvider *cardPreferences = nullptr); ~PlayerLogic() override; void initializeZones(); @@ -97,8 +110,8 @@ public: void processPlayerInfo(const ServerInfo_Player &info); void processCardAttachment(const ServerInfo_Player &info); - void addCard(CardItem *c); - void deleteCard(CardItem *c); + void addCard(AbstractCardItem *c); + void deleteCard(CardState *c); bool clearCardsToDelete(); @@ -250,7 +263,7 @@ private: QMap counters; bool dialogSemaphore; - QList cardsToDelete; + QList cardsToDelete; // Playmat from player properties (for opponent display) CardRef remotePlaymatCard; @@ -258,15 +271,4 @@ private: bool hasRemotePlaymat = false; }; -class AnnotationDialog : public QInputDialog -{ - Q_OBJECT - void keyPressEvent(QKeyEvent *e) override; - -public: - explicit AnnotationDialog(QWidget *parent = nullptr) : QInputDialog(parent) - { - } -}; - #endif diff --git a/cockatrice/src/game/player/player_manager.cpp b/libcockatrice_game/libcockatrice/game/player/player_manager.cpp similarity index 86% rename from cockatrice/src/game/player/player_manager.cpp rename to libcockatrice_game/libcockatrice/game/player/player_manager.cpp index 4730805ad..284d21f68 100644 --- a/cockatrice/src/game/player/player_manager.cpp +++ b/libcockatrice_game/libcockatrice/game/player/player_manager.cpp @@ -12,9 +12,12 @@ PlayerManager::PlayerManager(AbstractGame *_game, int _localPlayerId, bool _localPlayerIsJudge, - bool localPlayerIsSpectator) + bool localPlayerIsSpectator, + IInterfaceSettingsProvider *_interfaceSettings, + ICardPreferenceProvider *_cardPreferences) : QObject(_game), game(_game), players(QMap()), localPlayerId(_localPlayerId), - localPlayerIsJudge(_localPlayerIsJudge), localPlayerIsSpectator(localPlayerIsSpectator) + localPlayerIsJudge(_localPlayerIsJudge), localPlayerIsSpectator(localPlayerIsSpectator), + interfaceSettings(_interfaceSettings), cardPreferences(_cardPreferences) { } @@ -52,7 +55,7 @@ bool PlayerManager::isLocalPlayer(int playerId) PlayerLogic *PlayerManager::addPlayer(int playerId, const ServerInfo_User &info) { auto *newPlayer = new PlayerLogic(info, playerId, isLocalPlayer(playerId) || game->getGameState()->getIsLocalGame(), - isJudge(), getGame()); + isJudge(), getGame(), interfaceSettings, cardPreferences); connect(newPlayer, &PlayerLogic::concededChanged, this, &PlayerManager::onPlayerConceded); players.insert(playerId, newPlayer); emit playerAdded(newPlayer); diff --git a/cockatrice/src/game/player/player_manager.h b/libcockatrice_game/libcockatrice/game/player/player_manager.h similarity index 84% rename from cockatrice/src/game/player/player_manager.h rename to libcockatrice_game/libcockatrice/game/player/player_manager.h index 7a9f7645b..c95bbbb96 100644 --- a/cockatrice/src/game/player/player_manager.h +++ b/libcockatrice_game/libcockatrice/game/player/player_manager.h @@ -16,6 +16,8 @@ #include class AbstractGame; +class ICardPreferenceProvider; +class IInterfaceSettingsProvider; class PlayerLogic; class PlayerManager : public QObject @@ -23,7 +25,12 @@ class PlayerManager : public QObject Q_OBJECT public: - PlayerManager(AbstractGame *_game, int _localPlayerId, bool _localPlayerIsJudge, bool localPlayerIsSpectator); + PlayerManager(AbstractGame *_game, + int _localPlayerId, + bool _localPlayerIsJudge, + bool localPlayerIsSpectator, + IInterfaceSettingsProvider *interfaceSettings = nullptr, + ICardPreferenceProvider *cardPreferences = nullptr); AbstractGame *game; QMap players; @@ -31,6 +38,9 @@ public: bool localPlayerIsJudge; bool localPlayerIsSpectator; QMap spectators; + // Borrowed settings providers forwarded to each PlayerLogic (owned by the application). + IInterfaceSettingsProvider *interfaceSettings; + ICardPreferenceProvider *cardPreferences; [[nodiscard]] bool isSpectator() const { diff --git a/libcockatrice_game/libcockatrice/game/player/token_info.h b/libcockatrice_game/libcockatrice/game/player/token_info.h new file mode 100644 index 000000000..99869f33e --- /dev/null +++ b/libcockatrice_game/libcockatrice/game/player/token_info.h @@ -0,0 +1,23 @@ +/** + * @file token_info.h + * @ingroup GameLogicPlayers + */ +//! \todo Document this file. + +#ifndef COCKATRICE_TOKEN_INFO_H +#define COCKATRICE_TOKEN_INFO_H + +#include + +struct TokenInfo +{ + QString name; + QString color; + QString pt; + QString annotation; + bool destroy = true; + bool faceDown = false; + QString providerId; +}; + +#endif // COCKATRICE_TOKEN_INFO_H diff --git a/cockatrice/src/game/replay.cpp b/libcockatrice_game/libcockatrice/game/replay.cpp similarity index 51% rename from cockatrice/src/game/replay.cpp rename to libcockatrice_game/libcockatrice/game/replay.cpp index 4ab4a6321..4d41a12ea 100644 --- a/cockatrice/src/game/replay.cpp +++ b/libcockatrice_game/libcockatrice/game/replay.cpp @@ -9,10 +9,15 @@ class GameReplay; class QObject; -Replay::Replay(QObject *_parent, const GameReplay *_replay, bool isLocalGame) : AbstractGame(_parent) +Replay::Replay(QObject *_parent, + const GameReplay *_replay, + bool isLocalGame, + IInterfaceSettingsProvider *interfaceSettings, + ICardPreferenceProvider *cardPreferences) + : AbstractGame(_parent) { gameState = new GameState(this, 0, -1, isLocalGame, {}, false, false, -1, false); connect(gameMetaInfo, &GameMetaInfo::startedChanged, gameState, &GameState::onStartedChanged); - playerManager = new PlayerManager(this, -1, false, true); + playerManager = new PlayerManager(this, -1, false, true, interfaceSettings, cardPreferences); loadReplay(_replay); } diff --git a/libcockatrice_game/libcockatrice/game/replay.h b/libcockatrice_game/libcockatrice/game/replay.h new file mode 100644 index 000000000..f8f82a70d --- /dev/null +++ b/libcockatrice_game/libcockatrice/game/replay.h @@ -0,0 +1,32 @@ +/** + * @file replay.h + * @ingroup GameLogic + * @ingroup Replay + */ +//! \todo Document this file. + +#ifndef COCKATRICE_REPLAY_H +#define COCKATRICE_REPLAY_H + +#include "abstract_game.h" + +#include + +class GameReplay; +class QObject; +class ICardPreferenceProvider; +class IInterfaceSettingsProvider; + +class Replay : public AbstractGame +{ + Q_OBJECT + +public: + explicit Replay(QObject *_parent, + const GameReplay *_replay, + bool isLocalGame, + IInterfaceSettingsProvider *interfaceSettings = nullptr, + ICardPreferenceProvider *cardPreferences = nullptr); +}; + +#endif // COCKATRICE_REPLAY_H diff --git a/cockatrice/src/client/translation.h b/libcockatrice_game/libcockatrice/game/translation.h similarity index 100% rename from cockatrice/src/client/translation.h rename to libcockatrice_game/libcockatrice/game/translation.h diff --git a/cockatrice/src/game/zones/card_zone_algorithms.h b/libcockatrice_game/libcockatrice/game/zones/card_zone_algorithms.h similarity index 81% rename from cockatrice/src/game/zones/card_zone_algorithms.h rename to libcockatrice_game/libcockatrice/game/zones/card_zone_algorithms.h index 118a7d29d..35e96b86c 100644 --- a/cockatrice/src/game/zones/card_zone_algorithms.h +++ b/libcockatrice_game/libcockatrice/game/zones/card_zone_algorithms.h @@ -5,9 +5,9 @@ namespace CardZoneAlgorithms { /** - * Shared insertion logic for zones where cards become visible on add and follow - * the standard pattern: clamp index, insert, clear identity if contents unknown, - * reset state, show card. + * Shared insertion logic for zones where cards follow the standard pattern: + * clamp index, insert, clear identity if contents unknown, reset state. + * Visibility and parenting are handled by the graphics zone's onCardAdded(). * * Zones with different post-add behavior (signal connections, positional resets, * hidden cards, or coordinate-based placement) should NOT use this — implement @@ -18,8 +18,7 @@ namespace CardZoneAlgorithms * * @tparam CardList Must provide: size() -> int, insert(int, CardType*), * getContentsKnown() -> bool - * @tparam CardType Must provide: setId(int), setCardRef(CardRefType), - * resetState(bool), setVisible(bool) + * @tparam CardType Must provide: setId(int), setCardRef(CardRefType), resetState(bool) * @param keepAnnotations Forwarded to card->resetState(). Stack-like zones preserve * annotations across zone transitions; hand-like zones clear them. */ @@ -37,7 +36,6 @@ void addCardToList(CardList &cards, CardType *card, int x, bool keepAnnotations) } card->resetState(keepAnnotations); - card->setVisible(true); } } // namespace CardZoneAlgorithms diff --git a/cockatrice/src/game/zones/card_zone_logic.cpp b/libcockatrice_game/libcockatrice/game/zones/card_zone_logic.cpp similarity index 79% rename from cockatrice/src/game/zones/card_zone_logic.cpp rename to libcockatrice_game/libcockatrice/game/zones/card_zone_logic.cpp index d997ab9ad..a160dcc1d 100644 --- a/cockatrice/src/game/zones/card_zone_logic.cpp +++ b/libcockatrice_game/libcockatrice/game/zones/card_zone_logic.cpp @@ -1,13 +1,11 @@ #include "card_zone_logic.h" -#include "../../client/translation.h" -#include "../../game_graphics/board/card_item.h" -#include "../../game_graphics/zones/card_zone.h" -#include "../../game_graphics/zones/view_zone.h" #include "../board/card_list.h" +#include "../board/card_state.h" #include "../player/player_actions.h" #include "../player/player_info.h" #include "../player/player_logic.h" +#include "../translation.h" #include "libcockatrice/card/database/card_database.h" #include "view_zone_logic.h" @@ -44,19 +42,24 @@ CardZoneLogic::CardZoneLogic(PlayerLogic *_player, &CardZoneLogic::refreshCardInfos); } -void CardZoneLogic::addCard(CardItem *card, const bool reorganize, const int x, const int y) +void CardZoneLogic::addCard(CardState *card, const bool reorganize, const int x, const int y) { if (!card) { - qCWarning(CardZoneLog) << "CardZoneLogic::addCard() card is null; this shouldn't normally happen"; + qCWarning(CardZoneLogicLog) << "CardZoneLogic::addCard() card is null; this shouldn't normally happen"; return; } - for (auto *view : views) { - if (qobject_cast(view->getLogic())->prepareAddCard(x)) { - auto copy = new CardItem(player, nullptr, card->getCardRef(), card->getId()); - copy->setFaceDown(card->getFaceDown()); - - view->getLogic()->addCard(copy, reorganize, x, y); + for (auto *viewLogic : views) { + if (viewLogic->prepareAddCard(x)) { + ServerInfo_Card cardInfo; + const CardRef &cardRef = card->getCardRef(); + cardInfo.set_name(cardRef.name.toStdString()); + cardInfo.set_provider_id(cardRef.providerId.toStdString()); + cardInfo.set_id(card->getId()); + cardInfo.set_face_down(card->getFaceDown()); + cardInfo.set_x(x); + cardInfo.set_y(y); + emit viewLogic->createCardRequested(cardInfo, reorganize); } } @@ -71,7 +74,7 @@ void CardZoneLogic::addCard(CardItem *card, const bool reorganize, const int x, emit cardCountChanged(); } -CardItem *CardZoneLogic::takeCard(int position, int cardId, bool toNewZone) +CardState *CardZoneLogic::takeCard(int position, int cardId, bool toNewZone) { if (position == -1) { // position == -1 means either that the zone is indexed by card id @@ -90,11 +93,11 @@ CardItem *CardZoneLogic::takeCard(int position, int cardId, bool toNewZone) return nullptr; } - for (auto *view : views) { - qobject_cast(view->getLogic())->removeCard(position, toNewZone); + for (auto *viewLogic : views) { + viewLogic->removeCard(position, toNewZone); } - CardItem *c = cards.takeAt(position); + CardState *c = cards.takeAt(position); c->setId(cardId); @@ -103,11 +106,11 @@ CardItem *CardZoneLogic::takeCard(int position, int cardId, bool toNewZone) return c; } -CardItem *CardZoneLogic::getCard(int cardId) +CardState *CardZoneLogic::getCard(int cardId) { - CardItem *c = cards.findCard(cardId); + CardState *c = cards.findCard(cardId); if (!c) { - qCWarning(CardZoneLog) << "CardZoneLogic::getCard: card id=" << cardId << "not found"; + qCWarning(CardZoneLogicLog) << "CardZoneLogic::getCard: card id=" << cardId << "not found"; return nullptr; } // If the card's id is -1, this zone is invisible, @@ -119,10 +122,10 @@ CardItem *CardZoneLogic::getCard(int cardId) return c; } -void CardZoneLogic::removeCard(CardItem *card) +void CardZoneLogic::removeCard(CardState *card) { if (!card) { - qCWarning(CardZoneLog) << "CardZoneLogic::removeCard: card is null, this shouldn't normally happen"; + qCWarning(CardZoneLogicLog) << "CardZoneLogic::removeCard: card is null, this shouldn't normally happen"; return; } @@ -135,8 +138,8 @@ void CardZoneLogic::removeCard(CardItem *card) void CardZoneLogic::refreshCardInfos() { - for (const auto &cardItem : cards) { - cardItem->refreshCardInfo(); + for (const auto &cardState : cards) { + cardState->refreshCardInfo(); } } @@ -168,17 +171,17 @@ void CardZoneLogic::clearContents() const CardList toClear = cards; // Detach and notify attached cards and zones *before* deleting anything. - for (CardItem *card : toClear) { + for (CardState *card : toClear) { // If an incorrectly implemented server doesn't return attached cards to whom they belong before dropping a // player, we have to return them to avoid a crash. - const QList &attachedCards = card->getAttachedCards(); - for (CardItem *attachedCard : attachedCards) { + const QList &attachedCards = card->getAttachedCards(); + for (CardState *attachedCard : attachedCards) { emit attachedCard->getZone()->cardAdded(attachedCard); } } // Now request deletions after all manipulations are done. - for (CardItem *card : toClear) { + for (CardState *card : toClear) { player->deleteCard(card); } diff --git a/cockatrice/src/game/zones/card_zone_logic.h b/libcockatrice_game/libcockatrice/game/zones/card_zone_logic.h similarity index 73% rename from cockatrice/src/game/zones/card_zone_logic.h rename to libcockatrice_game/libcockatrice/game/zones/card_zone_logic.h index 2e232b258..d3cd9121f 100644 --- a/cockatrice/src/game/zones/card_zone_logic.h +++ b/libcockatrice_game/libcockatrice/game/zones/card_zone_logic.h @@ -7,28 +7,30 @@ #ifndef COCKATRICE_CARD_ZONE_LOGIC_H #define COCKATRICE_CARD_ZONE_LOGIC_H -#include "../../client/translation.h" #include "../board/card_list.h" +#include "../translation.h" #include #include #include #include +#include #include -class CardItem; - inline Q_LOGGING_CATEGORY(CardZoneLogicLog, "card_zone_logic"); class PlayerLogic; -class ZoneViewZone; +class ZoneViewZoneLogic; +class CardDragItem; class CardZoneLogic : public QObject { Q_OBJECT signals: - void cardAdded(CardItem *addedCard); + void cardAdded(CardState *addedCard); + /** @brief Asks the graphics side to build the card item for @p cardInfo and add it to this zone. */ + void createCardRequested(const ServerInfo_Card &cardInfo, bool reorganize); void cardCountChanged(); void reorganizeCards(); void updateGraphics(); @@ -43,16 +45,17 @@ public: bool _contentsKnown, QObject *parent = nullptr); - void addCard(CardItem *card, bool reorganize, int x, int y = -1); + void addCard(CardState *card, bool reorganize, int x, int y = -1); // getCard() finds a card by id. - CardItem *getCard(int cardId); - void removeCard(CardItem *card); + CardState *getCard(int cardId); + void removeCard(CardState *card); // takeCard() finds a card by position and removes it from the zone and from all of its views. - virtual CardItem *takeCard(int position, int cardId, bool canResize = true); + virtual CardState *takeCard(int position, int cardId, bool canResize = true); - void rawInsertCard(CardItem *card, int index) + void rawInsertCard(CardState *card, int index) { cards.insert(index, card); + emit cardCountChanged(); } [[nodiscard]] const CardList &getCards() const @@ -63,6 +66,7 @@ public: void sortCards(const QList &options) { cards.sortBy(options); + emit cardCountChanged(); } [[nodiscard]] QString getName() const { @@ -77,7 +81,7 @@ public: { return cards.getContentsKnown(); } - QList &getViews() + QList &getViews() { return views; } @@ -109,12 +113,12 @@ protected: PlayerLogic *player; QString name; CardList cards; - QList views; + QList views; bool hasCardAttr; bool isShufflable; bool alwaysRevealTopCard; - virtual void addCardImpl(CardItem *card, int x, int y) = 0; + virtual void addCardImpl(CardState *card, int x, int y) = 0; }; #endif // COCKATRICE_CARD_ZONE_LOGIC_H diff --git a/cockatrice/src/game/zones/hand_zone_logic.cpp b/libcockatrice_game/libcockatrice/game/zones/hand_zone_logic.cpp similarity index 82% rename from cockatrice/src/game/zones/hand_zone_logic.cpp rename to libcockatrice_game/libcockatrice/game/zones/hand_zone_logic.cpp index cea17d031..1d3f54929 100644 --- a/cockatrice/src/game/zones/hand_zone_logic.cpp +++ b/libcockatrice_game/libcockatrice/game/zones/hand_zone_logic.cpp @@ -1,6 +1,6 @@ #include "hand_zone_logic.h" -#include "../../game_graphics/board/card_item.h" +#include "../board/card_state.h" #include "card_zone_algorithms.h" class PlayerLogic; @@ -16,7 +16,7 @@ HandZoneLogic::HandZoneLogic(PlayerLogic *_player, { } -void HandZoneLogic::addCardImpl(CardItem *card, int x, int /*y*/) +void HandZoneLogic::addCardImpl(CardState *card, int x, int /*y*/) { CardZoneAlgorithms::addCardToList(cards, card, x, false); } diff --git a/cockatrice/src/game/zones/hand_zone_logic.h b/libcockatrice_game/libcockatrice/game/zones/hand_zone_logic.h similarity index 91% rename from cockatrice/src/game/zones/hand_zone_logic.h rename to libcockatrice_game/libcockatrice/game/zones/hand_zone_logic.h index c7d63bc0d..f8756eafd 100644 --- a/cockatrice/src/game/zones/hand_zone_logic.h +++ b/libcockatrice_game/libcockatrice/game/zones/hand_zone_logic.h @@ -26,7 +26,7 @@ public: QObject *parent = nullptr); protected: - void addCardImpl(CardItem *card, int x, int y) override; + void addCardImpl(CardState *card, int x, int y) override; }; #endif // COCKATRICE_HAND_ZONE_LOGIC_H diff --git a/cockatrice/src/game/zones/pile_zone_logic.cpp b/libcockatrice_game/libcockatrice/game/zones/pile_zone_logic.cpp similarity index 70% rename from cockatrice/src/game/zones/pile_zone_logic.cpp rename to libcockatrice_game/libcockatrice/game/zones/pile_zone_logic.cpp index 40af25cc5..e33d8fe79 100644 --- a/cockatrice/src/game/zones/pile_zone_logic.cpp +++ b/libcockatrice_game/libcockatrice/game/zones/pile_zone_logic.cpp @@ -1,10 +1,6 @@ #include "pile_zone_logic.h" -#include "../../game_graphics/board/card_item.h" -#include "../board/card_list.h" - -class PlayerLogic; -class QObject; +#include "../board/card_state.h" PileZoneLogic::PileZoneLogic(PlayerLogic *_player, const QString &_name, @@ -16,15 +12,14 @@ PileZoneLogic::PileZoneLogic(PlayerLogic *_player, { } -void PileZoneLogic::addCardImpl(CardItem *card, int x, int /*y*/) +void PileZoneLogic::addCardImpl(CardState *card, int x, int /*y*/) { - connect(card, &CardItem::sigPixmapUpdated, this, &PileZoneLogic::callUpdate); + connect(card, &CardState::cardPixmapUpdated, this, &PileZoneLogic::callUpdate); // if x is negative set it to add at end if (x < 0 || x >= cards.size()) { x = cards.size(); } cards.insert(x, card); - card->setPos(0, 0); if (!contentsKnown()) { card->setCardRef({}); card->setId(-1); @@ -33,6 +28,5 @@ void PileZoneLogic::addCardImpl(CardItem *card, int x, int /*y*/) cards.at(x + 1)->setCardRef({}); } } - card->setVisible(false); - card->resetState(); + card->resetState(false); } \ No newline at end of file diff --git a/cockatrice/src/game/zones/pile_zone_logic.h b/libcockatrice_game/libcockatrice/game/zones/pile_zone_logic.h similarity index 91% rename from cockatrice/src/game/zones/pile_zone_logic.h rename to libcockatrice_game/libcockatrice/game/zones/pile_zone_logic.h index ed6841b75..662b0377f 100644 --- a/cockatrice/src/game/zones/pile_zone_logic.h +++ b/libcockatrice_game/libcockatrice/game/zones/pile_zone_logic.h @@ -31,7 +31,7 @@ public: QObject *parent = nullptr); protected: - void addCardImpl(CardItem *card, int x, int y) override; + void addCardImpl(CardState *card, int x, int y) override; }; #endif // COCKATRICE_PILE_ZONE_LOGIC_H diff --git a/cockatrice/src/game/zones/stack_zone_logic.cpp b/libcockatrice_game/libcockatrice/game/zones/stack_zone_logic.cpp similarity index 83% rename from cockatrice/src/game/zones/stack_zone_logic.cpp rename to libcockatrice_game/libcockatrice/game/zones/stack_zone_logic.cpp index 454274971..ccf0d32f3 100644 --- a/cockatrice/src/game/zones/stack_zone_logic.cpp +++ b/libcockatrice_game/libcockatrice/game/zones/stack_zone_logic.cpp @@ -1,6 +1,6 @@ #include "stack_zone_logic.h" -#include "../../game_graphics/board/card_item.h" +#include "../board/card_state.h" #include "card_zone_algorithms.h" class PlayerLogic; @@ -16,7 +16,7 @@ StackZoneLogic::StackZoneLogic(PlayerLogic *_player, { } -void StackZoneLogic::addCardImpl(CardItem *card, int x, int /*y*/) +void StackZoneLogic::addCardImpl(CardState *card, int x, int /*y*/) { CardZoneAlgorithms::addCardToList(cards, card, x, true); } diff --git a/cockatrice/src/game/zones/stack_zone_logic.h b/libcockatrice_game/libcockatrice/game/zones/stack_zone_logic.h similarity index 91% rename from cockatrice/src/game/zones/stack_zone_logic.h rename to libcockatrice_game/libcockatrice/game/zones/stack_zone_logic.h index 657d44ecf..ac6c4617c 100644 --- a/cockatrice/src/game/zones/stack_zone_logic.h +++ b/libcockatrice_game/libcockatrice/game/zones/stack_zone_logic.h @@ -26,7 +26,7 @@ public: QObject *parent = nullptr); protected: - void addCardImpl(CardItem *card, int x, int y) override; + void addCardImpl(CardState *card, int x, int y) override; }; #endif // COCKATRICE_STACK_ZONE_LOGIC_H diff --git a/cockatrice/src/game/zones/table_zone_logic.cpp b/libcockatrice_game/libcockatrice/game/zones/table_zone_logic.cpp similarity index 60% rename from cockatrice/src/game/zones/table_zone_logic.cpp rename to libcockatrice_game/libcockatrice/game/zones/table_zone_logic.cpp index 07e4a759b..834794268 100644 --- a/cockatrice/src/game/zones/table_zone_logic.cpp +++ b/libcockatrice_game/libcockatrice/game/zones/table_zone_logic.cpp @@ -1,14 +1,9 @@ #include "table_zone_logic.h" -#include "../../game_graphics/board/card_item.h" -#include "../board/card_list.h" -#include "libcockatrice/card/card_info.h" +#include "../board/card_state.h" #include -class PlayerLogic; -class QObject; - TableZoneLogic::TableZoneLogic(PlayerLogic *_player, const QString &_name, bool _hasCardAttr, @@ -19,7 +14,7 @@ TableZoneLogic::TableZoneLogic(PlayerLogic *_player, { } -void TableZoneLogic::addCardImpl(CardItem *card, int _x, int _y) +void TableZoneLogic::addCardImpl(CardState *card, int _x, int _y) { cards.append(card); if (!card->getFaceDown() && card->getPT().isEmpty()) { @@ -29,15 +24,33 @@ void TableZoneLogic::addCardImpl(CardItem *card, int _x, int _y) card->setDoesntUntap(true); } card->setGridPoint(QPoint(_x, _y)); - card->setVisible(true); } -CardItem *TableZoneLogic::takeCard(int position, int cardId, bool toNewZone) +CardState *TableZoneLogic::takeCard(int position, int cardId, bool toNewZone) { - CardItem *result = CardZoneLogic::takeCard(position, cardId); + CardState *result = CardZoneLogic::takeCard(position, cardId); if (toNewZone) { emit contentSizeChanged(); } return result; -} \ No newline at end of file +} + +int TableZoneLogic::clampValidTableRow(const int row) +{ + if (row < 0) { + return 0; + } + if (row >= ROW_COUNT) { + return ROW_COUNT - 1; + } + return row; +} + +int TableZoneLogic::tableRowToGridY(int tableRow) +{ + if (tableRow > 2) { + tableRow = 1; + } + return clampValidTableRow(2 - tableRow); +} diff --git a/cockatrice/src/game/zones/table_zone_logic.h b/libcockatrice_game/libcockatrice/game/zones/table_zone_logic.h similarity index 57% rename from cockatrice/src/game/zones/table_zone_logic.h rename to libcockatrice_game/libcockatrice/game/zones/table_zone_logic.h index 8078bfd05..c828d8b91 100644 --- a/cockatrice/src/game/zones/table_zone_logic.h +++ b/libcockatrice_game/libcockatrice/game/zones/table_zone_logic.h @@ -29,8 +29,23 @@ public: bool _contentsKnown, QObject *parent = nullptr); + /** @brief Number of rows in the table zone grid (0=creatures, 1=noncreatures, 2=lands). */ + static const int ROW_COUNT = 3; + + /** + * Clamps a grid row index into [0, ROW_COUNT - 1]. + */ + static int clampValidTableRow(const int row); + + /** + * Converts a card's logical table row (0=creatures, 1=noncreatures, 2=lands) + * to the corresponding grid Y coordinate. Cards with tableRow > 2 (e.g., + * instants/sorceries) default to the noncreatures row. + */ + static int tableRowToGridY(int tableRow); + protected: - void addCardImpl(CardItem *card, int x, int y) override; + void addCardImpl(CardState *card, int x, int y) override; /** * @brief Removes a card from view. @@ -38,9 +53,9 @@ protected: * @param position card position * @param cardId id of card to take * @param toNewZone Whether the destination of the card is not the same as the starting zone. Defaults to true - * @return CardItem that has been removed + * @return CardState that has been removed */ - CardItem *takeCard(int position, int cardId, bool toNewZone = true) override; + CardState *takeCard(int position, int cardId, bool toNewZone = true) override; }; #endif // COCKATRICE_TABLE_ZONE_LOGIC_H diff --git a/cockatrice/src/game/zones/view_zone_logic.cpp b/libcockatrice_game/libcockatrice/game/zones/view_zone_logic.cpp similarity index 81% rename from cockatrice/src/game/zones/view_zone_logic.cpp rename to libcockatrice_game/libcockatrice/game/zones/view_zone_logic.cpp index eb3b7715f..db1fa32c3 100644 --- a/cockatrice/src/game/zones/view_zone_logic.cpp +++ b/libcockatrice_game/libcockatrice/game/zones/view_zone_logic.cpp @@ -1,10 +1,11 @@ #include "view_zone_logic.h" -#include "../../client/settings/cache_settings.h" -#include "../../game_graphics/board/card_item.h" -#include "../board/card_list.h" +#include "../board/card_state.h" +#include "../player/player_actions.h" +#include "../player/player_logic.h" #include +#include #include class PlayerLogic; @@ -22,13 +23,19 @@ ZoneViewZoneLogic::ZoneViewZoneLogic(PlayerLogic *_player, bool _revealZone, bool _writeableRevealZone, bool _isReversed, + IInterfaceSettingsProvider *_interfaceSettings, QObject *parent) : CardZoneLogic(_player, _origZone->getName(), false, false, true, parent), origZone(_origZone), numberCards(_numberCards), revealZone(_revealZone), writeableRevealZone(_writeableRevealZone), - isReversed(_isReversed) + isReversed(_isReversed), interfaceSettings(_interfaceSettings) { } +bool ZoneViewZoneLogic::getCloseEmptyCardView() const +{ + return interfaceSettings ? interfaceSettings->getCloseEmptyCardView() : true; +} + /** * Checks if inserting a card at the given position requires an actual new card to be created and added to the view. * Also does any cardId updates that would be required if a card is inserted in that position. @@ -36,7 +43,7 @@ ZoneViewZoneLogic::ZoneViewZoneLogic(PlayerLogic *_player, * Note that this method can end up modifying the cardIds despite returning false. * (for example, if the card is inserted into a hidden portion of the deck while the view is reversed) * - * Make sure to call this method once before calling addCard(), so that you skip creating a new CardItem and calling + * Make sure to call this method once before calling addCard(), so that you skip creating the card and calling * addCard() if it's not required. * * @param x The position to insert the card at. @@ -64,7 +71,7 @@ bool ZoneViewZoneLogic::prepareAddCard(int x) // autoclose check is done both here and in removeCard - if (cards.isEmpty() && !doInsert && SettingsCache::instance().userInterface().getCloseEmptyCardView()) { + if (cards.isEmpty() && !doInsert && getCloseEmptyCardView()) { emit closeView(); } @@ -75,7 +82,7 @@ bool ZoneViewZoneLogic::prepareAddCard(int x) * Make sure prepareAddCard() was called before calling addCard(). * This method assumes we already checked that the card is being inserted into the visible portion */ -void ZoneViewZoneLogic::addCardImpl(CardItem *card, int x, int /*y*/) +void ZoneViewZoneLogic::addCardImpl(CardState *card, int x, int /*y*/) { if (!isReversed) { // if x is negative set it to add at end @@ -144,14 +151,14 @@ void ZoneViewZoneLogic::removeCard(int position, bool toNewZone) return; } - CardItem *card = cards.takeAt(position); - card->deleteLater(); + CardState *card = cards.takeAt(position); + card->deleteView(); // The toNewZone check is to prevent the view from auto-closing if the view contains only a single card and that // card gets dragged within the view. // Another autoclose check is done in prepareAddCard so that the view autocloses if the last card was moved to an // unrevealed portion of the same zone. - if (cards.isEmpty() && SettingsCache::instance().userInterface().getCloseEmptyCardView() && toNewZone) { + if (cards.isEmpty() && getCloseEmptyCardView() && toNewZone) { emit closeView(); return; } @@ -160,12 +167,18 @@ void ZoneViewZoneLogic::removeCard(int position, bool toNewZone) reorganizeCards(); } +void ZoneViewZoneLogic::requestClose() +{ + emit closeView(); +} + void ZoneViewZoneLogic::clearCards() { for (auto card : cards) { - card->deleteLater(); + card->deleteView(); } cards.clear(); + emit cardCountChanged(); } void ZoneViewZoneLogic::setWriteableRevealZone(bool _writeableRevealZone) @@ -177,4 +190,16 @@ void ZoneViewZoneLogic::setWriteableRevealZone(bool _writeableRevealZone) emit removeFromViews(); } writeableRevealZone = _writeableRevealZone; -} \ No newline at end of file +} + +PendingCommand *ZoneViewZoneLogic::prepareZoneDump() +{ + return getPlayer()->getPlayerActions()->prepareZoneDump(this, numberCards); +} + +void ZoneViewZoneLogic::sendZoneDump(PendingCommand *pendingCommand) +{ + if (pendingCommand != nullptr) { + getPlayer()->getPlayerActions()->sendGameCommand(pendingCommand); + } +} diff --git a/cockatrice/src/game/zones/view_zone_logic.h b/libcockatrice_game/libcockatrice/game/zones/view_zone_logic.h similarity index 55% rename from cockatrice/src/game/zones/view_zone_logic.h rename to libcockatrice_game/libcockatrice/game/zones/view_zone_logic.h index 6fae301e4..a29e543a3 100644 --- a/cockatrice/src/game/zones/view_zone_logic.h +++ b/libcockatrice_game/libcockatrice/game/zones/view_zone_logic.h @@ -12,6 +12,8 @@ class PlayerLogic; class QObject; +class PendingCommand; +class IInterfaceSettingsProvider; class ZoneViewZoneLogic : public CardZoneLogic { @@ -26,6 +28,8 @@ private: int numberCards; bool revealZone, writeableRevealZone; bool isReversed; + // Borrowed settings provider (owned by the application). Null outside the client, in which case views stay open. + IInterfaceSettingsProvider *interfaceSettings; public: enum CardAction @@ -41,15 +45,25 @@ public: bool _revealZone, bool _writeableRevealZone, bool _isReversed, + IInterfaceSettingsProvider *interfaceSettings = nullptr, QObject *parent = nullptr); bool prepareAddCard(int x); void removeCard(int position, bool toNewZone); void updateCardIds(CardAction action); + /** + * @brief Asks the graphics view showing this zone to close itself. + * + * Logic code has no handle on the view, so it goes through the logic object the view was built from. + */ + void requestClose(); + /** @brief Removes all cards from the view, without touching anything else. Used in replay rewind */ void clearCards(); + [[nodiscard]] bool getCloseEmptyCardView() const; + int getNumberCards() const { return numberCards; @@ -73,8 +87,25 @@ public: return origZone; } + /** + * @brief Builds the zone-dump command for this view without sending it. + * @return The pending command, or nullptr when there is no zone to dump. + * + * Keeps command construction in game/ so graphics views don't reach + * through the player into PlayerActions to set it up. The response can + * arrive synchronously (local games), so callers must connect to + * PendingCommand::finished before calling sendZoneDump(). + */ + PendingCommand *prepareZoneDump(); + + /** + * @brief Sends a command returned by prepareZoneDump(). + * @param pendingCommand The prepared command; nullptr is ignored. + */ + void sendZoneDump(PendingCommand *pendingCommand); + protected: - void addCardImpl(CardItem *card, int x, int y) override; + void addCardImpl(CardState *card, int x, int y) override; }; #endif // COCKATRICE_VIEW_ZONE_LOGIC_H diff --git a/libcockatrice_interfaces/CMakeLists.txt b/libcockatrice_interfaces/CMakeLists.txt index 3b17569b1..69ed13b0e 100644 --- a/libcockatrice_interfaces/CMakeLists.txt +++ b/libcockatrice_interfaces/CMakeLists.txt @@ -1,7 +1,3 @@ -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - set(HEADERS libcockatrice/interfaces/interface_cache_storage_settings_provider.h libcockatrice/interfaces/interface_card_database_path_provider.h @@ -24,9 +20,7 @@ set(HEADERS libcockatrice/interfaces/noop_card_set_priority_controller.h ) -qt6_wrap_cpp(MOC_SOURCES ${HEADERS}) - -add_library(libcockatrice_interfaces STATIC ${MOC_SOURCES}) +add_library(libcockatrice_interfaces STATIC ${HEADERS}) target_include_directories(libcockatrice_interfaces PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/interface_chat_settings_provider.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_chat_settings_provider.h index 04dad9243..03f98f7bd 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_chat_settings_provider.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_chat_settings_provider.h @@ -12,6 +12,8 @@ public: [[nodiscard]] virtual bool getChatMentionCompleter() const = 0; [[nodiscard]] virtual QString getChatMentionColor() const = 0; [[nodiscard]] virtual QString getChatHighlightColor() const = 0; + [[nodiscard]] virtual QString getChannelUnreadColor() const = 0; + [[nodiscard]] virtual QString getChannelMentionColor() const = 0; [[nodiscard]] virtual bool getChatMentionForeground() const = 0; [[nodiscard]] virtual bool getChatHighlightForeground() const = 0; [[nodiscard]] virtual bool getIgnoreUnregisteredUsers() const = 0; diff --git a/libcockatrice_models/CMakeLists.txt b/libcockatrice_models/CMakeLists.txt index 7af35b2d5..5186eff13 100644 --- a/libcockatrice_models/CMakeLists.txt +++ b/libcockatrice_models/CMakeLists.txt @@ -1,7 +1,3 @@ -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - add_subdirectory(libcockatrice/models/database) add_subdirectory(libcockatrice/models/deck_list) diff --git a/libcockatrice_models/libcockatrice/models/database/CMakeLists.txt b/libcockatrice_models/libcockatrice/models/database/CMakeLists.txt index a73e36dbb..22a04cd32 100644 --- a/libcockatrice_models/libcockatrice/models/database/CMakeLists.txt +++ b/libcockatrice_models/libcockatrice/models/database/CMakeLists.txt @@ -1,10 +1,7 @@ -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - set(HEADERS card_database_model.h card_database_display_model.h + card_database_visual_display_model.h card/card_completer_proxy_model.h card/card_search_model.h card_set/card_sets_model.h @@ -12,13 +9,12 @@ set(HEADERS token/token_edit_model.h ) -qt6_wrap_cpp(MOC_SOURCES ${HEADERS}) - add_library( libcockatrice_models_database STATIC - ${MOC_SOURCES} + ${HEADERS} card_database_model.cpp card_database_display_model.cpp + card_database_visual_display_model.cpp card/card_completer_proxy_model.cpp card/card_search_model.cpp card_set/card_sets_model.cpp diff --git a/libcockatrice_models/libcockatrice/models/database/card/card_search_model.cpp b/libcockatrice_models/libcockatrice/models/database/card/card_search_model.cpp index 5bde6e3fc..8a267ed7f 100644 --- a/libcockatrice_models/libcockatrice/models/database/card/card_search_model.cpp +++ b/libcockatrice_models/libcockatrice/models/database/card/card_search_model.cpp @@ -9,6 +9,7 @@ #include #include #include +#include CardSearchModel::CardSearchModel(CardDatabaseDisplayModel *sourceModel, QObject *parent) : QAbstractListModel(parent), sourceModel(sourceModel) @@ -69,31 +70,28 @@ void CardSearchModel::updateSearchResults(const QString &query) continue; } - // The completer suggestions match against the same languages the card - // search uses, so typing a localized name finds the card. In Both mode - // either language can match. - for (const QString &matchName : searchableNames(card)) { - const QString lowerName = matchName.toLower(); - if (!lowerName.contains(lowerQuery)) { - continue; - } + const std::optional lowerName = matchingLowercaseName(card, lowerQuery); + if (!lowerName) { + continue; + } - const int distance = levenshteinDistance(lowerQuery, lowerName); + const int distance = levenshteinDistance(lowerQuery, *lowerName); + // Length of the matched name, used to break distance ties. Stored here + // so the comparator does not look the name up again for every comparison. + const int nameLength = static_cast(lowerName->size()); - if (lowerName.startsWith(lowerQuery)) { - prefixMatches.append({card, distance}); - } else { - containsMatches.append({card, distance}); - } - break; + if (lowerName->startsWith(lowerQuery)) { + prefixMatches.append({card, distance, nameLength}); + } else { + containsMatches.append({card, distance, nameLength}); } } - auto sortByDistanceThenLength = [this](const SearchResult &a, const SearchResult &b) { + auto sortByDistanceThenLength = [](const SearchResult &a, const SearchResult &b) { if (a.distance != b.distance) { return a.distance < b.distance; } - return sortableName(a.card).size() < sortableName(b.card).size(); + return a.nameLength < b.nameLength; }; std::sort(prefixMatches.begin(), prefixMatches.end(), sortByDistanceThenLength); @@ -112,24 +110,28 @@ void CardSearchModel::updateSearchResults(const QString &query) endResetModel(); } -QStringList CardSearchModel::searchableNames(const CardInfoPtr &card) const +std::optional CardSearchModel::matchingLowercaseName(const CardInfoPtr &card, const QString &lowerQuery) const { + if (!searchLanguage.isEnglishOnly() && searchLanguage.mode == SearchLanguageMode::Selected) { + QString localizedName = card->getLocalizedName(searchLanguage.language).toLower(); + if (localizedName.contains(lowerQuery)) { + return std::optional(std::move(localizedName)); + } + return std::nullopt; + } + + QString englishName = card->getName().toLower(); + if (englishName.contains(lowerQuery)) { + return std::optional(std::move(englishName)); + } + if (searchLanguage.isEnglishOnly()) { - return {card->getName()}; + return std::nullopt; } - const QString localizedName = card->getLocalizedName(searchLanguage.language); - if (searchLanguage.mode == SearchLanguageMode::Selected) { - return {localizedName}; + QString localizedName = card->getLocalizedName(searchLanguage.language).toLower(); + if (localizedName.contains(lowerQuery)) { + return std::optional(std::move(localizedName)); } - - return {card->getName(), localizedName}; -} - -QString CardSearchModel::sortableName(const CardInfoPtr &card) const -{ - if (searchLanguage.isEnglishOnly()) { - return card->getName(); - } - return card->getLocalizedName(searchLanguage.language); + return std::nullopt; } diff --git a/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h b/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h index 08d0e8636..ec7c1195e 100644 --- a/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card/card_search_model.h @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -51,13 +52,20 @@ private: { CardInfoPtr card; int distance; + int nameLength; }; - /** @brief The names a card is searched by with the current search language. */ - [[nodiscard]] QStringList searchableNames(const CardInfoPtr &card) const; - - /** @brief The name used to break distance ties when sorting suggestions. */ - [[nodiscard]] QString sortableName(const CardInfoPtr &card) const; + /** + * @brief Finds the lowercased card name that matches the query. + * + * The completer suggestions match against the same languages the card + * search uses, so typing a localized name finds the card. In Both mode + * either language can match. + * + * @return The matched lowercased name, or std::nullopt when the card does not match. + */ + [[nodiscard]] std::optional matchingLowercaseName(const CardInfoPtr &card, + const QString &lowerQuery) const; CardDatabaseDisplayModel *sourceModel; QList searchResults; diff --git a/libcockatrice_models/libcockatrice/models/database/card_database_display_model.cpp b/libcockatrice_models/libcockatrice/models/database/card_database_display_model.cpp index 39bb8314c..9ec0afd9c 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_database_display_model.cpp +++ b/libcockatrice_models/libcockatrice/models/database/card_database_display_model.cpp @@ -1,5 +1,6 @@ #include "card_database_display_model.h" +#include "../natural_sort_collator.h" #include "card_database_model.h" #include "libcockatrice/card/card_localization.h" #include "libcockatrice/filters/filter_string.h" @@ -20,7 +21,8 @@ class QObject; CardDatabaseDisplayModel::CardDatabaseDisplayModel(QObject *parent) - : QSortFilterProxyModel(parent), isToken(ShowAll), filterString(nullptr) + : QSortFilterProxyModel(parent), isToken(ShowAll), filterString(nullptr), cardModel(nullptr), + collator(NaturalSort::makeNaturalSortCollator()) { filterTree = nullptr; setFilterCaseSensitivity(Qt::CaseInsensitive); @@ -31,22 +33,37 @@ CardDatabaseDisplayModel::CardDatabaseDisplayModel(QObject *parent) invalidate(); emit modelDirty(); }); - - loadedRowCount = 0; } void CardDatabaseDisplayModel::setSourceModel(QAbstractItemModel *model) { + cardModel = qobject_cast(model); QSortFilterProxyModel::setSourceModel(model); connect(model, &QAbstractItemModel::rowsInserted, this, [this]() { dirty(); }); connect(model, &QAbstractItemModel::rowsRemoved, this, [this]() { dirty(); }); - connect(model, &QAbstractItemModel::modelReset, this, [this]() { - loadedRowCount = 0; - dirty(); - }); + connect(model, &QAbstractItemModel::modelReset, this, [this]() { dirty(); }); +} + +QVariant CardDatabaseDisplayModel::data(const QModelIndex &index, int role) const +{ + if (role == CardInfoRole) { + return QVariant::fromValue(cardInfoAt(index)); + } + + return QSortFilterProxyModel::data(index, role); +} + +CardInfoPtr CardDatabaseDisplayModel::cardInfoAt(const QModelIndex &index) const +{ + if (!index.isValid() || cardModel == nullptr) { + return {}; + } + + const QModelIndex sourceIndex = mapToSource(index); + return sourceIndex.isValid() ? cardModel->getCard(sourceIndex.row()) : CardInfoPtr(); } QMap CardDatabaseDisplayModel::characterTranslation = {{L'“', L'\"'}, @@ -54,41 +71,14 @@ QMap CardDatabaseDisplayModel::characterTranslation = {{L'“' {L'‘', L'\''}, {L'’', L'\''}}; -bool CardDatabaseDisplayModel::canFetchMore(const QModelIndex &index) const -{ - return loadedRowCount < sourceModel()->rowCount(index); -} - -void CardDatabaseDisplayModel::fetchMore(const QModelIndex &index) -{ - int remainder = sourceModel()->rowCount(index) - loadedRowCount; - int itemsToFetch = qMin(100, remainder); - - if (itemsToFetch == 0) { - return; - } - - const auto startIndex = qMin(rowCount(QModelIndex()), loadedRowCount); - beginInsertRows(QModelIndex(), startIndex, startIndex + itemsToFetch - 1); - - loadedRowCount += itemsToFetch; - endInsertRows(); -} - -int CardDatabaseDisplayModel::rowCount(const QModelIndex &parent) const -{ - return QSortFilterProxyModel::rowCount(parent); -} - bool CardDatabaseDisplayModel::lessThan(const QModelIndex &left, const QModelIndex &right) const { - - QString leftString = sourceModel()->data(left, CardDatabaseModel::SortRole).toString(); - QString rightString = sourceModel()->data(right, CardDatabaseModel::SortRole).toString(); + const QString leftString = sourceModel()->data(left, CardDatabaseModel::SortRole).toString(); + const QString rightString = sourceModel()->data(right, CardDatabaseModel::SortRole).toString(); if (!cardName.isEmpty() && left.column() == CardDatabaseModel::NameColumn) { - bool isLeftType = leftString.startsWith(cardName, Qt::CaseInsensitive); - bool isRightType = rightString.startsWith(cardName, Qt::CaseInsensitive); + const bool isLeftType = leftString.startsWith(cardName, Qt::CaseInsensitive); + const bool isRightType = rightString.startsWith(cardName, Qt::CaseInsensitive); // test for an exact match: isLeftType && leftString.size() == cardName.size() // or an exclusive start match: isLeftType && !isRightType @@ -101,31 +91,37 @@ bool CardDatabaseDisplayModel::lessThan(const QModelIndex &left, const QModelInd return false; } } else if (right.column() == CardDatabaseModel::PTColumn && left.column() == CardDatabaseModel::PTColumn) { - QStringList leftList = leftString.split("/"); - QStringList rightList = rightString.split("/"); + // The comparator runs O(n log n) times while the view is sorted, so + // splitting every power/toughness string into a QStringList allocates + // a lot for data that has exactly one separator. Work on views and + // only split the two halves out when both strings are well formed. + const QStringView leftPT = leftString; + const QStringView rightPT = rightString; + const qsizetype leftSlash = leftPT.indexOf(u'/'); + const qsizetype rightSlash = rightPT.indexOf(u'/'); + const bool leftIsWellFormed = leftSlash >= 0 && leftPT.indexOf(u'/', leftSlash + 1) < 0; + const bool rightIsWellFormed = rightSlash >= 0 && rightPT.indexOf(u'/', rightSlash + 1) < 0; - if (leftList.size() == 2 && rightList.size() == 2) { - - // cool, have both P/T in list now - int lessThanNum = lessThanNumerically(leftList.at(0), rightList.at(0)); - if (lessThanNum != 0) { - return lessThanNum < 0; - } else { - // power equal, check toughness - return lessThanNumerically(leftList.at(1), rightList.at(1)) < 0; + if (leftIsWellFormed && rightIsWellFormed) { + const int powerComparison = lessThanNumerically(leftPT.left(leftSlash), rightPT.left(rightSlash)); + if (powerComparison != 0) { + return powerComparison < 0; } + // power equal, check toughness + return lessThanNumerically(leftPT.mid(leftSlash + 1), rightPT.mid(rightSlash + 1)) < 0; } } - return QString::localeAwareCompare(leftString, rightString) < 0; + return collator.compare(leftString, rightString) < 0; } -int CardDatabaseDisplayModel::lessThanNumerically(const QString &left, const QString &right) +int CardDatabaseDisplayModel::lessThanNumerically(QStringView left, QStringView right) const { if (left == right) { return 0; } - bool okLeft, okRight; + bool okLeft = false; + bool okRight = false; float leftNum = left.toFloat(&okLeft); float rightNum = right.toFloat(&okRight); @@ -139,11 +135,11 @@ int CardDatabaseDisplayModel::lessThanNumerically(const QString &left, const QSt } } // try and parsing again, for weird ones like "1+*" - QString leftAfterNum = ""; - QString rightAfterNum = ""; + QStringView leftAfterNum; + QStringView rightAfterNum; if (!okLeft) { int leftNumIndex = 0; - for (; leftNumIndex < left.length(); leftNumIndex++) { + for (; leftNumIndex < left.size(); leftNumIndex++) { if (!left.at(leftNumIndex).isDigit()) { break; } @@ -155,7 +151,7 @@ int CardDatabaseDisplayModel::lessThanNumerically(const QString &left, const QSt } if (!okRight) { int rightNumIndex = 0; - for (; rightNumIndex < right.length(); rightNumIndex++) { + for (; rightNumIndex < right.size(); rightNumIndex++) { if (!right.at(rightNumIndex).isDigit()) { break; } @@ -177,7 +173,7 @@ int CardDatabaseDisplayModel::lessThanNumerically(const QString &left, const QSt } else { // both parsed, same number, but at least one has something else // so compare the part after the number - prefer nothing - return QString::localeAwareCompare(leftAfterNum, rightAfterNum); + return collator.compare(leftAfterNum, rightAfterNum); } } else if (okLeft) { return -1; @@ -185,7 +181,7 @@ int CardDatabaseDisplayModel::lessThanNumerically(const QString &left, const QSt return 1; } // couldn't parse it, just return String comparison - return QString::localeAwareCompare(left, right); + return collator.compare(left, right); } bool CardDatabaseDisplayModel::filterAcceptsRow(int sourceRow, const QModelIndex & /*sourceParent*/) const { diff --git a/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h b/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h index d12dd1fb4..3a9bbc6ab 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card_database_display_model.h @@ -10,15 +10,18 @@ #include "libcockatrice/card/card_info.h" +#include #include #include #include #include +#include #include #include #include #include +class CardDatabaseModel; class FilterTree; class QObject; template class QMap; @@ -34,13 +37,25 @@ public: ShowAll }; + enum Role + { + /** + * @brief The CardInfoPtr behind a proxy row. + * + * Saves views from looking the card up again through its name, and keeps + * them from depending on the source model's column layout. + */ + CardInfoRole = Qt::UserRole + 1 + }; + private: FilterBool isToken; QString cardName, cardText; QSet cardNameSet, cardTypes, cardColors; FilterTree *filterTree; FilterString *filterString; - int loadedRowCount; + /// The source model, kept typed so CardInfoRole does not cast per row. + CardDatabaseModel *cardModel; QTimer dirtyTimer; CardSearchLanguage searchLanguage; QString searchText; @@ -48,6 +63,9 @@ private: /** The translation table that will be used for sanitizeCardName. */ static QMap characterTranslation; + /** Natural, locale-aware comparison shared by every sort in this model. */ + QCollator collator; + public: explicit CardDatabaseDisplayModel(QObject *parent = nullptr); void setSourceModel(QAbstractItemModel *model) override; @@ -80,15 +98,22 @@ public: dirtyTimer.start(20); } void clearFilterAll(); - [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; - [[nodiscard]] bool canFetchMore(const QModelIndex &parent) const override; - void fetchMore(const QModelIndex &parent) override; + [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + /** + * @brief Returns the card behind a proxy row, without the QVariant wrapper of CardInfoRole. + * + * The visual grid calls this for every row on each rebuild; CardInfoRole stays the + * generic way in for consumers that only see the model through data(). + */ + [[nodiscard]] CardInfoPtr cardInfoAt(const QModelIndex &index) const; + signals: void modelDirty(); protected: [[nodiscard]] bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; - static int lessThanNumerically(const QString &left, const QString &right); + int lessThanNumerically(QStringView left, QStringView right) const; [[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; [[nodiscard]] bool rowMatchesCardName(CardInfoPtr info) const; diff --git a/libcockatrice_models/libcockatrice/models/database/card_database_model.cpp b/libcockatrice_models/libcockatrice/models/database/card_database_model.cpp index 1ede554f8..a83b7c83d 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_database_model.cpp +++ b/libcockatrice_models/libcockatrice/models/database/card_database_model.cpp @@ -27,14 +27,18 @@ CardDatabaseModel::CardDatabaseModel(CardDatabase *_db, bool _showOnlyCardsFromE CardDatabaseModel::~CardDatabaseModel() = default; -int CardDatabaseModel::rowCount(const QModelIndex & /*parent*/) const +int CardDatabaseModel::rowCount(const QModelIndex &parent) const { - return cardList.size(); + // A list model's card rows are leaves; returning the full card count for a + // valid parent makes every row claim all cards as children. + return parent.isValid() ? 0 : cardList.size(); } -int CardDatabaseModel::columnCount(const QModelIndex & /*parent*/) const +int CardDatabaseModel::columnCount(const QModelIndex &parent) const { - return CARDDBMODEL_COLUMNS; + // Card rows are leaves, so a valid parent holds no columns. Reporting the + // row width for a card index would claim child cells that do not exist. + return parent.isValid() ? 0 : CARDDBMODEL_COLUMNS; } QVariant CardDatabaseModel::data(const QModelIndex &index, int role) const diff --git a/libcockatrice_models/libcockatrice/models/database/card_database_visual_display_model.cpp b/libcockatrice_models/libcockatrice/models/database/card_database_visual_display_model.cpp new file mode 100644 index 000000000..f6a3a925c --- /dev/null +++ b/libcockatrice_models/libcockatrice/models/database/card_database_visual_display_model.cpp @@ -0,0 +1,123 @@ +#include "card_database_visual_display_model.h" + +#include "card_database_display_model.h" +#include "libcockatrice/card/database/card_database_querier.h" + +#include +#include +#include +#include + +/// Coalesces the bursts of proxy signals a filter, sort or database load produces. +static constexpr int REBUILD_COALESCE_MS = 30; + +CardDatabaseVisualDisplayModel::CardDatabaseVisualDisplayModel(CardDatabaseDisplayModel *_displayModel, + CardDatabaseQuerier *_querier, + QObject *parent) + : QAbstractListModel(parent), displayModel(_displayModel), querier(_querier) +{ + rebuildTimer.setSingleShot(true); + rebuildTimer.setInterval(REBUILD_COALESCE_MS); + connect(&rebuildTimer, &QTimer::timeout, this, &CardDatabaseVisualDisplayModel::rebuild); + + if (displayModel == nullptr) { + return; + } + + connect(displayModel, &QAbstractItemModel::modelReset, this, &CardDatabaseVisualDisplayModel::scheduleRebuild); + connect(displayModel, &QAbstractItemModel::layoutChanged, this, &CardDatabaseVisualDisplayModel::scheduleRebuild); + connect(displayModel, &QAbstractItemModel::rowsInserted, this, &CardDatabaseVisualDisplayModel::scheduleRebuild); + connect(displayModel, &QAbstractItemModel::rowsRemoved, this, &CardDatabaseVisualDisplayModel::scheduleRebuild); + connect(displayModel, &QAbstractItemModel::dataChanged, this, &CardDatabaseVisualDisplayModel::scheduleRebuild); +} + +void CardDatabaseVisualDisplayModel::setSetFilterTerms(const QStringList &terms) +{ + if (setFilterTerms == terms) { + return; + } + + setFilterTerms = terms; + refresh(); +} + +void CardDatabaseVisualDisplayModel::refresh() +{ + rebuildTimer.stop(); + rebuild(); +} + +void CardDatabaseVisualDisplayModel::scheduleRebuild() +{ + rebuildTimer.start(); +} + +void CardDatabaseVisualDisplayModel::rebuild() +{ + beginResetModel(); + entries.clear(); + + if (displayModel != nullptr) { + const int proxyRows = displayModel->rowCount(); + entries.reserve(proxyRows); + for (int row = 0; row < proxyRows; ++row) { + const CardInfoPtr info = displayModel->cardInfoAt(displayModel->index(row, 0)); + if (info) { + appendPrintingsFor(info); + } + } + } + + endResetModel(); +} + +void CardDatabaseVisualDisplayModel::appendPrintingsFor(const CardInfoPtr &info) +{ + if (setFilterTerms.isEmpty()) { + entries.append({info, PrintingInfo()}); + return; + } + + const auto &printingsBySet = info->getSets(); + for (const QString &term : setFilterTerms) { + const auto printings = printingsBySet.constFind(term); + if (printings == printingsBySet.constEnd()) { + continue; + } + for (const PrintingInfo &printing : printings.value()) { + entries.append({info, printing}); + } + } +} + +int CardDatabaseVisualDisplayModel::rowCount(const QModelIndex &parent) const +{ + return parent.isValid() ? 0 : static_cast(entries.size()); +} + +QVariant CardDatabaseVisualDisplayModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() >= entries.size()) { + return {}; + } + + if (role == Qt::DisplayRole || role == Qt::AccessibleTextRole) { + return cardAt(index.row()).getName(); + } + + return {}; +} + +ExactCard CardDatabaseVisualDisplayModel::cardAt(int row) const +{ + if (row < 0 || row >= entries.size()) { + return {}; + } + + const Entry &entry = entries.at(row); + if (entry.printing.isEmpty() && setFilterTerms.isEmpty()) { + return querier != nullptr ? querier->getPreferredCard(entry.info) : ExactCard(entry.info); + } + + return ExactCard(entry.info, entry.printing); +} diff --git a/libcockatrice_models/libcockatrice/models/database/card_database_visual_display_model.h b/libcockatrice_models/libcockatrice/models/database/card_database_visual_display_model.h new file mode 100644 index 000000000..1303b6464 --- /dev/null +++ b/libcockatrice_models/libcockatrice/models/database/card_database_visual_display_model.h @@ -0,0 +1,90 @@ +/** + * @file card_database_visual_display_model.h + * @ingroup CardDatabaseModels + * @brief A list model of the exact cards a visual card grid paints. + * + * The filtered and sorted CardDatabaseDisplayModel holds one row per card name. + * A visual grid shows one picture per *printing* instead, so this model flattens + * the proxy rows into ExactCards: the preferred printing per card normally, and + * every printing of the selected sets while set filters are active. + */ + +#ifndef COCKATRICE_CARD_DATABASE_VISUAL_DISPLAY_MODEL_H +#define COCKATRICE_CARD_DATABASE_VISUAL_DISPLAY_MODEL_H + +#include "libcockatrice/card/card_info.h" +#include "libcockatrice/card/printing/exact_card.h" +#include "libcockatrice/card/printing/printing_info.h" + +#include +#include +#include +#include +#include +#include +#include + +class CardDatabaseDisplayModel; +class CardDatabaseQuerier; + +class CardDatabaseVisualDisplayModel : public QAbstractListModel +{ + Q_OBJECT + +public: + /** + * @brief Flattens _displayModel into the cards a visual grid displays. + * @param _displayModel The filtered and sorted proxy model of the card database. + * @param _querier Resolves the preferred printing of a card. + * @param parent Parent object. + */ + explicit CardDatabaseVisualDisplayModel(CardDatabaseDisplayModel *_displayModel, + CardDatabaseQuerier *_querier, + QObject *parent = nullptr); + + /** + * @brief Sets the set shorthand names of the active set filters. + * + * With no terms every card is shown with its preferred printing. With terms + * every printing belonging to one of the named sets is shown instead, in term + * order. Rebuilds immediately when the terms change. + */ + void setSetFilterTerms(const QStringList &terms); + + /** + * @brief Rebuilds the row list from the proxy model right away. + * + * Proxy model changes normally rebuild on a short coalescing timer; call this + * when the caller needs the new rows before returning. + */ + void refresh(); + + [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; + [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + /** + * @brief Returns the card painted at @p row, with its preferred printing resolved. + * @param row Row in this model, not in the proxy model. + */ + [[nodiscard]] ExactCard cardAt(int row) const; + +private: + struct Entry + { + CardInfoPtr info; + /// Empty when the entry follows the preferred printing rather than a set filter. + PrintingInfo printing; + }; + + void scheduleRebuild(); + void rebuild(); + void appendPrintingsFor(const CardInfoPtr &info); + + QPointer displayModel; + CardDatabaseQuerier *querier; + QStringList setFilterTerms; + QList entries; + QTimer rebuildTimer; +}; + +#endif // COCKATRICE_CARD_DATABASE_VISUAL_DISPLAY_MODEL_H diff --git a/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp index a0b9c1061..b8bdc3466 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp +++ b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.cpp @@ -1,5 +1,6 @@ #include "card_sets_model.h" +#include "../../natural_sort_collator.h" #include "libcockatrice/card/database/card_database.h" #include "libcockatrice/card/set/card_set.h" #include "libcockatrice/interfaces/interface_card_set_priority_controller.h" @@ -296,7 +297,8 @@ QStringList SetsModel::mimeTypes() const return QStringList() << "application/x-cockatricecardset"; } -SetsDisplayModel::SetsDisplayModel(QObject *parent) : QSortFilterProxyModel(parent) +SetsDisplayModel::SetsDisplayModel(QObject *parent) + : QSortFilterProxyModel(parent), collator(NaturalSort::makeNaturalSortCollator()) { setFilterCaseSensitivity(Qt::CaseInsensitive); setSortCaseSensitivity(Qt::CaseInsensitive); @@ -331,5 +333,5 @@ bool SetsDisplayModel::lessThan(const QModelIndex &left, const QModelIndex &righ QString leftString = sourceModel()->data(left, SetsModel::SortRole).toString(); QString rightString = sourceModel()->data(right, SetsModel::SortRole).toString(); - return QString::localeAwareCompare(leftString, rightString) < 0; + return collator.compare(leftString, rightString) < 0; } diff --git a/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.h b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.h index 354414dc8..17eab6c53 100644 --- a/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.h +++ b/libcockatrice_models/libcockatrice/models/database/card_set/card_sets_model.h @@ -11,6 +11,7 @@ #include "libcockatrice/card/set/card_set_list.h" #include +#include #include #include #include @@ -112,6 +113,9 @@ protected: [[nodiscard]] bool lessThan(const QModelIndex &left, const QModelIndex &right) const override; [[nodiscard]] bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; void fetchMore(const QModelIndex &index) override; + +private: + QCollator collator; }; #endif diff --git a/libcockatrice_models/libcockatrice/models/deck_list/CMakeLists.txt b/libcockatrice_models/libcockatrice/models/deck_list/CMakeLists.txt index 324bed102..ce5c4d54b 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/CMakeLists.txt +++ b/libcockatrice_models/libcockatrice/models/deck_list/CMakeLists.txt @@ -1,13 +1,7 @@ -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - set(HEADERS deck_list_model.h deck_list_sort_filter_proxy_model.h) -qt6_wrap_cpp(MOC_SOURCES ${HEADERS}) - add_library( - libcockatrice_models_deck_list STATIC ${MOC_SOURCES} deck_list_model.cpp deck_list_model_custom_zones.cpp + libcockatrice_models_deck_list STATIC ${HEADERS} deck_list_model.cpp deck_list_model_custom_zones.cpp deck_list_sort_filter_proxy_model.cpp ) diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp index 4b4088972..617324fe3 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_model.cpp @@ -134,6 +134,13 @@ void DeckListModel::rebuildTree() int DeckListModel::rowCount(const QModelIndex &parent) const { + // Only column 0 carries children. Column 1 and later indexes are siblings + // used by views for the remaining cells of a row, and a model that hangs + // children off them breaks QAbstractItemModelTester and proxy models. + if (parent.isValid() && parent.column() != 0) { + return 0; + } + // debugIndexInfo("rowCount", parent); auto *node = getNode(parent); if (node) { @@ -282,7 +289,7 @@ QVariant DeckListModel::headerData(const int section, const Qt::Orientation orie QModelIndex DeckListModel::index(int row, int column, const QModelIndex &parent) const { // debugIndexInfo("index", parent); - if (!hasIndex(row, column, parent)) { + if ((parent.isValid() && parent.column() != 0) || !hasIndex(row, column, parent)) { return {}; } diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.cpp b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.cpp index dcb294ec8..cf3410560 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.cpp +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.cpp @@ -23,7 +23,7 @@ bool DeckListSortFilterProxyModel::lessThan(const QModelIndex &left, const QMode if (!leftIsCard || !rightIsCard) { QString lName = src->data(left.siblingAtColumn(DeckListModelColumns::CARD_NAME), Qt::EditRole).toString(); QString rName = src->data(right.siblingAtColumn(DeckListModelColumns::CARD_NAME), Qt::EditRole).toString(); - return lName.localeAwareCompare(rName) < 0; + return collator.compare(lName, rName) < 0; } // Both are cards -> apply sort criteria @@ -38,7 +38,7 @@ bool DeckListSortFilterProxyModel::lessThan(const QModelIndex &left, const QMode if (crit == "name") { QString ln = lNode->getName(); QString rn = rNode->getName(); - int cmp = ln.localeAwareCompare(rn); + int cmp = collator.compare(ln, rn); if (cmp != 0) { return cmp < 0; } @@ -51,14 +51,14 @@ bool DeckListSortFilterProxyModel::lessThan(const QModelIndex &left, const QMode } else if (crit == "colors") { QString lr = lInfo ? lInfo->getColors() : QString(); QString rr = rInfo ? rInfo->getColors() : QString(); - int cmp = lr.localeAwareCompare(rr); + int cmp = collator.compare(lr, rr); if (cmp != 0) { return cmp < 0; } } else if (crit == "maintype") { QString lr = lInfo ? lInfo->getMainCardType() : QString(); QString rr = rInfo ? rInfo->getMainCardType() : QString(); - int cmp = lr.localeAwareCompare(rr); + int cmp = collator.compare(lr, rr); if (cmp != 0) { return cmp < 0; } diff --git a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.h b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.h index 522ed1e23..b911b4740 100644 --- a/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.h +++ b/libcockatrice_models/libcockatrice/models/deck_list/deck_list_sort_filter_proxy_model.h @@ -7,6 +7,9 @@ #ifndef COCKATRICE_DECK_LIST_SORT_FILTER_PROXY_MODEL_H #define COCKATRICE_DECK_LIST_SORT_FILTER_PROXY_MODEL_H +#include "../natural_sort_collator.h" + +#include #include #include #include @@ -19,7 +22,8 @@ class DeckListSortFilterProxyModel : public QSortFilterProxyModel { Q_OBJECT public: - explicit DeckListSortFilterProxyModel(QObject *parent = nullptr) : QSortFilterProxyModel(parent) + explicit DeckListSortFilterProxyModel(QObject *parent = nullptr) + : QSortFilterProxyModel(parent), collator(NaturalSort::makeNaturalSortCollator()) { } @@ -34,6 +38,7 @@ protected: private: QStringList sortCriteria; + QCollator collator; }; #endif // COCKATRICE_DECK_LIST_SORT_FILTER_PROXY_MODEL_H diff --git a/libcockatrice_models/libcockatrice/models/natural_sort_collator.h b/libcockatrice_models/libcockatrice/models/natural_sort_collator.h new file mode 100644 index 000000000..8c026ca06 --- /dev/null +++ b/libcockatrice_models/libcockatrice/models/natural_sort_collator.h @@ -0,0 +1,30 @@ +#ifndef NATURAL_SORT_COLLATOR_H +#define NATURAL_SORT_COLLATOR_H + +#include +#include + +namespace NaturalSort +{ + +/** + * @brief A collator that compares runs of digits numerically, whatever the process locale is. + * + * Numeric mode only takes effect through a real locale. Under the C locale, which + * headless CI processes run in, QCollator silently falls back to plain string order + * and sorts "Forest 10" before "Forest 2". Pin English for that single degenerate + * locale so sorting is the same everywhere, and keep the user's locale otherwise. + */ +inline QCollator makeNaturalSortCollator() +{ + QCollator collator; + collator.setNumericMode(true); + if (QLocale().language() == QLocale::C) { + collator.setLocale(QLocale(QLocale::English)); + } + return collator; +} + +} // namespace NaturalSort + +#endif // NATURAL_SORT_COLLATOR_H \ No newline at end of file diff --git a/libcockatrice_network/CMakeLists.txt b/libcockatrice_network/CMakeLists.txt index 3069c0db4..925e4d445 100644 --- a/libcockatrice_network/CMakeLists.txt +++ b/libcockatrice_network/CMakeLists.txt @@ -1,7 +1,3 @@ -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - add_subdirectory(libcockatrice/network/client) add_subdirectory(libcockatrice/network/server) diff --git a/libcockatrice_network/libcockatrice/network/client/CMakeLists.txt b/libcockatrice_network/libcockatrice/network/client/CMakeLists.txt index bcf62463c..cf8d9fc4d 100644 --- a/libcockatrice_network/libcockatrice/network/client/CMakeLists.txt +++ b/libcockatrice_network/libcockatrice/network/client/CMakeLists.txt @@ -1,7 +1,3 @@ -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - add_subdirectory(abstract) add_subdirectory(local) add_subdirectory(remote) diff --git a/libcockatrice_network/libcockatrice/network/client/abstract/CMakeLists.txt b/libcockatrice_network/libcockatrice/network/client/abstract/CMakeLists.txt index 53ad524b4..33812e0bf 100644 --- a/libcockatrice_network/libcockatrice/network/client/abstract/CMakeLists.txt +++ b/libcockatrice_network/libcockatrice/network/client/abstract/CMakeLists.txt @@ -1,14 +1,8 @@ -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - set(HEADERS abstract_client.h latency_tracker.h) set(SOURCES abstract_client.cpp latency_tracker.cpp) -qt6_wrap_cpp(MOC_SOURCES ${HEADERS}) - -add_library(libcockatrice_network_client_abstract STATIC ${MOC_SOURCES} ${SOURCES}) +add_library(libcockatrice_network_client_abstract STATIC ${HEADERS} ${SOURCES}) add_dependencies(libcockatrice_network_client_abstract libcockatrice_protocol libcockatrice_network_server_remote) @@ -17,7 +11,7 @@ target_include_directories(libcockatrice_network_client_abstract PUBLIC .) target_link_libraries( libcockatrice_network_client_abstract PUBLIC ${COCKATRICE_QT_VERSION_NAME}::Network ${COCKATRICE_QT_VERSION_NAME}::WebSockets libcockatrice_protocol - libcockatrice_network_server_remote + libcockatrice_network_server_remote libcockatrice_utility ) target_precompile_headers(libcockatrice_network_client_abstract PRIVATE "${CMAKE_SOURCE_DIR}/cmake/pch/qtcore_pch.h") diff --git a/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.cpp b/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.cpp index d66653fb1..04e6beda0 100644 --- a/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.cpp +++ b/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.cpp @@ -10,6 +10,7 @@ #include "libcockatrice/protocol/pb/serverinfo_user.pb.h" #include "libcockatrice/protocol/pb/session_commands.pb.h" #include "libcockatrice/protocol/pb/session_event.pb.h" +#include "libcockatrice/utility/passwordhasher.h" #include #include @@ -41,7 +42,8 @@ #include AbstractClient::AbstractClient(QObject *parent) - : QObject(parent), nextCmdId(0), status(StatusDisconnected), serverSupportsPasswordHash(false) + : QObject(parent), nextCmdId(0), status(StatusDisconnected), serverSupportsPasswordHash(false), + serverSupportsChallengeResponse(false) { qRegisterMetaType("QVariant"); qRegisterMetaType("CommandContainer"); @@ -79,6 +81,26 @@ AbstractClient::~AbstractClient() { } +Command_AccountPassword AbstractClient::makeAccountPasswordCommand(const QString &oldPassword, + const QString &newPassword) const +{ + Command_AccountPassword cmd; + cmd.set_old_password(oldPassword.toStdString()); + if (getServerSupportsChallengeResponse()) { + // A verifier lets the server upgrade the stored credential without ever seeing the + // password, and it is what a challenge-response server expects for a new password. + cmd.set_hashed_new_password(PasswordHasher::generatePasswordVerifier(newPassword).toStdString()); + } else if (getServerSupportsPasswordHash()) { + // The server hashes the password itself with its own salt; a salted hash is the + // strongest form it accepts, so the plaintext never goes on the wire. + const QString passwordSalt = PasswordHasher::generateRandomSalt(); + cmd.set_hashed_new_password(PasswordHasher::computeHash(newPassword, passwordSalt).toStdString()); + } else { + cmd.set_new_password(newPassword.toStdString()); + } + return cmd; +} + void AbstractClient::processProtocolItem(const ServerMessage &item) { switch (item.message_type()) { diff --git a/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h b/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h index 442e96eb2..005587ccc 100644 --- a/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h +++ b/libcockatrice_network/libcockatrice/network/client/abstract/abstract_client.h @@ -47,6 +47,7 @@ class Event_UserJoined; class Event_UserLeft; class Event_ServerMessage; class Event_ListRooms; +class Command_AccountPassword; class Event_GameJoined; class Event_UserMessage; class Event_NotifyUser; @@ -67,6 +68,9 @@ enum ClientStatus StatusSubmitForgotPasswordReset, StatusSubmitForgotPasswordChallenge, StatusGettingPasswordSalt, + //! The server accepted the credentials but keeps the session open until the account + //! password is replaced; the connection must survive for `Command_AccountPassword`. + StatusPasswordChangeRequired, }; class AbstractClient : public QObject @@ -133,6 +137,7 @@ protected: QMap pendingCommands; QString userName, password, email, country, realName, token; bool serverSupportsPasswordHash; + bool serverSupportsChallengeResponse; void setStatus(ClientStatus _status); int getNewCmdId() { @@ -166,6 +171,20 @@ public: { return serverSupportsPasswordHash; } + bool getServerSupportsChallengeResponse() const + { + return serverSupportsChallengeResponse; + } + + /** + * @brief Builds the account-password command in the form this server understands. + * + * Challenge-response servers take a scrypt verifier; servers that only advertise + * SupportsPasswordHash take a salted SHA-512 hash; anything older gets the plaintext, + * which is the only form it understands. Both password-change paths go through here so + * the two cannot drift apart and hand a plaintext password to a server that can hash it. + */ + Command_AccountPassword makeAccountPasswordCommand(const QString &oldPassword, const QString &newPassword) const; const QString &getUserName() const { return userName; diff --git a/libcockatrice_network/libcockatrice/network/client/local/CMakeLists.txt b/libcockatrice_network/libcockatrice/network/client/local/CMakeLists.txt index 08c12b7f8..3eff7943a 100644 --- a/libcockatrice_network/libcockatrice/network/client/local/CMakeLists.txt +++ b/libcockatrice_network/libcockatrice/network/client/local/CMakeLists.txt @@ -1,14 +1,8 @@ -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - set(HEADERS local_client.h) set(SOURCES local_client.cpp) -qt6_wrap_cpp(MOC_SOURCES ${HEADERS}) - -add_library(libcockatrice_network_client_local STATIC ${MOC_SOURCES} ${SOURCES}) +add_library(libcockatrice_network_client_local STATIC ${HEADERS} ${SOURCES}) add_dependencies(libcockatrice_network_client_local libcockatrice_network_client_abstract) diff --git a/libcockatrice_network/libcockatrice/network/client/local/local_client.cpp b/libcockatrice_network/libcockatrice/network/client/local/local_client.cpp index c1c1cc976..b9a1566f8 100644 --- a/libcockatrice_network/libcockatrice/network/client/local/local_client.cpp +++ b/libcockatrice_network/libcockatrice/network/client/local/local_client.cpp @@ -33,20 +33,26 @@ LocalClient::LocalClient(LocalServerInterface *_lsi, sendCommand(prepareSessionCommand(joinCmd)); } -LocalClient::~LocalClient() -{ -} - void LocalClient::sendCommandContainer(const CommandContainer &cont) { - qCDebug(LocalClientLog).noquote() << userName << "OUT" << getSafeDebugString(cont); + if (LocalClientLog().isDebugEnabled()) { + qCDebug(LocalClientLog).noquote() << userName << "OUT" << getSafeDebugString(cont); + } + + if (lsi.isNull()) { + qCWarning(LocalClientLog).noquote() + << userName << "dropping command, local server interface no longer exists:" << getSafeDebugString(cont); + return; + } lsi->itemFromClient(cont); } void LocalClient::itemFromServer(const ServerMessage &item) { - qCDebug(LocalClientLog).noquote() << userName << "IN" << getSafeDebugString(item); + if (LocalClientLog().isDebugEnabled()) { + qCDebug(LocalClientLog).noquote() << userName << "IN" << getSafeDebugString(item); + } processProtocolItem(item); } diff --git a/libcockatrice_network/libcockatrice/network/client/local/local_client.h b/libcockatrice_network/libcockatrice/network/client/local/local_client.h index 3ae570255..15b36d3db 100644 --- a/libcockatrice_network/libcockatrice/network/client/local/local_client.h +++ b/libcockatrice_network/libcockatrice/network/client/local/local_client.h @@ -10,6 +10,7 @@ #include "../abstract/abstract_client.h" #include +#include #include #include @@ -24,14 +25,15 @@ class LocalClient : public AbstractClient { Q_OBJECT private: - LocalServerInterface *lsi; + // Guarded pointer: the LocalServer (and its interfaces) may be destroyed while this client + // still has pending commands queued on the event loop. + QPointer lsi; public: LocalClient(LocalServerInterface *_lsi, const QString &_playerName, const QString &_clientId, QObject *parent = nullptr); - ~LocalClient() override; void sendCommandContainer(const CommandContainer &cont) override; private slots: diff --git a/libcockatrice_network/libcockatrice/network/client/remote/CMakeLists.txt b/libcockatrice_network/libcockatrice/network/client/remote/CMakeLists.txt index c32ca0aa8..006c18d42 100644 --- a/libcockatrice_network/libcockatrice/network/client/remote/CMakeLists.txt +++ b/libcockatrice_network/libcockatrice/network/client/remote/CMakeLists.txt @@ -1,14 +1,8 @@ -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - set(HEADERS remote_client.h) set(SOURCES remote_client.cpp) -qt6_wrap_cpp(MOC_SOURCES ${HEADERS}) - -add_library(libcockatrice_network_client_remote STATIC ${MOC_SOURCES} ${SOURCES}) +add_library(libcockatrice_network_client_remote STATIC ${HEADERS} ${SOURCES}) add_dependencies(libcockatrice_network_client_remote libcockatrice_network_client_abstract libcockatrice_protocol) diff --git a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp index feda9e3f4..faacdc9f7 100644 --- a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp +++ b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.cpp @@ -40,9 +40,51 @@ class QObject; static const unsigned int protocolVersion = 14; +// Explanations for logins that do not complete but carry no missing-feature list: the +// ones RemoteClient stops itself, plus a strict server asking for a password. They all +// travel with RespClientUpdateRequired and are shown by the same dialog, so their wording +// is kept together here and adjusted as a set. The context has to stay "RemoteClient" for +// the tr() calls at the use sites to find the translations. +namespace +{ +// The server warns that this account still uses the legacy password hash while this client +// holds a scrypt verifier. Sending the verifier would downgrade the proof, so the client +// stops; the login only succeeds after the saved verifier is gone and the user types the +// password, which also upgrades the account. +constexpr const char *const LOGIN_ABORT_AUTH_DOWNGRADE = QT_TRANSLATE_NOOP( + "RemoteClient", + "The server still uses an old password format for this account, so the saved login on this computer cannot be " + "used. Untick 'Save password' in the Connect window and connect; when it opens again, type your password."); +// The account is still on the legacy hash and only the plaintext password can produce it. +constexpr const char *const LOGIN_ABORT_PASSWORD_REQUIRED = + QT_TRANSLATE_NOOP("RemoteClient", + "This account still uses an old password format. Type your password in the Connect window and " + "log in; the account is upgraded automatically."); +// Rejected by PasswordHasher::costParamsAreSane, either for being weak or for being too +// expensive for this client. Nothing on the client side can fix it. +constexpr const char *const LOGIN_ABORT_UNSAFE_SCRYPT_PARAMS = + QT_TRANSLATE_NOOP("RemoteClient", + "The server asked for password settings that Cockatrice will not accept because they are too " + "weak or too demanding. The login was stopped; only the server's staff can change this."); +// Scrypt derivation failed locally: a device resource problem, or server parameters this +// client cannot satisfy. +constexpr const char *const LOGIN_ABORT_VERIFIER_NOT_DERIVED = + QT_TRANSLATE_NOOP("RemoteClient", + "Cockatrice could not prepare your password for this login. Try again; if it keeps happening, " + "the server's password settings may be too demanding for this version of Cockatrice."); +constexpr const char *const LOGIN_ABORT_STORED_VERIFIER_UNUSABLE = + QT_TRANSLATE_NOOP("RemoteClient", + "The saved login for this account cannot be used. Type your password in the Connect window; if " + "'Save password' is ticked, a new saved login is created after you log in."); +constexpr const char *const LOGIN_ABORT_PASSWORD_MISSING = + QT_TRANSLATE_NOOP("RemoteClient", + "This server requires your password to log in. Type it in the Connect window and try again."); +} // namespace + RemoteClient::RemoteClient(QObject *parent, INetworkSettingsProvider *_networkSettingsProvider) : AbstractClient(parent), networkSettingsProvider(_networkSettingsProvider), timeRunning(0), lastDataReceived(0), - messageInProgress(false), handshakeStarted(false), usingWebSocket(false), messageLength(0), hashedPassword() + messageInProgress(false), handshakeStarted(false), usingWebSocket(false), messageLength(0), hashedPassword(), + passwordNeedsMigration(false) { clearNewClientFeatures(); @@ -83,6 +125,7 @@ RemoteClient::RemoteClient(QObject *parent, INetworkSettingsProvider *_networkSe &RemoteClient::doSubmitForgotPasswordResetToServer); connect(this, &RemoteClient::sigSubmitForgotPasswordChallengeToServer, this, &RemoteClient::doSubmitForgotPasswordChallengeToServer); + connect(this, &RemoteClient::sigChangePasswordToServer, this, &RemoteClient::doChangePasswordToServer); } RemoteClient::~RemoteClient() @@ -129,6 +172,11 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica return; } serverSupportsPasswordHash = event.server_options() & Event_ServerIdentification::SupportsPasswordHash; + // Challenge-response is advertised in its own field so server_options stays + // exactly SupportsPasswordHash on the wire for already-released clients + // (proto2 enums are closed: an unknown value parses as NoOptions and those + // clients would fall back to plaintext passwords). + serverSupportsChallengeResponse = event.has_supports_challenge_response() && event.supports_challenge_response(); if (getStatus() == StatusRequestingForgotPassword) { Command_ForgotPasswordRequest cmdForgotPasswordRequest; @@ -145,7 +193,13 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica cmdForgotPasswordReset.set_user_name(userName.toStdString()); cmdForgotPasswordReset.set_clientid(getSrvClientID(lastHostname).toStdString()); cmdForgotPasswordReset.set_token(token.toStdString()); - if (!password.isEmpty() && serverSupportsPasswordHash) { + if (!password.isEmpty() && serverSupportsChallengeResponse) { + hashedPassword = PasswordHasher::generatePasswordVerifier(password); + // Only this branch yields a challenge-response verifier worth persisting; the + // legacy-hash and plaintext branches below must not be saved under the password key. + derivedVerifier = hashedPassword; + cmdForgotPasswordReset.set_hashed_new_password(hashedPassword.toStdString()); + } else if (!password.isEmpty() && serverSupportsPasswordHash) { auto passwordSalt = PasswordHasher::generateRandomSalt(); hashedPassword = PasswordHasher::computeHash(password, passwordSalt); cmdForgotPasswordReset.set_hashed_new_password(hashedPassword.toStdString()); @@ -173,7 +227,10 @@ void RemoteClient::processServerIdentificationEvent(const Event_ServerIdentifica if (getStatus() == StatusRegistering) { Command_Register cmdRegister; cmdRegister.set_user_name(userName.toStdString()); - if (!password.isEmpty() && serverSupportsPasswordHash) { + if (!password.isEmpty() && serverSupportsChallengeResponse) { + hashedPassword = PasswordHasher::generatePasswordVerifier(password); + cmdRegister.set_hashed_password(hashedPassword.toStdString()); + } else if (!password.isEmpty() && serverSupportsPasswordHash) { auto passwordSalt = PasswordHasher::generateRandomSalt(); hashedPassword = PasswordHasher::computeHash(password, passwordSalt); cmdRegister.set_hashed_password(hashedPassword.toStdString()); @@ -238,7 +295,9 @@ Command_Login RemoteClient::generateCommandLogin() void RemoteClient::doLogin() { - if (!password.isEmpty() && serverSupportsPasswordHash) { + if ((!password.isEmpty() || !storedVerifier.isEmpty()) && serverSupportsChallengeResponse) { + doRequestPasswordSalt(); // ask salt + nonce to build the challenge response + } else if (!password.isEmpty() && serverSupportsPasswordHash) { //! \todo Store and log in using stored hashed password. if (hashedPassword.isEmpty()) { doRequestPasswordSalt(); // ask salt to create hashedPassword, then log in @@ -272,6 +331,53 @@ void RemoteClient::doHashedLogin() sendCommand(pend); } +void RemoteClient::doSubmitPasswordVerifier() +{ + pendingVerifier = PasswordHasher::generatePasswordVerifier(password); + Command_SubmitPasswordVerifier cmdSubmitVerifier; + cmdSubmitVerifier.set_password_verifier(pendingVerifier.toStdString()); + + PendingCommand *pend = prepareSessionCommand(cmdSubmitVerifier); + connect(pend, &PendingCommand::finished, this, &RemoteClient::submitPasswordVerifierResponse); + sendCommand(pend); +} + +void RemoteClient::submitPasswordVerifierResponse(const Response &response) +{ + if (response.response_code() == Response::RespOk) { + qCDebug(RemoteClientLog) << "Password verifier migrated successfully"; + if (!pendingVerifier.isEmpty()) { + emit sigPasswordVerifierReady(pendingVerifier); + pendingVerifier.clear(); + } + } else { + qCWarning(RemoteClientLog) << "Failed to migrate password verifier:" << response.response_code(); + } +} + +void RemoteClient::changePasswordToServer(const QString &oldPassword, const QString &newPassword) +{ + emit sigChangePasswordToServer(oldPassword, newPassword); +} + +void RemoteClient::doChangePasswordToServer(const QString &oldPassword, const QString &newPassword) +{ + const Command_AccountPassword cmd = makeAccountPasswordCommand(oldPassword, newPassword); + + PendingCommand *pend = prepareSessionCommand(cmd); + connect(pend, &PendingCommand::finished, this, &RemoteClient::accountPasswordResponse); + sendCommand(pend); +} + +void RemoteClient::accountPasswordResponse(const Response &response) +{ + if (response.response_code() == Response::RespOk) { + emit sigPasswordChangeSuccess(); + } else { + emit sigPasswordChangeError(response.response_code()); + } +} + void RemoteClient::processConnectionClosedEvent(const Event_ConnectionClosed & /*event*/) { doDisconnectFromServer(); @@ -284,7 +390,65 @@ void RemoteClient::passwordSaltResponse(const Response &response) auto passwordSalt = QString::fromStdString(resp.password_salt()); if (passwordSalt.isEmpty()) { // the server does not recognize the user but allows them to enter unregistered password.clear(); // the password will not be used + storedVerifier.clear(); doLogin(); + } else if (serverSupportsChallengeResponse && resp.has_nonce()) { + const QByteArray nonce = QByteArray::fromStdString(resp.nonce()); + // needs_migration is a server-controlled flag over an unauthenticated + // transport (plain TCP by default). If we already hold a scrypt + // verifier for this account, a server telling us to fall back to the + // 1000-round legacy hash is a downgrade of the KDF — refuse it rather + // than hand an attacker a weakly-keyed HMAC they can attack offline. + if (resp.needs_migration() && PasswordHasher::parsePasswordVerifier(storedVerifier).isValid) { + emit loginError(Response::RespClientUpdateRequired, tr(LOGIN_ABORT_AUTH_DOWNGRADE), 0, {}); + return; + } + QByteArray key; + if (resp.needs_migration()) { + // The account still uses the legacy format; the legacy full hash + // is only derivable from the plaintext password. + if (password.isEmpty()) { + emit loginError(Response::RespClientUpdateRequired, tr(LOGIN_ABORT_PASSWORD_REQUIRED), 0, {}); + return; + } + key = PasswordHasher::computeHash(password, passwordSalt).toUtf8(); + } else if (!password.isEmpty()) { + // A hostile server must not be able to make us run or allocate for + // unreasonable scrypt parameters. + const int n = resp.has_n() ? resp.n() : SCRYPT_N; + const int r = resp.has_r() ? resp.r() : SCRYPT_R; + const int p = resp.has_p() ? resp.p() : SCRYPT_P; + if (!PasswordHasher::costParamsAreSane(n, r, p)) { + emit loginError(Response::RespClientUpdateRequired, tr(LOGIN_ABORT_UNSAFE_SCRYPT_PARAMS), 0, {}); + return; + } + // password_salt may carry a legacy backup salt for pre-challenge clients; + // the verifier itself is always derived from scrypt_salt when present. + const QString scryptSalt = + resp.has_scrypt_salt() ? QString::fromStdString(resp.scrypt_salt()) : passwordSalt; + key = PasswordHasher::deriveKey(password, QByteArray::fromBase64(scryptSalt.toUtf8()), n, r, p); + if (key.isEmpty()) { + emit loginError(Response::RespClientUpdateRequired, tr(LOGIN_ABORT_VERIFIER_NOT_DERIVED), 0, {}); + return; + } + derivedVerifier = + QString("$scrypt$%1$%2$%3$%4$%5").arg(n).arg(r).arg(p).arg(scryptSalt).arg(QString(key.toBase64())); + } else if (!storedVerifier.isEmpty()) { + const PasswordVerifier verifier = PasswordHasher::parsePasswordVerifier(storedVerifier); + if (!verifier.isValid) { + emit loginError(Response::RespClientUpdateRequired, tr(LOGIN_ABORT_STORED_VERIFIER_UNUSABLE), 0, + {}); + return; + } + key = verifier.verifier; + } else { + emit loginError(Response::RespLoginNeeded, {}, 0, {}); + return; + } + passwordNeedsMigration = resp.needs_migration(); + const QByteArray responseBytes = PasswordHasher::computeResponse(key, nonce); + hashedPassword = "$challenge$" + QString(nonce.toBase64()) + "$" + QString(responseBytes.toBase64()); + doHashedLogin(); } else { hashedPassword = PasswordHasher::computeHash(password, passwordSalt); doHashedLogin(); @@ -309,6 +473,14 @@ void RemoteClient::loginResponse(const Response &response) setStatus(StatusLoggedIn); emit userInfoChanged(resp.user_info()); + if (passwordNeedsMigration) { + // The account still used the legacy password format; upgrade it to scrypt. + doSubmitPasswordVerifier(); + } else if (!derivedVerifier.isEmpty()) { + emit sigPasswordVerifierReady(derivedVerifier); + derivedVerifier.clear(); + } + QList buddyList; for (int i = resp.buddy_list_size() - 1; i >= 0; --i) { buddyList.append(resp.buddy_list(i)); @@ -327,6 +499,23 @@ void RemoteClient::loginResponse(const Response &response) emit notifyUserAboutUpdate(); } + } else if (response.response_code() == Response::RespPasswordChangeRequired) { + // The credentials were accepted: the server keeps this session open and serves + // nothing but ping and Command_AccountPassword until the password is replaced. + // Treating it as a failed login would disconnect before the dialog that collects + // the new password (queued to the UI thread behind this slot) can send anything: + // the command would sit in pendingCommands forever, because the ping timer that + // times it out stops with the connection. Keep the session and let the controller + // report the outcome through accountPasswordResponse(). + QList missingFeatures; + if (resp.missing_features_size() > 0) { + for (int i = 0; i < resp.missing_features_size(); ++i) { + missingFeatures << QString::fromStdString(resp.missing_features(i)); + } + } + setStatus(StatusPasswordChangeRequired); + emit loginError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), + static_cast(resp.denied_end_time()), missingFeatures); } else if (response.response_code() != Response::RespNotConnected) { QList missingFeatures; if (resp.missing_features_size() > 0) { @@ -334,8 +523,19 @@ void RemoteClient::loginResponse(const Response &response) missingFeatures << QString::fromStdString(resp.missing_features(i)); } } - emit loginError(response.response_code(), QString::fromStdString(resp.denied_reason_str()), - static_cast(resp.denied_end_time()), missingFeatures); + + QString reasonStr = QString::fromStdString(resp.denied_reason_str()); + // A strict server reports a login that carried no credential as a missing feature, + // because challenge-response needs one. No client update can supply a password, so + // report it like the other local aborts instead of advising an update. + if (response.response_code() == Response::RespClientUpdateRequired && missingFeatures.size() == 1 && + missingFeatures.first() == "challenge_response_auth" && password.isEmpty() && storedVerifier.isEmpty()) { + missingFeatures.clear(); + reasonStr = tr(LOGIN_ABORT_PASSWORD_MISSING); + } + + emit loginError(response.response_code(), reasonStr, static_cast(resp.denied_end_time()), + missingFeatures); setStatus(StatusDisconnecting); } } @@ -384,9 +584,15 @@ void RemoteClient::readData() inputBuffer.append(data); - do { + // Parse messages straight out of inputBuffer at readOffset. Removing each + // consumed message from the front of the buffer would memmove all of the + // remaining bytes once per message; instead the consumed prefix is dropped + // once parsing stops. + qsizetype readOffset = 0; + + while (true) { if (!messageInProgress) { - if (inputBuffer.size() >= 4) { + if (inputBuffer.size() - readOffset >= 4) { // dirty hack to be compatible with v14 server that sends 60 bytes of garbage at the beginning if (!handshakeStarted) { handshakeStarted = true; @@ -396,29 +602,30 @@ void RemoteClient::readData() } } else { // end of hack - messageLength = (((quint32)(unsigned char)inputBuffer[0]) << 24) + - (((quint32)(unsigned char)inputBuffer[1]) << 16) + - (((quint32)(unsigned char)inputBuffer[2]) << 8) + - ((quint32)(unsigned char)inputBuffer[3]); - inputBuffer.remove(0, 4); + const auto *header = reinterpret_cast(inputBuffer.constData() + readOffset); + messageLength = (static_cast(header[0]) << 24) + (static_cast(header[1]) << 16) + + (static_cast(header[2]) << 8) + static_cast(header[3]); + readOffset += 4; messageInProgress = true; } } else { - return; + break; } } - if (inputBuffer.size() < messageLength) { - return; + if (inputBuffer.size() - readOffset < messageLength) { + break; } ServerMessage newServerMessage; - bool ok = newServerMessage.ParseFromArray(inputBuffer.data(), messageLength); + bool ok = newServerMessage.ParseFromArray(inputBuffer.constData() + readOffset, messageLength); - inputBuffer.remove(0, messageLength); + readOffset += messageLength; messageInProgress = false; if (ok) { - qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage); + if (RemoteClientLog().isDebugEnabled()) { + qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage); + } processProtocolItem(newServerMessage); } else { @@ -428,7 +635,12 @@ void RemoteClient::readData() if (getStatus() == StatusDisconnecting) { // use thread-safe getter doDisconnectFromServer(); } - } while (!inputBuffer.isEmpty()); + } + + // Drop the consumed prefix once, instead of shifting the buffer per message. + if (readOffset > 0) { + inputBuffer.remove(0, readOffset); + } } void RemoteClient::websocketMessageReceived(const QByteArray &message) @@ -436,7 +648,9 @@ void RemoteClient::websocketMessageReceived(const QByteArray &message) lastDataReceived = timeRunning; ServerMessage newServerMessage; if (newServerMessage.ParseFromArray(message.data(), message.length())) { - qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage); + if (RemoteClientLog().isDebugEnabled()) { + qCDebug(RemoteClientLog).noquote() << "IN" << getSafeDebugString(newServerMessage); + } processProtocolItem(newServerMessage); } else { @@ -452,7 +666,9 @@ void RemoteClient::sendCommandContainer(const CommandContainer &cont) auto size = static_cast(cont.ByteSize()); #endif - qCDebug(RemoteClientLog).noquote() << "OUT" << getSafeDebugString(cont); + if (RemoteClientLog().isDebugEnabled()) { + qCDebug(RemoteClientLog).noquote() << "OUT" << getSafeDebugString(cont); + } QByteArray buf; bool ok; @@ -565,6 +781,8 @@ void RemoteClient::doDisconnectFromServer() websocket->close(); } socket->close(); + derivedVerifier.clear(); + pendingVerifier.clear(); } void RemoteClient::ping() @@ -707,7 +925,7 @@ void RemoteClient::requestForgotPasswordResponse(const Response &response) emit sigPromptForForgotPasswordReset(); } } else { - emit sigForgotPasswordError(); + emit sigForgotPasswordError(response.response_code()); } doDisconnectFromServer(); @@ -736,8 +954,14 @@ void RemoteClient::submitForgotPasswordResetResponse(const Response &response) { if (response.response_code() == Response::RespOk) { emit sigForgotPasswordSuccess(); + // Persist only a real scrypt verifier; a legacy hash must not be stored under + // the password key, where it would break future challenge-response logins. + if (!derivedVerifier.isEmpty()) { + emit sigPasswordVerifierReady(derivedVerifier); + derivedVerifier.clear(); + } } else { - emit sigForgotPasswordError(); + emit sigForgotPasswordError(response.response_code()); } doDisconnectFromServer(); @@ -772,7 +996,7 @@ void RemoteClient::submitForgotPasswordChallengeResponse(const Response &respons if (response.response_code() == Response::RespOk) { emit sigPromptForForgotPasswordReset(); } else { - emit sigForgotPasswordError(); + emit sigForgotPasswordError(response.response_code()); } doDisconnectFromServer(); diff --git a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.h b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.h index 4d8c24fd2..1cf40491c 100644 --- a/libcockatrice_network/libcockatrice/network/client/remote/remote_client.h +++ b/libcockatrice_network/libcockatrice/network/client/remote/remote_client.h @@ -54,7 +54,7 @@ signals: void notifyUserAboutUpdate(); void sigRequestForgotPasswordToServer(const QString &hostname, unsigned int port, const QString &_userName); void sigForgotPasswordSuccess(); - void sigForgotPasswordError(); + void sigForgotPasswordError(int responseCode); void sigPromptForForgotPasswordReset(); void sigSubmitForgotPasswordResetToServer(const QString &hostname, unsigned int port, @@ -66,6 +66,16 @@ signals: unsigned int port, const QString &_userName, const QString &_email); + //! \brief Emitted once a scrypt verifier for the given account is known and + //! can be persisted instead of the plaintext password. The receiving side + //! should only store it for the connection it is currently negotiating, so + //! the hostname and user name are intentionally not part of the signal. + void sigPasswordVerifierReady(const QString &verifier); + //! \brief Outcome of the password change the server demanded at login. + void sigPasswordChangeSuccess(); + void sigPasswordChangeError(int responseCode); + //! \brief Request a password change; queued to the client thread like the other requests. + void sigChangePasswordToServer(const QString &oldPassword, const QString &newPassword); private slots: void slotConnected(); void readData(); @@ -92,6 +102,10 @@ private slots: void doLogin(); void doHashedLogin(); Command_Login generateCommandLogin(); + void doSubmitPasswordVerifier(); + void submitPasswordVerifierResponse(const Response &response); + void accountPasswordResponse(const Response &response); + void doChangePasswordToServer(const QString &oldPassword, const QString &newPassword); void doDisconnectFromServer(); void doActivateToServer(const QString &_token); void doRequestForgotPasswordToServer(const QString &hostname, unsigned int port, const QString &_userName); @@ -123,6 +137,14 @@ private: QString lastHostname; unsigned int lastPort; QString hashedPassword; + bool passwordNeedsMigration; + //! \brief A previously stored "$scrypt$..." verifier used to authenticate + //! without the plaintext password. + QString storedVerifier; + //! \brief Verifier derived during the current login, persisted after success. + QString derivedVerifier; + //! \brief Verifier sent for migration, persisted once the server accepts it. + QString pendingVerifier; QString getSrvClientID(const QString &_hostname); bool newMissingFeatureFound(const QString &_serversMissingFeatures); @@ -161,6 +183,12 @@ public: } void connectToServer(const QString &hostname, unsigned int port, const QString &_userName, const QString &_password); + //! \brief Provide a stored "$scrypt$..." verifier so the client can + //! authenticate without the plaintext password. + void setStoredVerifier(const QString &verifier) + { + storedVerifier = verifier; + } void registerToServer(const QString &hostname, unsigned int port, const QString &_userName, @@ -180,6 +208,10 @@ public: unsigned int port, const QString &_userName, const QString &_email); + //! \brief Change the password of the account that just logged in, following the + //! server's forced-password-change reply. Emits sigPasswordChangeSuccess or + //! sigPasswordChangeError when the server answers. + void changePasswordToServer(const QString &oldPassword, const QString &newPassword); }; #endif diff --git a/libcockatrice_network/libcockatrice/network/network_manager.h b/libcockatrice_network/libcockatrice/network/network_manager.h new file mode 100644 index 000000000..bb20e1315 --- /dev/null +++ b/libcockatrice_network/libcockatrice/network/network_manager.h @@ -0,0 +1,38 @@ +#ifndef COCKATRICE_NETWORK_NETWORK_MANAGER_H +#define COCKATRICE_NETWORK_NETWORK_MANAGER_H + +#include +#include +#include + +/** + * @file network_manager.h + * @ingroup Network + * @brief Creates QNetworkAccessManagers with the application-wide baseline. + * + * Every manager in the client should start from NetworkManagerFactory::create() + * so features that apply to all HTTP traffic (the system proxy, a transfer + * timeout) cannot be forgotten in one of the many call sites. Feature-specific + * setup such as a disk cache or a manual redirect policy stays with the caller. + */ +namespace NetworkManagerFactory +{ +/** + * @brief Returns a QNetworkAccessManager configured for Cockatrice. + * + * The manager is parented to @p parent. The system proxy is enabled once, + * process-wide, the first time this function is called. Transfers use Qt's + * default timeout instead of waiting forever. + */ +inline QNetworkAccessManager *create(QObject *parent = nullptr) +{ + static std::once_flag systemProxyFlag; + std::call_once(systemProxyFlag, [] { QNetworkProxyFactory::setUseSystemConfiguration(true); }); + + auto *manager = new QNetworkAccessManager(parent); + manager->setTransferTimeout(); + return manager; +} +} // namespace NetworkManagerFactory + +#endif // COCKATRICE_NETWORK_NETWORK_MANAGER_H diff --git a/libcockatrice_network/libcockatrice/network/server/CMakeLists.txt b/libcockatrice_network/libcockatrice/network/server/CMakeLists.txt index cbb717ad8..b59d61572 100644 --- a/libcockatrice_network/libcockatrice/network/server/CMakeLists.txt +++ b/libcockatrice_network/libcockatrice/network/server/CMakeLists.txt @@ -1,7 +1,3 @@ -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - add_subdirectory(local) add_subdirectory(remote) diff --git a/libcockatrice_network/libcockatrice/network/server/local/CMakeLists.txt b/libcockatrice_network/libcockatrice/network/server/local/CMakeLists.txt index 020753d7f..4055efa64 100644 --- a/libcockatrice_network/libcockatrice/network/server/local/CMakeLists.txt +++ b/libcockatrice_network/libcockatrice/network/server/local/CMakeLists.txt @@ -1,14 +1,8 @@ -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - set(HEADERS local_server.h local_server_interface.h) set(SOURCES local_server.cpp local_server_interface.cpp) -qt6_wrap_cpp(MOC_SOURCES ${HEADERS}) - -add_library(libcockatrice_network_server_local STATIC ${MOC_SOURCES} ${SOURCES}) +add_library(libcockatrice_network_server_local STATIC ${HEADERS} ${SOURCES}) add_dependencies(libcockatrice_network_server_local libcockatrice_protocol) diff --git a/libcockatrice_network/libcockatrice/network/server/remote/CMakeLists.txt b/libcockatrice_network/libcockatrice/network/server/remote/CMakeLists.txt index 2127487d2..181218e1a 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/CMakeLists.txt +++ b/libcockatrice_network/libcockatrice/network/server/remote/CMakeLists.txt @@ -1,7 +1,3 @@ -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - set(HEADERS game/dump_zone_range.h game/server_abstract_participant.h @@ -12,6 +8,7 @@ set(HEADERS game/server_cardzone.h game/server_counter.h game/game_config.h + game/role_info.h game/server_deck_validation_strategy.h game/server_game.h game/server_game_lifecycle_strategy.h @@ -29,11 +26,9 @@ set(HEADERS serverinfo_user_container.h ) -qt6_wrap_cpp(MOC_SOURCES ${HEADERS}) - add_library( libcockatrice_network_server_remote STATIC - ${MOC_SOURCES} + ${HEADERS} game/dump_zone_range.cpp game/server_abstract_participant.cpp game/server_abstract_player.cpp diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/role_info.h b/libcockatrice_network/libcockatrice/network/server/remote/game/role_info.h new file mode 100644 index 000000000..ef8692361 --- /dev/null +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/role_info.h @@ -0,0 +1,13 @@ +#ifndef COCKATRICE_ROLE_INFO_H +#define COCKATRICE_ROLE_INFO_H + +/** + * @brief Represents information about a participant's roles. + */ +struct RoleInfo +{ + bool isJudge = false; ///< Whether the participant joined as a judge + bool isSpectator = false; ///< Whether the participant joined as a spectator +}; + +#endif // COCKATRICE_ROLE_INFO_H diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.cpp index 5b8d901bb..ff8ea14f3 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.cpp @@ -61,10 +61,10 @@ Server_AbstractParticipant::Server_AbstractParticipant(Server_Game *_game, int _playerId, const ServerInfo_User &_userInfo, - bool _judge, + const RoleInfo &_roleInfo, Server_AbstractUserInterface *_userInterface) : ServerInfo_User_Container(_userInfo), game(_game), userInterface(_userInterface), pingTime(0), - playerId(_playerId), judge(_judge) + playerId(_playerId), roleInfo(_roleInfo) { } @@ -98,8 +98,8 @@ void Server_AbstractParticipant::getProperties(ServerInfo_PlayerProperties &resu if (withUserInfo) { copyUserInfo(*(result.mutable_user_info()), true); } - result.set_spectator(spectator); - result.set_judge(judge); + result.set_spectator(isSpectator()); + result.set_judge(isJudge()); result.set_ping_seconds(pingTime); getPlayerProperties(result); } @@ -176,7 +176,7 @@ Response::ResponseCode Server_AbstractParticipant::cmdUnconcede(const Command_Un Response::ResponseCode Server_AbstractParticipant::cmdJudge(const Command_Judge &cmd, ResponseContainer &rc, GameEventStorage &ges) { - if (!judge) { + if (!isJudge()) { return Response::RespFunctionNotAllowed; } @@ -204,7 +204,7 @@ Response::ResponseCode Server_AbstractParticipant::cmdReadyStart(const Command_R Response::ResponseCode Server_AbstractParticipant::cmdGameSay(const Command_GameSay &cmd, ResponseContainer & /*rc*/, GameEventStorage &ges) { - if (spectator) { + if (isSpectator()) { /* Spectators can only talk if: * (a) the game creator allows it * (b) the spectator is a moderator/administrator @@ -365,7 +365,7 @@ Response::ResponseCode Server_AbstractParticipant::cmdNextTurn(const Command_Nex return Response::RespGameNotStarted; } - if (!judge) { + if (!isJudge()) { return Response::RespFunctionNotAllowed; } @@ -381,7 +381,7 @@ Response::ResponseCode Server_AbstractParticipant::cmdSetActivePhase(const Comma return Response::RespGameNotStarted; } - if (!judge) { + if (!isJudge()) { return Response::RespFunctionNotAllowed; } @@ -415,8 +415,8 @@ Response::ResponseCode Server_AbstractParticipant::cmdReverseTurn(const Command_ ResponseContainer & /*rc*/, GameEventStorage &ges) { - if (!judge) { - if (spectator) { + if (!isJudge()) { + if (isSpectator()) { return Response::RespFunctionNotAllowed; } @@ -577,7 +577,7 @@ void Server_AbstractParticipant::setUserInterface(Server_AbstractUserInterface * void Server_AbstractParticipant::disconnectClient() { bool isRegistered = userInfo->user_level() & ServerInfo_User::IsRegistered; - if (!isRegistered || spectator) { + if (!isRegistered || isSpectator()) { game->removeParticipant(this, Event_Leave::USER_DISCONNECTED); } else { setUserInterface(nullptr); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.h index 195da9b7f..b6d4a8025 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_participant.h @@ -2,6 +2,7 @@ #define ABSTRACT_PARTICIPANT_H #include "../serverinfo_user_container.h" +#include "role_info.h" #include "server_arrowtarget.h" #include @@ -61,8 +62,7 @@ protected: Server_AbstractUserInterface *userInterface; int pingTime; int playerId; - bool spectator; - bool judge; + RoleInfo roleInfo; virtual void getPlayerProperties(ServerInfo_PlayerProperties &result); mutable QMutex playerMutex; @@ -70,7 +70,7 @@ public: Server_AbstractParticipant(Server_Game *_game, int _playerId, const ServerInfo_User &_userInfo, - bool _judge, + const RoleInfo &_roleInfo, Server_AbstractUserInterface *_handler); ~Server_AbstractParticipant() override; virtual void prepareDestroy() @@ -91,11 +91,11 @@ public: } bool isSpectator() const { - return spectator; + return roleInfo.isSpectator; } bool isJudge() const { - return judge; + return roleInfo.isJudge; } Server_Game *getGame() const { diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp index c03f1e966..b5a620798 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_abstract_player.cpp @@ -81,10 +81,9 @@ Server_AbstractPlayer::Server_AbstractPlayer(Server_Game *_game, const ServerInfo_User &_userInfo, bool _judge, Server_AbstractUserInterface *_userInterface) - : Server_AbstractParticipant(_game, _playerId, _userInfo, _judge, _userInterface), conceded(false), deck(nullptr), - sideboardLocked(true), readyStart(false), nextCardId(0) + : Server_AbstractParticipant(_game, _playerId, _userInfo, {.isJudge = _judge}, _userInterface), conceded(false), + deck(nullptr), sideboardLocked(true), readyStart(false), nextCardId(0) { - spectator = false; } Server_AbstractPlayer::~Server_AbstractPlayer() = default; @@ -295,7 +294,7 @@ Response::ResponseCode Server_AbstractPlayer::moveCard(GameEventStorage &ges, { // Disallow controller change to other zones than the table. if (((targetzone->getType() != ServerInfo_Zone::PublicZone) || !targetzone->hasCoords()) && - (startzone->getPlayer() != targetzone->getPlayer()) && !judge) { + (startzone->getPlayer() != targetzone->getPlayer()) && !isJudge()) { return Response::RespContextError; } @@ -808,7 +807,7 @@ Server_AbstractPlayer::cmdMoveCard(const Command_MoveCard &cmd, ResponseContaine return Response::RespNameNotFound; } - if ((startPlayer != this) && (!startZone->getPlayersWithWritePermission().contains(playerId)) && !judge) { + if ((startPlayer != this) && (!startZone->getPlayersWithWritePermission().contains(playerId)) && !isJudge()) { return Response::RespContextError; } @@ -821,7 +820,7 @@ Server_AbstractPlayer::cmdMoveCard(const Command_MoveCard &cmd, ResponseContaine return Response::RespNameNotFound; } - if ((startPlayer != this) && (targetPlayer != this) && !judge) { + if ((startPlayer != this) && (targetPlayer != this) && !isJudge()) { return Response::RespContextError; } @@ -1598,7 +1597,7 @@ Server_AbstractPlayer::cmdRevealCards(const Command_RevealCards &cmd, ResponseCo zone->addWritePermission(cmd.player_id()); } - if (judge) { + if (isJudge()) { ges.setOverwriteOwnership(true); } diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp index b1863c91e..49db999f6 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.cpp @@ -41,6 +41,7 @@ #include "libcockatrice/protocol/pb/serverinfo_replay_match.pb.h" #include "libcockatrice/protocol/pb/serverinfo_user.pb.h" #include "libcockatrice/protocol/pb/session_event.pb.h" +#include "role_info.h" #include "server_abstract_player.h" #include "server_abstractuserinterface.h" #include "server_arrow.h" @@ -479,9 +480,8 @@ void Server_Game::stopGameIfFinished() Response::ResponseCode Server_Game::checkJoin(ServerInfo_User *user, const QString &_password, - bool spectator, - bool overrideRestrictions, - bool asJudge) + const RoleInfo &roleInfo, + bool overrideRestrictions) { Server_DatabaseInterface *databaseInterface = room->getServer()->getDatabaseInterface(); for (auto *participant : participants.values()) { @@ -490,11 +490,11 @@ Response::ResponseCode Server_Game::checkJoin(ServerInfo_User *user, } } - if (asJudge && !(user->user_level() & ServerInfo_User::IsJudge)) { + if (roleInfo.isJudge && !(user->user_level() & ServerInfo_User::IsJudge)) { return Response::RespUserLevelTooLow; } if (!(overrideRestrictions && (user->user_level() & (ServerInfo_User::IsModerator | ServerInfo_User::IsJudge)))) { - if ((_password != password) && !(spectator && !spectatorsNeedPassword)) { + if ((_password != password) && !(roleInfo.isSpectator && !spectatorsNeedPassword)) { return Response::RespWrongPassword; } if (!(user->user_level() & ServerInfo_User::IsRegistered) && onlyRegistered) { @@ -510,13 +510,13 @@ Response::ResponseCode Server_Game::checkJoin(ServerInfo_User *user, QString::fromStdString(user->name()))) { return Response::RespInIgnoreList; } - if (spectator) { + if (roleInfo.isSpectator) { if (!spectatorsAllowed) { return Response::RespSpectatorsNotAllowed; } } } - if (!spectator && (gameStarted || (getPlayerCount() >= getMaxPlayers()))) { + if (!roleInfo.isSpectator && (gameStarted || (getPlayerCount() >= getMaxPlayers()))) { return Response::RespGameFull; } @@ -537,19 +537,18 @@ bool Server_Game::containsUser(const QString &userName) const void Server_Game::addPlayer(Server_AbstractUserInterface *userInterface, ResponseContainer &rc, - bool spectator, - bool judge, + const RoleInfo &roleInfo, bool broadcastUpdate) { QMutexLocker locker(&gameMutex); Server_AbstractParticipant *newParticipant; - if (spectator) { + if (roleInfo.isSpectator) { newParticipant = new Server_Spectator(this, nextPlayerId++, userInterface->copyUserInfo(true, true, true), - judge, userInterface); + roleInfo.isJudge, userInterface); } else { - newParticipant = new Server_Player(this, nextPlayerId++, userInterface->copyUserInfo(true, true, true), judge, - userInterface); + newParticipant = new Server_Player(this, nextPlayerId++, userInterface->copyUserInfo(true, true, true), + roleInfo.isJudge, userInterface); } newParticipant->moveToThread(thread()); @@ -560,7 +559,7 @@ void Server_Game::addPlayer(Server_AbstractUserInterface *userInterface, const QString playerName = QString::fromStdString(newParticipant->getUserInfo()->name()); participants.insert(newParticipant->getPlayerId(), newParticipant); - if (spectator) { + if (roleInfo.isSpectator) { allSpectatorsEver.insert(playerName); } else { allPlayersEver.insert(playerName); @@ -579,7 +578,7 @@ void Server_Game::addPlayer(Server_AbstractUserInterface *userInterface, emit gameInfoChanged(gameInfo); } - if ((newParticipant->getUserInfo()->user_level() & ServerInfo_User::IsRegistered) && !spectator) { + if ((newParticipant->getUserInfo()->user_level() & ServerInfo_User::IsRegistered) && !roleInfo.isSpectator) { room->getServer()->addPersistentPlayer(playerName, room->getId(), gameId, newParticipant->getPlayerId()); } @@ -871,6 +870,13 @@ void Server_Game::createGameJoinedEvent(Server_AbstractParticipant *joiningParti } } +bool Server_Game::canSeePrivateEventsFor(const Server_AbstractParticipant *participant, int privatePlayerId) const +{ + return participant->getPlayerId() == privatePlayerId // same player + || participant->isJudge() // judges see everything + || (participant->isSpectator() && spectatorsSeeEverything); // spectators see everything +} + void Server_Game::sendGameEventContainer(GameEventContainer *cont, GameEventStorageItem::EventRecipients recipients, int privatePlayerId) @@ -879,8 +885,7 @@ void Server_Game::sendGameEventContainer(GameEventContainer *cont, cont->set_game_id(gameId); for (auto *participant : participants.values()) { - const bool playerPrivate = (participant->getPlayerId() == privatePlayerId) || participant->isJudge() || - (participant->isSpectator() && spectatorsSeeEverything); + const bool playerPrivate = canSeePrivateEventsFor(participant, privatePlayerId); if ((recipients.testFlag(GameEventStorageItem::SendToPrivate) && playerPrivate) || (recipients.testFlag(GameEventStorageItem::SendToOthers) && !playerPrivate)) { participant->sendGameEvent(*cont); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h index c7f8d0929..363c0ca94 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_game.h @@ -62,6 +62,7 @@ class Message; } // namespace protobuf } // namespace google struct GameConfig; +struct RoleInfo; class Server_Game : public QObject { @@ -109,6 +110,7 @@ private: bool omniscient, bool withUserInfo); void storeGameInformation(); + bool canSeePrivateEventsFor(const Server_AbstractParticipant *participant, int privatePlayerId) const; signals: void sigStartGameIfReady(bool override); void gameInfoChanged(ServerInfo_Game gameInfo); @@ -188,12 +190,11 @@ public: return shareDecklistsOnLoad; } Response::ResponseCode - checkJoin(ServerInfo_User *user, const QString &_password, bool spectator, bool overrideRestrictions, bool asJudge); + checkJoin(ServerInfo_User *user, const QString &_password, const RoleInfo &roleInfo, bool overrideRestrictions); bool containsUser(const QString &userName) const; void addPlayer(Server_AbstractUserInterface *userInterface, ResponseContainer &rc, - bool spectator, - bool judge, + const RoleInfo &roleInfo, bool broadcastUpdate = true); void removeParticipant(Server_AbstractParticipant *participant, Event_Leave::LeaveReason reason); void removeArrowsRelatedToPlayer(GameEventStorage &ges, Server_AbstractPlayer *player); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp index a4c904c01..b40dae09b 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_player.cpp @@ -257,6 +257,19 @@ Server_Player::cmdDeckSelect(const Command_DeckSelect &cmd, ResponseContainer &r return Response::RespInternalError; } + // Card art rules apply to playmats as well: never broadcast a DENY-listed + // printing as a playmat. Stripping keeps the deck selectable while hiding + // the banned art from all clients. + const auto deckPlaymat = newDeck->getPlaymat(); + if (!deckPlaymat.card.name.isEmpty()) { + auto *db = game->getRoom()->getServer()->getDatabaseInterface(); + // Check the truncated values actually broadcast below; the rule columns are varchar(255). + if (db && !db->isCardArtAllowed(deckPlaymat.card.name.left(MAX_NAME_LENGTH), + deckPlaymat.card.providerId.left(MAX_NAME_LENGTH))) { + newDeck->setPlaymat({}); + } + } + delete deck; deck = newDeck; sideboardLocked = true; @@ -560,7 +573,7 @@ Server_Player::cmdNextTurn(const Command_NextTurn & /*cmd*/, ResponseContainer & return Response::RespGameNotStarted; } - if (conceded && !judge) { + if (conceded && !isJudge()) { return Response::RespContextError; } @@ -576,7 +589,7 @@ Response::ResponseCode Server_Player::cmdSetActivePhase(const Command_SetActiveP return Response::RespGameNotStarted; } - if (!judge) { + if (!isJudge()) { if (conceded) { return Response::RespContextError; } @@ -610,7 +623,7 @@ Response::ResponseCode Server_Player::cmdReverseTurn(const Command_ReverseTurn &cmd, ResponseContainer &rc, GameEventStorage &ges) { - if (!judge && conceded) { + if (!isJudge() && conceded) { return Response::RespContextError; } return Server_AbstractParticipant::cmdReverseTurn(cmd, rc, ges); @@ -631,6 +644,14 @@ Server_Player::cmdSetPlaymat(const Command_SetPlaymat &cmd, ResponseContainer &r if (rawName.length() > MAX_NAME_LENGTH || rawProviderId.length() > MAX_NAME_LENGTH) { return Response::RespInvalidData; } + // Card art rules apply to playmats as well: reject DENY-listed art, like + // cmdSetCardArtParams does. Empty clears the playmat and is always allowed. + if (!rawName.isEmpty()) { + auto *db = game->getRoom()->getServer()->getDatabaseInterface(); + if (db && !db->isCardArtAllowed(rawName, rawProviderId)) { + return Response::RespFunctionNotAllowed; + } + } PlaymatInfo playmat; playmat.card.name = rawName; playmat.card.providerId = rawProviderId; diff --git a/libcockatrice_network/libcockatrice/network/server/remote/game/server_spectator.cpp b/libcockatrice_network/libcockatrice/network/server/remote/game/server_spectator.cpp index d1c369949..164a4af5e 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/game/server_spectator.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/game/server_spectator.cpp @@ -11,7 +11,6 @@ Server_Spectator::Server_Spectator(Server_Game *_game, const ServerInfo_User &_userInfo, bool _judge, Server_AbstractUserInterface *_userInterface) - : Server_AbstractParticipant(_game, _playerId, _userInfo, _judge, _userInterface) + : Server_AbstractParticipant(_game, _playerId, _userInfo, {.isJudge = _judge, .isSpectator = true}, _userInterface) { - spectator = true; } diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server.cpp b/libcockatrice_network/libcockatrice/network/server/remote/server.cpp index 73b13e23e..8ea2cd993 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/server.cpp @@ -131,6 +131,16 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, return authState; } + if (authState == PasswordChangeRequired) { + // The credentials were right, but the account must choose a new password before it may + // use the server. Give the session an identity so it can complete that change, without + // registering a user session or announcing the user as logged in. + ServerInfo_User pendingData = databaseInterface->getUserData(name, true); + pendingData.set_address(session->getAddress().toStdString()); + session->setUserInfo(pendingData); + return authState; + } + ServerInfo_User data = databaseInterface->getUserData(name, true); data.set_address(session->getAddress().toStdString()); name = QString::fromStdString(data.name()); // Compensate for case indifference @@ -296,7 +306,9 @@ void Server::removeClient(Server_ProtocolHandler *client) QWriteLocker locker(&clientsLock); clients.removeAt(clientIndex); ServerInfo_User *data = client->getUserInfo(); - if (data) { + // A session that never completed login (for example one that still owes a password change) + // has no session id: it was never announced as joined, so it must not be announced as leaving. + if (data && data->has_session_id()) { Event_UserLeft event; event.set_name(data->name()); SessionEvent *se = Server_ProtocolHandler::prepareSessionEvent(event); @@ -311,12 +323,10 @@ void Server::removeClient(Server_ProtocolHandler *client) users.remove(QString::fromStdString(data->name())); qDebug() << "Server::removeClient: name=" << QString::fromStdString(data->name()); - if (data->has_session_id()) { - const qint64 sessionId = data->session_id(); - usersBySessionId.remove(sessionId); - emit endSession(sessionId); - qDebug() << "closed session id:" << sessionId; - } + const qint64 sessionId = data->session_id(); + usersBySessionId.remove(sessionId); + emit endSession(sessionId); + qDebug() << "closed session id:" << sessionId; } qDebug() << "Server::removeClient: removed" << (void *)client << ";" << clients.size() << "clients; " << users.size() << "users left"; @@ -330,7 +340,9 @@ QList Server::getOnlineModeratorList() const ServerInfo_User *data = client->getUserInfo(); //! \todo This line should be updated in the event there is any type of new user level created. - if (data && + // A session that still owes a forced password change has user info but no session id: + // it never joined, so it must not be reported as an online moderator either. + if (data && data->has_session_id() && (data->user_level() & ServerInfo_User::IsModerator || data->user_level() & ServerInfo_User::IsAdmin)) { results << QString::fromStdString(data->name()).simplified(); } @@ -441,7 +453,7 @@ void Server::externalRoomUserLeft(int roomId, const QString &userName) room->removeExternalUser(userName); } -void Server::externalRoomSay(int roomId, const QString &userName, const QString &message) +void Server::externalRoomSay(int roomId, const QString &userName, const QString &message, const QString &channelId) { // This function is always called from the main thread via signal/slot. QReadLocker locker(&roomsLock); @@ -451,10 +463,22 @@ void Server::externalRoomSay(int roomId, const QString &userName, const QString qDebug() << "externalRoomSay: room id=" << roomId << "not found"; return; } - room->say(userName, message, false); + if (!channelId.isEmpty() && !Server_Room::isMainChannel(channelId) && room->findChannel(channelId) == nullptr) { + // The receiver does not know this channel; say() would drop it, so do + // not log what was never delivered. + qDebug() << "externalRoomSay: unknown channel" << channelId << "for room id=" << roomId << "dropping"; + return; + } + room->say(userName, message, channelId, false); - getDatabaseInterface()->logMessage(0, userName, "ISL", message, Server_DatabaseInterface::MessageTargetIslRoom, - room->getId(), room->getName()); + if (channelId.isEmpty()) { + getDatabaseInterface()->logMessage(0, userName, "ISL", message, Server_DatabaseInterface::MessageTargetIslRoom, + room->getId(), room->getName()); + } else { + getDatabaseInterface()->logMessage(0, userName, "ISL", message, + Server_DatabaseInterface::MessageTargetRoomChannel, room->getId(), + room->getName(), channelId); + } } void Server::externalRoomRemoveMessages(int roomId, const QString &userName, int amount) diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server.h b/libcockatrice_network/libcockatrice/network/server/remote/server.h index 932a136ff..9e05750ac 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server.h @@ -87,6 +87,11 @@ public: { return QMap(); } + /** @brief True when only challenge-response logins are accepted (strict mode). */ + virtual bool requiresChallengeResponseAuth() const + { + return false; + } void addClient(Server_ProtocolHandler *player); void removeClient(Server_ProtocolHandler *player); QList getOnlineModeratorList() const; @@ -265,7 +270,7 @@ protected slots: void externalUserLeft(const QString &userName); void externalRoomUserJoined(int roomId, const ServerInfo_User &userInfo); void externalRoomUserLeft(int roomId, const QString &userName); - void externalRoomSay(int roomId, const QString &userName, const QString &message); + void externalRoomSay(int roomId, const QString &userName, const QString &message, const QString &channelId); void externalRoomRemoveMessages(int roomId, const QString &userName, int amount); void externalRoomGameListChanged(int roomId, const ServerInfo_Game &gameInfo); void diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp b/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp index 25aa415fc..c3524bb93 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.cpp @@ -84,6 +84,18 @@ void Server_AbstractUserInterface::onLogin(ResponseContainer &) { } +bool Server_AbstractUserInterface::getGameInfo(int gameId, int &roomId, int &playerId) const +{ + QMutexLocker locker(&gameListMutex); + const auto it = games.constFind(gameId); + if (it == games.constEnd()) { + return false; + } + roomId = it.value().first; + playerId = it.value().second; + return true; +} + void Server_AbstractUserInterface::playerRemovedFromGame(Server_Game *game) { qDebug() << "Server_AbstractUserInterface::playerRemovedFromGame(): gameId =" << game->getGameId(); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.h b/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.h index a19475e34..a2a4ed8c7 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_abstractuserinterface.h @@ -58,6 +58,13 @@ public: return games; } + /** + * @brief Looks up the room and player id of one game without copying the game list. + * + * @return True if the game is known; otherwise false and the outputs are untouched. + */ + bool getGameInfo(int gameId, int &roomId, int &playerId) const; + virtual void sendProtocolItem(const Response &item) = 0; virtual void sendProtocolItem(const SessionEvent &item) = 0; virtual void sendProtocolItem(const GameEventContainer &item) = 0; diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h b/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h index e0be7990b..936c48880 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_database_interface.h @@ -53,6 +53,14 @@ public: { return {}; } + virtual QString getUserPasswordData(const QString & /* user */) + { + return {}; + } + virtual bool submitPasswordVerifier(const QString & /* user */, const QString & /* passwordVerifier */) + { + return false; + } virtual QMap getBuddyList(const QString & /* name */) { return QMap(); @@ -151,7 +159,8 @@ public: MessageTargetRoom, MessageTargetGame, MessageTargetChat, - MessageTargetIslRoom + MessageTargetIslRoom, + MessageTargetRoomChannel }; virtual void logMessage(const int /* senderId */, const QString & /* senderName */, @@ -159,7 +168,8 @@ public: const QString & /* logMessage */, LogMessage_TargetType /* targetType */, const int /* targetId */, - const QString & /* targetName */) + const QString & /* targetName */, + const QString & /* channel */ = QString()) { } virtual bool checkUserIsBanned(Server_ProtocolHandler * /* session */, @@ -188,6 +198,16 @@ public: virtual void setForcePasswordChange(const QString & /* user */, bool /* force */) { } + /** + * @brief Whether the given card art may be shown (card art rules). + * + * Default allows everything; the real server overrides this with a lookup + * in {prefix}_card_art_name_rules. Empty names are always allowed (clear path). + */ + virtual bool isCardArtAllowed(const QString & /* cardName */, const QString & /* providerId */) + { + return true; + } }; #endif diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp b/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp index fbca9589a..f9cadc9aa 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.cpp @@ -1,5 +1,8 @@ #include "server_protocolhandler.h" +// Challenge-response nonces are valid for at most one minute from issuance. +static constexpr qint64 kAuthNonceLifetimeSeconds = 60; + #include "game/game_config.h" #include "game/server_abstract_participant.h" #include "game/server_game.h" @@ -13,6 +16,7 @@ #include "libcockatrice/protocol/pb/server_message.pb.h" #include "libcockatrice/protocol/pb/serverinfo_chat_message.pb.h" #include "libcockatrice/protocol/pb/serverinfo_game.pb.h" +#include "libcockatrice/protocol/pb/serverinfo_room_channel.pb.h" #include "libcockatrice/protocol/pb/session_commands.pb.h" #include "libcockatrice/protocol/pb/session_event.pb.h" #include "server_database_interface.h" @@ -40,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -59,8 +64,9 @@ Server_ProtocolHandler::Server_ProtocolHandler(Server *_server, Server_DatabaseInterface *_databaseInterface, QObject *parent) : QObject(parent), Server_AbstractUserInterface(_server), deleted(false), databaseInterface(_databaseInterface), - authState(NotLoggedIn), usingRealPassword(false), acceptsUserListChanges(false), acceptsRoomListChanges(false), - idleClientWarningSent(false), timeRunning(0), lastDataReceived(0), lastActionReceived(0) + authState(NotLoggedIn), usingRealPassword(false), passwordChangePending(false), acceptsUserListChanges(false), + acceptsRoomListChanges(false), idleClientWarningSent(false), timeRunning(0), lastDataReceived(0), + lastActionReceived(0) { connect(server, &Server::pingClockTimeout, this, &Server_ProtocolHandler::pingClockTimeout); @@ -70,6 +76,28 @@ Server_ProtocolHandler::~Server_ProtocolHandler() { } +void Server_ProtocolHandler::setAuthNonce(const QByteArray &nonce, const QString &userName) +{ + authNonce = nonce; + authNonceUser = userName; + authNonceCreated = QDateTime::currentDateTimeUtc(); +} + +bool Server_ProtocolHandler::isAuthNonceValid(const QByteArray &nonce, const QString &userName) const +{ + // secsTo is signed, so a wall-clock step backwards (NTP correction, VM resume) + // must not make the elapsed time negative and re-validate an old nonce. + const qint64 elapsed = authNonceCreated.secsTo(QDateTime::currentDateTimeUtc()); + return !authNonce.isEmpty() && authNonce == nonce && authNonceUser == userName && authNonceCreated.isValid() && + elapsed >= 0 && elapsed < kAuthNonceLifetimeSeconds; +} + +void Server_ProtocolHandler::clearAuthNonce() +{ + authNonce.clear(); + authNonceUser.clear(); +} + // This function must only be called from the thread this object lives in. // Except when the server is shutting down. // The thread must not hold any server locks when calling this (e.g. clientsLock, roomsLock). @@ -181,11 +209,14 @@ Response::ResponseCode Server_ProtocolHandler::processSessionCommandContainer(co Response::ResponseCode resp = Response::RespInvalidCommand; const SessionCommand &sc = cont.session_command(i); const int num = getPbExtension(sc); - if (num != SessionCommand::PING) { // don't log ping commands + if (num != SessionCommand::PING && logsDebugMessages()) { // don't log ping commands logDebugMessage(getSafeDebugString(sc)); } const auto commandType = static_cast(num); - if (authState == NotLoggedIn && !isPreAuthSessionCommand(commandType)) { + if (passwordChangePending && commandType != SessionCommand::PING && + commandType != SessionCommand::ACCOUNT_PASSWORD) { + resp = Response::RespFunctionNotAllowed; + } else if (authState == NotLoggedIn && !isPreAuthSessionCommand(commandType)) { resp = Response::RespLoginNeeded; } else { switch (commandType) { @@ -244,7 +275,9 @@ Response::ResponseCode Server_ProtocolHandler::processRoomCommandContainer(const Response::ResponseCode resp = Response::RespInvalidCommand; const RoomCommand &sc = cont.room_command(i); const int num = getPbExtension(sc); - logDebugMessage(getSafeDebugString(sc)); + if (logsDebugMessages()) { + logDebugMessage(getSafeDebugString(sc)); + } switch ((RoomCommand::RoomCommandType)num) { case RoomCommand::LEAVE_ROOM: resp = cmdLeaveRoom(sc.GetExtension(Command_LeaveRoom::ext), room, rc); @@ -252,6 +285,9 @@ Response::ResponseCode Server_ProtocolHandler::processRoomCommandContainer(const case RoomCommand::ROOM_SAY: resp = cmdRoomSay(sc.GetExtension(Command_RoomSay::ext), room, rc); break; + case RoomCommand::ROOM_CHANNEL_SAY: + resp = cmdRoomChannelSay(sc.GetExtension(Command_RoomChannelSay::ext), room, rc); + break; case RoomCommand::CREATE_GAME: resp = cmdCreateGame(sc.GetExtension(Command_CreateGame::ext), room, rc); break; @@ -290,14 +326,14 @@ Response::ResponseCode Server_ProtocolHandler::processGameCommandContainer(const return Response::RespLoginNeeded; } - QMap> gameMap = getGames(); - if (!gameMap.contains(cont.game_id())) { + int roomId; + int playerId; + if (!getGameInfo(cont.game_id(), roomId, playerId)) { return Response::RespNotInRoom; } - const QPair roomIdAndPlayerId = gameMap.value(cont.game_id()); QReadLocker roomsLocker(&server->roomsLock); - Server_Room *room = server->getRooms().value(roomIdAndPlayerId.first); + Server_Room *room = server->getRooms().value(roomId); if (!room) { return Response::RespNotInRoom; } @@ -307,14 +343,14 @@ Response::ResponseCode Server_ProtocolHandler::processGameCommandContainer(const if (!game) { if (room->getExternalGames().contains(cont.game_id())) { server->sendIsl_GameCommand(cont, room->getExternalGames().value(cont.game_id()).server_id(), - userInfo->session_id(), roomIdAndPlayerId.first, roomIdAndPlayerId.second); + userInfo->session_id(), roomId, playerId); return Response::RespNothing; } return Response::RespNotInRoom; } QMutexLocker gameLocker(&game->gameMutex); - auto *participant = game->getParticipants().value(roomIdAndPlayerId.second); + auto *participant = game->getParticipants().value(playerId); if (!participant) { return Response::RespNotInRoom; } @@ -327,8 +363,9 @@ Response::ResponseCode Server_ProtocolHandler::processGameCommandContainer(const Response::ResponseCode finalResponseCode = Response::RespOk; for (int i = cont.game_command_size() - 1; i >= 0; --i) { const GameCommand &sc = cont.game_command(i); - logDebugMessage(QString("game %1 player %2: ").arg(cont.game_id()).arg(roomIdAndPlayerId.second) + - getSafeDebugString(sc)); + if (logsDebugMessages()) { + logDebugMessage(QString("game %1 player %2: ").arg(cont.game_id()).arg(playerId) + getSafeDebugString(sc)); + } if (commandCountingInterval > 0) { int totalCount = 0; @@ -377,7 +414,9 @@ Response::ResponseCode Server_ProtocolHandler::processModeratorCommandContainer( Response::ResponseCode resp = Response::RespInvalidCommand; const ModeratorCommand &sc = cont.moderator_command(i); const int num = getPbExtension(sc); - logDebugMessage(getSafeDebugString(sc)); + if (logsDebugMessages()) { + logDebugMessage(getSafeDebugString(sc)); + } resp = processExtendedModeratorCommand(num, sc, rc); if (resp != Response::RespOk) { @@ -404,7 +443,9 @@ Response::ResponseCode Server_ProtocolHandler::processAdminCommandContainer(cons Response::ResponseCode resp = Response::RespInvalidCommand; const AdminCommand &sc = cont.admin_command(i); const int num = getPbExtension(sc); - logDebugMessage(getSafeDebugString(sc)); + if (logsDebugMessages()) { + logDebugMessage(getSafeDebugString(sc)); + } resp = processExtendedAdminCommand(num, sc, rc); if (resp != Response::RespOk) { @@ -431,7 +472,9 @@ Response::ResponseCode Server_ProtocolHandler::processDeveloperCommandContainer( Response::ResponseCode resp = Response::RespInvalidCommand; const DeveloperCommand &sc = cont.developer_command(i); const int num = getPbExtension(sc); - logDebugMessage(getSafeDebugString(sc)); + if (logsDebugMessages()) { + logDebugMessage(getSafeDebugString(sc)); + } resp = processExtendedDeveloperCommand(num, sc, rc); if (resp != Response::RespOk) { @@ -450,6 +493,19 @@ void Server_ProtocolHandler::processCommandContainer(const CommandContainer &con lastDataReceived = timeRunning; + // A session that still owes a forced password change may only reach the session family, + // where processSessionCommandContainer narrows it further to ping and the change itself. + // Every other family is rejected here, including a container that also carries a session + // command: the dispatch below prefers game/room over session, so testing only for the + // absence of a session command would let such a container through. + if (passwordChangePending && + (cont.game_command_size() || cont.room_command_size() || cont.moderator_command_size() || + cont.admin_command_size() || cont.developer_command_size())) { + sendResponseContainer(ResponseContainer(cont.has_cmd_id() ? cont.cmd_id() : -1), + Response::RespFunctionNotAllowed); + return; + } + ResponseContainer responseContainer(cont.has_cmd_id() ? cont.cmd_id() : -1); Response::ResponseCode finalResponseCode; @@ -563,6 +619,20 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd return Response::RespContextError; } + // In strict mode only challenge-response logins are accepted, but only for + // registered SQL accounts: guests (no DB row) and non-SQL authentication + // methods have no stored verifier to challenge against. userExists is false + // for both (it requires AuthenticationSql), so a single check covers them. + const bool isRegisteredAccount = databaseInterface != nullptr && databaseInterface->userExists(userName); + if (server->requiresChallengeResponseAuth() && isRegisteredAccount && + (!cmd.has_hashed_password() || cmd.hashed_password().rfind("$challenge$", 0) != 0)) { + auto *re = new Response_Login; + re->set_denied_reason_str("Client upgrade required"); + re->add_missing_features("challenge_response_auth"); + rc.setResponseExtension(re); + return Response::RespClientUpdateRequired; + } + // check client feature set against server feature set FeatureSet features; QMap receivedClientFeatures; @@ -643,6 +713,11 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd server->recordFailedLogin(getAddress(), userName); return Response::RespAccountNotActivated; case PasswordChangeRequired: + // The credentials were right, but the account must choose a new password first. + // Keep the session authenticated just far enough for the client to do that: + // processCommandContainer refuses everything except the change itself. + authState = PasswordRight; + passwordChangePending = true; return Response::RespPasswordChangeRequired; default: server->clearFailedLogins(getAddress(), userName); @@ -764,7 +839,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdGetGamesOfUser(const Command_G while (roomIterator.hasNext()) { Server_Room *room = roomIterator.next().value(); room->gamesLock.lockForRead(); - room->getInfo(*re->add_room_list(), false, true); + room->getInfo(*re->add_room_list(), false, true, true, userInfo); QListIterator gameIterator(room->getGamesOfUser(nameFromStdString(cmd.user_name()))); while (gameIterator.hasNext()) { re->add_game_list()->CopyFrom(gameIterator.next()); @@ -822,7 +897,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdListRooms(const Command_ListRo Event_ListRooms event; QMapIterator roomIterator(server->getRooms()); while (roomIterator.hasNext()) { - roomIterator.next().value()->getInfo(*event.add_room_list(), false); + roomIterator.next().value()->getInfo(*event.add_room_list(), false, false, true, userInfo); } rc.enqueuePreResponseItem(ServerMessage::SESSION_EVENT, prepareSessionEvent(event)); @@ -856,10 +931,8 @@ Response::ResponseCode Server_ProtocolHandler::cmdJoinRoom(const Command_JoinRoo rooms.insert(room->getId(), room); QReadLocker chatHistoryLocker(&room->historyLock); - QList chatHistory = room->getChatHistory(); - ServerInfo_ChatMessage chatMessage; - for (int i = 0; i < chatHistory.size(); ++i) { - chatMessage = chatHistory.at(i); + const QList mainChatHistory = room->getChatHistory(Server_Room::MAIN_CHANNEL_KEY); + for (const ServerInfo_ChatMessage &chatMessage : mainChatHistory) { Event_RoomSay roomChatHistory; roomChatHistory.set_message(chatMessage.sender_name() + ": " + chatMessage.message()); roomChatHistory.set_message_type(Event_RoomSay::ChatHistory); @@ -868,13 +941,31 @@ Response::ResponseCode Server_ProtocolHandler::cmdJoinRoom(const Command_JoinRoo rc.enqueuePostResponseItem(ServerMessage::ROOM_EVENT, room->prepareRoomEvent(roomChatHistory)); } + // Replay the history of every catalog channel the joining user may see. + // Public channels are replayed to everyone; restricted channels only to + // members whose privilege level meets the channel's access level. + for (const ServerInfo_RoomChannel &channel : room->getChannels()) { + if (!room->mayReadChannel(*userInfo, channel)) { + continue; + } + const QList channelHistory = room->getChatHistory(QString::fromStdString(channel.id())); + for (const ServerInfo_ChatMessage &chatMessage : channelHistory) { + Event_RoomChannelSay channelChatHistory; + channelChatHistory.set_channel_id(channel.id()); + channelChatHistory.set_message(chatMessage.sender_name() + ": " + chatMessage.message()); + channelChatHistory.set_time_of( + QDateTime::fromString(QString::fromStdString(chatMessage.time())).toMSecsSinceEpoch()); + rc.enqueuePostResponseItem(ServerMessage::ROOM_EVENT, room->prepareRoomEvent(channelChatHistory)); + } + } + Event_RoomSay joinMessageEvent; joinMessageEvent.set_message(room->getJoinMessage().toStdString()); joinMessageEvent.set_message_type(Event_RoomSay::Welcome); rc.enqueuePostResponseItem(ServerMessage::ROOM_EVENT, room->prepareRoomEvent(joinMessageEvent)); auto *re = new Response_JoinRoom; - room->getInfo(*re->mutable_room_info(), true); + room->getInfo(*re->mutable_room_info(), true, false, true, userInfo); rc.setResponseExtension(re); return Response::RespOk; @@ -959,6 +1050,36 @@ Server_ProtocolHandler::cmdRoomSay(const Command_RoomSay &cmd, Server_Room *room return Response::RespOk; } +Response::ResponseCode Server_ProtocolHandler::cmdRoomChannelSay(const Command_RoomChannelSay &cmd, + Server_Room *room, + ResponseContainer & /*rc*/) +{ + const QString channelId = QString::fromStdString(cmd.channel_id()); + const ServerInfo_RoomChannel *channel = room->findChannel(channelId); + if (channel == nullptr) { + // The client only ever offers catalog ids; a crafted or stale id is rejected. + return Response::RespInvalidCommand; + } + if (!room->mayWriteChannel(*userInfo, *channel)) { + return Response::RespFunctionNotAllowed; + } + + if (!addSaidMessageSize(static_cast(cmd.message().size()))) { + return Response::RespChatFlood; + } + QString msg = QString::fromStdString(cmd.message()); + + msg.replace(QChar('\n'), QChar(' ')); + + room->say(QString::fromStdString(userInfo->name()), msg, channelId); + + databaseInterface->logMessage( + userInfo->id(), QString::fromStdString(userInfo->name()), QString::fromStdString(userInfo->address()), msg, + Server_DatabaseInterface::MessageTargetRoomChannel, room->getId(), room->getName(), channelId); + + return Response::RespOk; +} + Response::ResponseCode Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room *room, ResponseContainer &rc) { @@ -1020,7 +1141,8 @@ Server_ProtocolHandler::cmdCreateGame(const Command_CreateGame &cmd, Server_Room auto *game = new Server_Game(config, room); - game->addPlayer(this, rc, asSpectator, asJudge, false); + RoleInfo roleInfo{.isJudge = asJudge, .isSpectator = asSpectator}; + game->addPlayer(this, rc, roleInfo, false); room->addGame(game); return Response::RespOk; diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.h b/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.h index a12706757..8ce110354 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_protocolhandler.h @@ -4,6 +4,8 @@ #include "server.h" #include "server_abstractuserinterface.h" +#include +#include #include #include #include @@ -30,6 +32,7 @@ class Command_ListRooms; class Command_JoinRoom; class Command_LeaveRoom; class Command_RoomSay; +class Command_RoomChannelSay; class Command_CreateGame; class Command_JoinGame; @@ -43,12 +46,32 @@ protected: Server_DatabaseInterface *databaseInterface; AuthenticationResult authState; bool usingRealPassword; + /** + * @brief True when the credentials were right but the account must choose a new password + * before it may use the server. Only ping, the password change and disconnecting + * are served while this is set. + */ + bool passwordChangePending; bool acceptsUserListChanges; bool acceptsRoomListChanges; bool idleClientWarningSent; + QByteArray authNonce; + QString authNonceUser; + QDateTime authNonceCreated; virtual void logDebugMessage(const QString & /* message */) { } + /** + * @brief Whether debug messages produced by this handler are actually written anywhere. + * + * Formatting a protocol message for the log is expensive, so callers only + * build the debug string when this returns true. The base handler has no + * log destination and therefore never needs one. + */ + [[nodiscard]] virtual bool logsDebugMessages() const + { + return false; + } private: QList messageSizeOverTime, messageCountOverTime, commandCountOverTime; @@ -66,6 +89,8 @@ private: Response::ResponseCode cmdListUsers(const Command_ListUsers &cmd, ResponseContainer &rc); Response::ResponseCode cmdLeaveRoom(const Command_LeaveRoom &cmd, Server_Room *room, ResponseContainer &rc); Response::ResponseCode cmdRoomSay(const Command_RoomSay &cmd, Server_Room *room, ResponseContainer &rc); + Response::ResponseCode + cmdRoomChannelSay(const Command_RoomChannelSay &cmd, Server_Room *room, ResponseContainer &rc); Response::ResponseCode cmdCreateGame(const Command_CreateGame &cmd, Server_Room *room, ResponseContainer &rc); Response::ResponseCode cmdJoinGame(const Command_JoinGame &cmd, Server_Room *room, ResponseContainer &rc); @@ -121,6 +146,13 @@ public: return databaseInterface; } + /** @brief Store a fresh challenge nonce bound to @p userName for the next challenge-response login attempt. */ + void setAuthNonce(const QByteArray &nonce, const QString &userName); + /** @brief True if nonce matches the pending one, was issued for @p userName, and is less than 60 seconds old. */ + bool isAuthNonceValid(const QByteArray &nonce, const QString &userName) const; + /** @brief Invalidate the pending nonce (single-use). */ + void clearAuthNonce(); + int getLastCommandTime() const { return timeRunning - lastDataReceived; diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_room.cpp b/libcockatrice_network/libcockatrice/network/server/remote/server_room.cpp index 614de8d63..78ef2b289 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_room.cpp +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_room.cpp @@ -1,5 +1,6 @@ #include "server_room.h" +#include "game/role_info.h" #include "game/server_game.h" #include "libcockatrice/protocol/pb/response.pb.h" #include "libcockatrice/protocol/pb/room_event.pb.h" @@ -26,16 +27,20 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include #include #include +const QString Server_Room::MAIN_CHANNEL_KEY = QStringLiteral("__main__"); + Server_Room::Server_Room(int _id, int _chatHistorySize, const QString &_name, @@ -45,10 +50,11 @@ Server_Room::Server_Room(int _id, bool _autoJoin, const QString &_joinMessage, const QStringList &_gameTypes, - Server *parent) + Server *parent, + const QList &_channels) : QObject(parent), id(_id), chatHistorySize(_chatHistorySize), name(_name), description(_description), permissionLevel(_permissionLevel), privilegeLevel(_privilegeLevel), autoJoin(_autoJoin), - joinMessage(_joinMessage), gameTypes(_gameTypes), gamesLock(QReadWriteLock::Recursive) + joinMessage(_joinMessage), gameTypes(_gameTypes), channels(_channels), gamesLock(QReadWriteLock::Recursive) { connect( this, &Server_Room::gameListChanged, this, [this](auto gameInfo) { broadcastGameListUpdate(gameInfo); }, @@ -102,8 +108,11 @@ Server *Server_Room::getServer() const return static_cast(parent()); } -const ServerInfo_Room & -Server_Room::getInfo(ServerInfo_Room &result, bool complete, bool showGameTypes, bool includeExternalData) const +const ServerInfo_Room &Server_Room::getInfo(ServerInfo_Room &result, + bool complete, + bool showGameTypes, + bool includeExternalData, + const ServerInfo_User *forUser) const { result.set_room_id(id); result.set_name(name.toStdString()); @@ -152,9 +161,73 @@ Server_Room::getInfo(ServerInfo_Room &result, bool complete, bool showGameTypes, } } + for (const ServerInfo_RoomChannel &channel : channels) { + if (forUser != nullptr && !mayReadChannel(*forUser, channel)) { + continue; + } + result.add_channel_list()->CopyFrom(channel); + } + return result; } +bool Server_Room::isMainChannel(const QString &channelId) +{ + return channelId.isEmpty() || channelId == MAIN_CHANNEL_KEY; +} + +const ServerInfo_RoomChannel *Server_Room::findChannel(const QString &channelId) const +{ + for (const ServerInfo_RoomChannel &channel : channels) { + if (QString::fromStdString(channel.id()) == channelId) { + return &channel; + } + } + return nullptr; +} + +bool Server_Room::hasChannelPrivilege(const ServerInfo_User &userInfo, + ServerInfo_RoomChannel::AccessLevel requiredLevel) +{ + // User flags are independent bits, not a ladder: holding IsDeveloper does + // not imply IsModerator or IsAdmin. The database grants admins the admin + // and the moderator bit together, so admins still reach moderator (and + // judge) channels; moderators likewise reach judge channels. + const uint32_t userLevel = userInfo.user_level(); + switch (requiredLevel) { + case ServerInfo_RoomChannel::Public: + return true; + case ServerInfo_RoomChannel::Registered: + return userLevel & ServerInfo_User::IsRegistered; + case ServerInfo_RoomChannel::Judge: + return userLevel & (ServerInfo_User::IsJudge | ServerInfo_User::IsModerator); + case ServerInfo_RoomChannel::Moderator: + return userLevel & ServerInfo_User::IsModerator; + case ServerInfo_RoomChannel::Admin: + return userLevel & ServerInfo_User::IsAdmin; + case ServerInfo_RoomChannel::Developer: + return userLevel & (ServerInfo_User::IsDeveloper | ServerInfo_User::IsAdmin); + default: + return false; + } +} + +bool Server_Room::mayReadChannel(const ServerInfo_User &userInfo, const ServerInfo_RoomChannel &channel) +{ + return hasChannelPrivilege(userInfo, channel.access_level()); +} + +bool Server_Room::mayWriteChannel(const ServerInfo_User &userInfo, const ServerInfo_RoomChannel &channel) +{ + const auto writeLevel = channel.has_write_access_level() ? channel.write_access_level() : channel.access_level(); + return mayReadChannel(userInfo, channel) && hasChannelPrivilege(userInfo, writeLevel); +} + +QList Server_Room::getChatHistory(const QString &channelId) const +{ + return channelChatHistory.value(isMainChannel(channelId) ? MAIN_CHANNEL_KEY : channelId); +} + RoomEvent *Server_Room::prepareRoomEvent(const ::google::protobuf::Message &roomEvent) { auto *event = new RoomEvent; @@ -300,22 +373,45 @@ Response::ResponseCode Server_Room::processJoinGameCommand(const Command_JoinGam QMutexLocker gameLocker(&game->gameMutex); - Response::ResponseCode result = - game->checkJoin(userInterface->getUserInfo(), QString::fromStdString(cmd.password()), cmd.spectator(), - cmd.override_restrictions(), cmd.join_as_judge()); + RoleInfo roleInfo{.isJudge = cmd.join_as_judge(), .isSpectator = cmd.spectator()}; + Response::ResponseCode result = game->checkJoin( + userInterface->getUserInfo(), QString::fromStdString(cmd.password()), roleInfo, cmd.override_restrictions()); if (result == Response::RespOk) { - game->addPlayer(userInterface, rc, cmd.spectator(), cmd.join_as_judge()); + game->addPlayer(userInterface, rc, roleInfo); } return result; } -void Server_Room::say(const QString &userName, const QString &userMessage, bool sendToIsl) +void Server_Room::say(const QString &userName, const QString &userMessage, const QString &channelId, bool sendToIsl) { - Event_RoomSay event; - event.set_name(userName.toStdString()); - event.set_message(userMessage.toStdString()); - sendRoomEvent(prepareRoomEvent(event), sendToIsl); + const bool isMain = isMainChannel(channelId); + ServerInfo_RoomChannel::AccessLevel accessLevel = ServerInfo_RoomChannel::Public; + if (!isMain) { + const ServerInfo_RoomChannel *channel = findChannel(channelId); + if (channel == nullptr) { + // The channel was not whitelisted against the catalog; drop the message. + return; + } + accessLevel = channel->access_level(); + // V1 does not relay restricted channels across ISL (plan N5). + if (accessLevel != ServerInfo_RoomChannel::Public) { + sendToIsl = false; + } + } + + if (isMain) { + Event_RoomSay event; + event.set_name(userName.toStdString()); + event.set_message(userMessage.toStdString()); + sendRoomEvent(prepareRoomEvent(event), sendToIsl, accessLevel); + } else { + Event_RoomChannelSay event; + event.set_channel_id(channelId.toStdString()); + event.set_name(userName.toStdString()); + event.set_message(userMessage.toStdString()); + sendRoomEvent(prepareRoomEvent(event), sendToIsl, accessLevel); + } if (chatHistorySize != 0) { ServerInfo_ChatMessage chatMessage; @@ -324,13 +420,18 @@ void Server_Room::say(const QString &userName, const QString &userMessage, bool chatMessage.set_time(dateTimeString.toStdString()); chatMessage.set_sender_name(userName.toStdString()); chatMessage.set_message(userMessage.simplified().toStdString()); - - historyLock.lockForWrite(); - if (chatHistory.size() >= chatHistorySize) { - chatHistory.removeAt(0); + if (!isMain) { + chatMessage.set_channel(channelId.toStdString()); } - chatHistory.push_back(std::move(chatMessage)); + const QString storageKey = isMain ? MAIN_CHANNEL_KEY : channelId; + historyLock.lockForWrite(); + QList &history = channelChatHistory[storageKey]; + if (history.size() >= chatHistorySize) { + history.removeAt(0); + } + + history.push_back(std::move(chatMessage)); historyLock.unlock(); } } @@ -344,26 +445,34 @@ void Server_Room::removeSaidMessages(const QString &userName, int amount, bool s sendRoomEvent(prepareRoomEvent(event), sendToIsl); if (chatHistorySize != 0) { - int removed = 0; historyLock.lockForWrite(); - // redact [amount] of the most recent messages from this user from history - for (auto message = chatHistory.rbegin(); message != chatHistory.rend() && removed != amount; ++message) { - if (message->sender_name() == stdStringUserName) { - message->clear_message(); - ++removed; + // redact [amount] of the most recent messages from this user from every channel history, + // so removed content does not resurface for late joiners on any stream + for (auto historyIt = channelChatHistory.begin(); historyIt != channelChatHistory.end(); ++historyIt) { + QList &history = historyIt.value(); + int removed = 0; + for (auto message = history.rbegin(); message != history.rend() && removed != amount; ++message) { + if (message->sender_name() == stdStringUserName) { + message->clear_message(); + ++removed; + } } } historyLock.unlock(); } } -void Server_Room::sendRoomEvent(RoomEvent *event, bool sendToIsl) +void Server_Room::sendRoomEvent(RoomEvent *event, bool sendToIsl, ServerInfo_RoomChannel::AccessLevel accessLevel) { usersLock.lockForRead(); { QMapIterator userIterator(users); while (userIterator.hasNext()) { - userIterator.next().value()->sendProtocolItem(*event); + Server_ProtocolHandler *user = userIterator.next().value(); + if (!hasChannelPrivilege(*user->getUserInfo(), accessLevel)) { + continue; + } + user->sendProtocolItem(*event); } } usersLock.unlock(); diff --git a/libcockatrice_network/libcockatrice/network/server/remote/server_room.h b/libcockatrice_network/libcockatrice/network/server/remote/server_room.h index 5933894d5..b6e6f269a 100644 --- a/libcockatrice_network/libcockatrice/network/server/remote/server_room.h +++ b/libcockatrice_network/libcockatrice/network/server/remote/server_room.h @@ -9,6 +9,7 @@ #include #include #include +#include #include class Server_ProtocolHandler; @@ -47,15 +48,18 @@ private: bool autoJoin; QString joinMessage; QStringList gameTypes; + QList channels; QMap games; QMap externalGames; QMap users; QMap externalUsers; - QList chatHistory; + QMap> channelChatHistory; private slots: void broadcastGameListUpdate(const ServerInfo_Game &gameInfo, bool sendToIsl = true); public: + static const QString MAIN_CHANNEL_KEY; + mutable QReadWriteLock usersLock; mutable QReadWriteLock gamesLock; mutable QReadWriteLock historyLock; @@ -68,7 +72,8 @@ public: bool _autoJoin, const QString &_joinMessage, const QStringList &_gameTypes, - Server *parent); + Server *parent, + const QList &_channels = {}); ~Server_Room() override; int getId() const { @@ -103,6 +108,35 @@ public: { return gameTypes; } + const QList &getChannels() const + { + return channels; + } + /** + * @brief Looks up a catalog channel by its stable id. + * @return The matching channel, or nullptr when the id is not in the catalog. + */ + const ServerInfo_RoomChannel *findChannel(const QString &channelId) const; + /** @brief True when @p channelId refers to the implicit Main channel. */ + static bool isMainChannel(const QString &channelId); + /** + * @brief True when the flags in @p userInfo satisfy @p requiredLevel. + * + * User flags are independent bits, not a ladder: IsDeveloper does not + * imply IsModerator or IsAdmin, so developer-only users cannot read or + * post in moderator/admin channels. Admins hold the moderator bit + * alongside the admin bit and moderators are admitted to judge channels. + */ + static bool hasChannelPrivilege(const ServerInfo_User &userInfo, ServerInfo_RoomChannel::AccessLevel requiredLevel); + /** @brief True when @p userInfo may read @p channel. */ + static bool mayReadChannel(const ServerInfo_User &userInfo, const ServerInfo_RoomChannel &channel); + /** + * @brief True when @p userInfo may write to @p channel. + * + * Writing requires the channel's write level, or — when no explicit write + * level is configured — the same level as reading. + */ + static bool mayWriteChannel(const ServerInfo_User &userInfo, const ServerInfo_RoomChannel &channel); const QMap &getGames() const { return games; @@ -112,14 +146,14 @@ public: return externalGames; } Server *getServer() const; - const ServerInfo_Room & - getInfo(ServerInfo_Room &result, bool complete, bool showGameTypes = false, bool includeExternalData = true) const; + const ServerInfo_Room &getInfo(ServerInfo_Room &result, + bool complete, + bool showGameTypes = false, + bool includeExternalData = true, + const ServerInfo_User *forUser = nullptr) const; int getGamesCreatedByUser(const QString &name) const; QList getGamesOfUser(const QString &name) const; - QList &getChatHistory() - { - return chatHistory; - } + QList getChatHistory(const QString &channelId) const; void addClient(Server_ProtocolHandler *client); void removeClient(Server_ProtocolHandler *client); @@ -136,13 +170,15 @@ public: ResponseContainer &rc, Server_AbstractUserInterface *userInterface); - void say(const QString &userName, const QString &s, bool sendToIsl = true); + void say(const QString &userName, const QString &s, const QString &channelId = QString(), bool sendToIsl = true); void removeSaidMessages(const QString &userName, int amount, bool sendToIsl = true); void addGame(Server_Game *game); void removeGame(Server_Game *game); - void sendRoomEvent(RoomEvent *event, bool sendToIsl = true); + void sendRoomEvent(RoomEvent *event, + bool sendToIsl = true, + ServerInfo_RoomChannel::AccessLevel accessLevel = ServerInfo_RoomChannel::Public); RoomEvent *prepareRoomEvent(const ::google::protobuf::Message &roomEvent); }; diff --git a/libcockatrice_protocol/libcockatrice/protocol/featureset.cpp b/libcockatrice_protocol/libcockatrice/protocol/featureset.cpp index 0f8fd4210..b0d67c00d 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/featureset.cpp +++ b/libcockatrice_protocol/libcockatrice/protocol/featureset.cpp @@ -26,7 +26,8 @@ void FeatureSet::initalizeFeatureList(QMap &_featureList) _featureList.insert("idle_client", false); _featureList.insert("forgot_password", false); _featureList.insert("websocket", false); - // featureList.insert("hashed_password_login", false); + _featureList.insert("hashed_password_login", false); + _featureList.insert("challenge_response_auth", false); // These are temp to force users onto a newer client _featureList.insert("2.7.0_min_version", false); _featureList.insert("2.8.0_min_version", false); diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt b/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt index 4e4f0e190..059e83096 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/CMakeLists.txt @@ -117,6 +117,7 @@ set(PROTO_FILES event_reveal_cards.proto event_reverse_turn.proto event_roll_die.proto + event_room_channel_say.proto event_room_say.proto event_server_complete_list.proto event_server_identification.proto @@ -173,6 +174,8 @@ set(PROTO_FILES response_report_user_info.proto response_moderator_last_logins.proto response_remove_user_avatar.proto + response_remove_user_banner.proto + response_clear_blacklisted_banners.proto response_reset_user_password.proto response_user_alts.proto response_user_sessions.proto @@ -201,6 +204,7 @@ set(PROTO_FILES serverinfo_replay_match.proto serverinfo_report.proto serverinfo_room.proto + serverinfo_room_channel.proto serverinfo_user.proto serverinfo_user_alt.proto serverinfo_user_session.proto diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/event_room_channel_say.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/event_room_channel_say.proto new file mode 100644 index 000000000..c8de1a799 --- /dev/null +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/event_room_channel_say.proto @@ -0,0 +1,12 @@ +syntax = "proto2"; +import "room_event.proto"; + +message Event_RoomChannelSay { + extend RoomEvent { + optional Event_RoomChannelSay ext = 1005; // next free RoomEvent extension + } + optional string channel_id = 1; + optional string name = 2; + optional string message = 3; + optional uint64 time_of = 4; +} diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/event_server_identification.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/event_server_identification.proto index 987ab20d1..1f1775063 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/event_server_identification.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/event_server_identification.proto @@ -13,4 +13,10 @@ message Event_ServerIdentification { optional string server_version = 2; optional uint32 protocol_version = 3; optional ServerOptions server_options = 4 [default = NoOptions]; + // Advertises challenge-response authentication separately from server_options. + // server_options must stay exactly SupportsPasswordHash on the wire: it is a + // proto2 enum (closed), so already-released clients compiled without any new + // enumerator parse an unknown value as NoOptions and fall back to sending + // the plaintext password. New clients check this field instead. + optional bool supports_challenge_response = 5; } diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/moderator_commands.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/moderator_commands.proto index 5298d3dad..04cb31d98 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/moderator_commands.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/moderator_commands.proto @@ -20,6 +20,8 @@ message ModeratorCommand { GET_MODERATOR_LAST_LOGINS = 1015; RESET_USER_PASSWORD = 1016; REMOVE_USER_AVATAR = 1017; + REMOVE_USER_BANNER = 1018; + CLEAR_BLACKLISTED_BANNERS = 1019; REPORT_LIST = 1200; REPORT_ASSIGN = 1201; REPORT_RESOLVE = 1202; @@ -85,14 +87,16 @@ message Command_ViewLogHistory { extend DeveloperCommand { optional Command_ViewLogHistory dev_ext = 1001; } - optional string user_name = 1; // user that created message - optional string ip_address = 2; // ip address of user that created message - optional string game_name = 3; // client id of user that created the message - optional string game_id = 4; // game number the message was sent to - optional string message = 5; // raw message that was sent - repeated string log_location = 6; // destination of message (ex: main room, game room, private chat) - required uint32 date_range = 7; // the length of time (in minutes) to look back for - optional uint32 maximum_results = 8; // the maximum number of query results + optional string user_name = 1; // user that created message + optional string ip_address = 2; // ip address of user that created message + optional string game_name = 3; // client id of user that created the message + optional string game_id = 4; // game number the message was sent to + optional string message = 5; // raw message that was sent + repeated string log_location = 6; // destination of message (ex: main room, game room, private chat) + required uint32 date_range = 7; // the length of time (in minutes) to look back for + optional uint32 maximum_results = 8; // the maximum number of query results (page size when paginating) + optional uint32 offset = 9 [default = 0]; // rows to skip for pagination, ORDER BY log_time DESC + optional string before_time = 10; // page those rows against this `log_time` snapshot (newest first page row) } message Command_GrantReplayAccess { @@ -179,3 +183,16 @@ message Command_RemoveUserAvatar { } optional string user_name = 1; } + +message Command_RemoveUserBanner { + extend ModeratorCommand { + optional Command_RemoveUserBanner ext = 1018; + } + optional string user_name = 1; +} + +message Command_ClearBlacklistedBanners { + extend ModeratorCommand { + optional Command_ClearBlacklistedBanners ext = 1019; + } +} diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/response_clear_blacklisted_banners.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/response_clear_blacklisted_banners.proto new file mode 100644 index 000000000..86cbc92f3 --- /dev/null +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/response_clear_blacklisted_banners.proto @@ -0,0 +1,9 @@ +syntax = "proto2"; +import "response.proto"; + +message Response_ClearBlacklistedBanners { + extend Response { + optional Response_ClearBlacklistedBanners ext = 1222; + } + optional uint32 cleared_count = 1 [default = 0]; +} diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/response_password_salt.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/response_password_salt.proto index 3fc228530..aeb3eade2 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/response_password_salt.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/response_password_salt.proto @@ -5,5 +5,23 @@ message Response_PasswordSalt { extend Response { optional Response_PasswordSalt ext = 1017; } + // Salt for the legacy password hash. Pre-challenge clients derive and send + // that hash from it. For migrated accounts holding a fresh legacy backup + // this carries the backup's salt so those clients can still log in; clients + // that speak challenge-response must use scrypt_salt instead when present. optional string password_salt = 1; + // scrypt cost parameters for the verifier. Absent/zero for legacy accounts. + optional int32 n = 2; + optional int32 r = 3; + optional int32 p = 4; + // Server-generated challenge. When present the client must authenticate + // with a challenge-response instead of transmitting the password hash. + optional bytes nonce = 5; + // True when the account still uses the legacy password format and should + // be migrated to the scrypt format after a successful login. + optional bool needs_migration = 6; + // Salt of the stored scrypt verifier. Present for migrated accounts; + // challenge-response clients derive the verifier from this rather than + // from password_salt, which may carry a legacy backup salt. + optional string scrypt_salt = 7; } diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/response_remove_user_banner.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/response_remove_user_banner.proto new file mode 100644 index 000000000..bfad7333c --- /dev/null +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/response_remove_user_banner.proto @@ -0,0 +1,11 @@ +syntax = "proto2"; +import "response.proto"; + +message Response_RemoveUserBanner { + extend Response { + optional Response_RemoveUserBanner ext = 1221; + } + optional string user_name = 1; + // false when the user exists but had no banner card set, so nothing was cleared + optional bool had_banner = 2 [default = true]; +} diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/response_viewlog_history.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/response_viewlog_history.proto index 2ec0aff28..1041bca74 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/response_viewlog_history.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/response_viewlog_history.proto @@ -7,4 +7,5 @@ message Response_ViewLogHistory { optional Response_ViewLogHistory ext = 1015; } repeated ServerInfo_ChatMessage log_message = 1; + optional bool has_more = 2 [default = false]; // true when more rows exist past this page } diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/room_commands.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/room_commands.proto index a8c90ec6c..9d9c6d277 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/room_commands.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/room_commands.proto @@ -5,6 +5,7 @@ message RoomCommand { ROOM_SAY = 1001; CREATE_GAME = 1002; JOIN_GAME = 1003; + ROOM_CHANNEL_SAY = 1004; } extensions 100 to max; } @@ -22,6 +23,14 @@ message Command_RoomSay { optional string message = 1; } +message Command_RoomChannelSay { + extend RoomCommand { + optional Command_RoomChannelSay ext = 1004; + } + optional string channel_id = 1; + optional string message = 2; +} + // Create a new game in the room message Command_CreateGame { extend RoomCommand { diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/room_event.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/room_event.proto index 9fa70e7e1..ec6c4f636 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/room_event.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/room_event.proto @@ -6,6 +6,7 @@ message RoomEvent { ROOM_SAY = 1002; LIST_GAMES = 1003; REMOVE_MESSAGES = 1004; + ROOM_CHANNEL_SAY = 1005; } optional sint32 room_id = 1; extensions 100 to max; diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_chat_message.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_chat_message.proto index d47ec4fc7..b6d35a32d 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_chat_message.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_chat_message.proto @@ -13,4 +13,5 @@ message ServerInfo_ChatMessage { optional string target_type = 6; // target type (room,game,chat) optional string target_id = 7; // id of target optional string target_name = 8; // name of target + optional string channel = 9; // channel id, for room_channel targets } diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_room.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_room.proto index c1c2e363b..8e99de48f 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_room.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_room.proto @@ -2,6 +2,7 @@ syntax = "proto2"; import "serverinfo_game.proto"; import "serverinfo_user.proto"; import "serverinfo_gametype.proto"; +import "serverinfo_room_channel.proto"; message ServerInfo_Room { optional sint32 room_id = 1; @@ -15,4 +16,5 @@ message ServerInfo_Room { repeated ServerInfo_GameType gametype_list = 9; optional string permissionlevel = 10; optional string privilegelevel = 11; + repeated ServerInfo_RoomChannel channel_list = 12; } diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_room_channel.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_room_channel.proto new file mode 100644 index 000000000..0f3a9ccad --- /dev/null +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/serverinfo_room_channel.proto @@ -0,0 +1,25 @@ +syntax = "proto2"; + +message ServerInfo_RoomChannel { + enum AccessLevel { + // Required user flag set, evaluated per level (not as a ladder): + // Registered needs IsRegistered; Judge needs IsJudge or IsModerator; + // Moderator needs IsModerator (admins hold it alongside IsAdmin); + // Admin needs IsAdmin; Developer needs IsDeveloper or IsAdmin. + Public = 0; + Registered = 1; + Judge = 2; + Moderator = 3; + Admin = 4; + Developer = 5; + } + optional string id = 1; // stable ASCII id, e.g. "deck-discussion" + optional string display_name = 2; // server-provided, localizable only server-side + optional AccessLevel access_level = 3 [default = Public]; // minimum privilege level required to read the channel + // Minimum privilege level required to write to the channel. When unset, writing requires the same level as + // reading. This allows read-mostly channels such as a developer news feed: readable by everyone, writable + // only by developers. + optional AccessLevel write_access_level = 4; + // Static banner shown at the top of the channel, analogous to the room join message on Main. Empty = none. + optional string banner_message = 5; +} diff --git a/libcockatrice_protocol/libcockatrice/protocol/pb/session_commands.proto b/libcockatrice_protocol/libcockatrice/protocol/pb/session_commands.proto index 4b7fe9c85..10aefe7b5 100644 --- a/libcockatrice_protocol/libcockatrice/protocol/pb/session_commands.proto +++ b/libcockatrice_protocol/libcockatrice/protocol/pb/session_commands.proto @@ -36,6 +36,7 @@ message SessionCommand { DECK_DOWNLOAD_PUBLIC = 1031; DECK_SHARE_LIST_MINE = 1032; DECK_SHARE_REMOVE = 1033; + SUBMIT_PASSWORD_VERIFIER = 1034; REPLAY_LIST = 1100; REPLAY_DOWNLOAD = 1101; REPLAY_MODIFY_MATCH = 1102; @@ -231,3 +232,14 @@ message Command_SetCardArtParams { optional double vertical_offset = 5; optional double zoom = 6; } + +// Client uploads the new password verifier to migrate a legacy account +// after a successful challenge-response login. Idempotent; only applies +// to accounts still using the legacy password format. +message Command_SubmitPasswordVerifier { + extend SessionCommand { + optional Command_SubmitPasswordVerifier ext = 1034; + } + // Full verifier string to store, e.g. "$scrypt$32768$8$1$$" + required string password_verifier = 1; +} diff --git a/libcockatrice_rng/CMakeLists.txt b/libcockatrice_rng/CMakeLists.txt index 6128e3f6e..77165122c 100644 --- a/libcockatrice_rng/CMakeLists.txt +++ b/libcockatrice_rng/CMakeLists.txt @@ -1,13 +1,7 @@ -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - set(HEADERS libcockatrice/rng/rng_abstract.h libcockatrice/rng/rng_sfmt.h libcockatrice/rng/sfmt/SFMT.h) -qt6_wrap_cpp(MOC_SOURCES ${HEADERS}) - add_library( - libcockatrice_rng STATIC ${MOC_SOURCES} libcockatrice/rng/rng_abstract.cpp libcockatrice/rng/rng_sfmt.cpp + libcockatrice_rng STATIC ${HEADERS} libcockatrice/rng/rng_abstract.cpp libcockatrice/rng/rng_sfmt.cpp libcockatrice/rng/sfmt/SFMT.c ) diff --git a/libcockatrice_rng/libcockatrice/rng/sfmt/SFMT.h b/libcockatrice_rng/libcockatrice/rng/sfmt/SFMT.h index 34d9e746f..df114cf9b 100644 --- a/libcockatrice_rng/libcockatrice/rng/sfmt/SFMT.h +++ b/libcockatrice_rng/libcockatrice/rng/sfmt/SFMT.h @@ -113,6 +113,14 @@ union W128_T { /** 128-bit data type */ typedef union W128_T w128_t; +#if defined(__AVX2__) +/* The AVX2 state below names __m256i (and __m512i under AVX-512VL). The SIMD header + above is only pulled in for HAVE_SSE2, so a build that has __AVX2__ from the compiler + (e.g. -march=native) but not HAVE_SSE2 from the build system would otherwise not have + those types declared. */ + #include +#endif + /** * SFMT internal state */ diff --git a/libcockatrice_settings/CMakeLists.txt b/libcockatrice_settings/CMakeLists.txt index 5a2fa42a9..a3b9a68db 100644 --- a/libcockatrice_settings/CMakeLists.txt +++ b/libcockatrice_settings/CMakeLists.txt @@ -1,7 +1,3 @@ -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - set(HEADERS libcockatrice/settings/appearance_settings.h libcockatrice/settings/cache_storage_settings.h @@ -31,11 +27,9 @@ set(HEADERS libcockatrice/settings/visual_deck_storage_settings.h ) -qt6_wrap_cpp(MOC_SOURCES ${HEADERS}) - add_library( libcockatrice_settings STATIC - ${MOC_SOURCES} + ${HEADERS} libcockatrice/settings/appearance_settings.cpp libcockatrice/settings/cache_storage_settings.cpp libcockatrice/settings/card_database_settings.cpp diff --git a/libcockatrice_settings/libcockatrice/settings/chat_settings.cpp b/libcockatrice_settings/libcockatrice/settings/chat_settings.cpp index d9ae0ddac..10567f2ff 100644 --- a/libcockatrice_settings/libcockatrice/settings/chat_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/chat_settings.cpp @@ -31,6 +31,16 @@ QString ChatSettings::getChatHighlightColor() const return getValue("highlightColor", QString(), QString(), "A6120D").toString(); } +QString ChatSettings::getChannelUnreadColor() const +{ + return getValue("channelUnreadColor", QString(), QString(), "1A7B00").toString(); +} + +QString ChatSettings::getChannelMentionColor() const +{ + return getValue("channelMentionColor", QString(), QString(), "A6120D").toString(); +} + bool ChatSettings::getChatMentionForeground() const { return getValue("mentionForeground", QString(), QString(), true).toBool(); @@ -107,6 +117,16 @@ void ChatSettings::setChatHighlightColor(const QString &_chatHighlightColor) setValue(_chatHighlightColor, "highlightColor"); } +void ChatSettings::setChannelUnreadColor(const QString &_channelUnreadColor) +{ + setValue(_channelUnreadColor, "channelUnreadColor"); +} + +void ChatSettings::setChannelMentionColor(const QString &_channelMentionColor) +{ + setValue(_channelMentionColor, "channelMentionColor"); +} + void ChatSettings::setChatMentionForeground(bool _chatMentionForeground) { setValue(_chatMentionForeground, "mentionForeground"); diff --git a/libcockatrice_settings/libcockatrice/settings/chat_settings.h b/libcockatrice_settings/libcockatrice/settings/chat_settings.h index e00da4527..d107dfb67 100644 --- a/libcockatrice_settings/libcockatrice/settings/chat_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/chat_settings.h @@ -19,6 +19,8 @@ public: [[nodiscard]] bool getChatMentionCompleter() const override; [[nodiscard]] QString getChatMentionColor() const override; [[nodiscard]] QString getChatHighlightColor() const override; + [[nodiscard]] QString getChannelUnreadColor() const override; + [[nodiscard]] QString getChannelMentionColor() const override; [[nodiscard]] bool getChatMentionForeground() const override; [[nodiscard]] bool getChatHighlightForeground() const override; [[nodiscard]] bool getIgnoreUnregisteredUsers() const override; @@ -35,6 +37,8 @@ public: void setChatMentionCompleter(bool _chatMentionCompleter); void setChatMentionColor(const QString &_chatMentionColor); void setChatHighlightColor(const QString &_chatHighlightColor); + void setChannelUnreadColor(const QString &_channelUnreadColor); + void setChannelMentionColor(const QString &_channelMentionColor); void setChatMentionForeground(bool _chatMentionForeground); void setChatHighlightForeground(bool _chatHighlightForeground); void setIgnoreUnregisteredUsers(bool _ignoreUnregisteredUsers); diff --git a/libcockatrice_settings/libcockatrice/settings/debug_settings.cpp b/libcockatrice_settings/libcockatrice/settings/debug_settings.cpp index c36e7187e..709155b1c 100644 --- a/libcockatrice_settings/libcockatrice/settings/debug_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/debug_settings.cpp @@ -34,4 +34,4 @@ int DebugSettings::getLocalGamePlayerCount() const QString DebugSettings::getDeckPathForPlayer(const QString &playerName) const { return getValue(playerName, "localgame", "deck").toString(); -} \ No newline at end of file +} diff --git a/libcockatrice_settings/libcockatrice/settings/personal_settings.cpp b/libcockatrice_settings/libcockatrice/settings/personal_settings.cpp index 682144485..79d06ddd4 100644 --- a/libcockatrice_settings/libcockatrice/settings/personal_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/personal_settings.cpp @@ -51,3 +51,24 @@ void PersonalSettings::setSeenTips(const QList &_seenTips) } setValue(QVariant::fromValue(storedTipList), "seenTips", "tipOfDay"); } + +PersonalSettings::CrashReportingMode PersonalSettings::getCrashReportingMode() const +{ + const int raw = getValue("crashReportingMode", QString(), QString(), 0).toInt(); + switch (raw) { + case 1: + return CrashReportingMode::AlwaysSend; + case 2: + return CrashReportingMode::NeverSend; + case 0: + default: + // Clamp corrupt ini values back to Ask; the QSettings key string is unchanged (no migration). + return CrashReportingMode::Ask; + } +} + +void PersonalSettings::setCrashReportingMode(CrashReportingMode _crashReportingMode) +{ + setValue(static_cast(_crashReportingMode), "crashReportingMode"); + emit crashReportingModeChanged(_crashReportingMode); +} diff --git a/libcockatrice_settings/libcockatrice/settings/personal_settings.h b/libcockatrice_settings/libcockatrice/settings/personal_settings.h index 0819af41b..0cd51cdd0 100644 --- a/libcockatrice_settings/libcockatrice/settings/personal_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/personal_settings.h @@ -24,8 +24,19 @@ public: void setShowTipsOnStartup(bool _showTipsOnStartup); void setSeenTips(const QList &_seenTips); + enum class CrashReportingMode + { + Ask = 0, ///< Show the crash-report dialog after an unclean exit. + AlwaysSend = 1, ///< Upload crash reports without asking. + NeverSend = 2 ///< Never upload; stay silent. + }; + + [[nodiscard]] CrashReportingMode getCrashReportingMode() const; + void setCrashReportingMode(CrashReportingMode _crashReportingMode); + signals: void langChanged(); + void crashReportingModeChanged(CrashReportingMode mode); public: explicit PersonalSettings(const QString &settingPath, QObject *parent = nullptr); diff --git a/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp b/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp index c5c4b2592..38b87b97c 100644 --- a/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp @@ -5,16 +5,118 @@ #include #include #include -#include +#include +#include #include -#include +#include +#include #include -class QObject; +namespace +{ +constexpr auto SERVER_GROUP = "server"; +constexpr auto SERVER_DETAILS_GROUP = "server_details"; +constexpr auto SERVERS_ARRAY = "servers"; + +QString legacyKey(const char *field, int index) +{ + return QStringLiteral("%1/%2%3") + .arg(QString::fromLatin1(SERVER_DETAILS_GROUP), QString::fromLatin1(field)) + .arg(index); +} + +//! Expects \a settings to be positioned on the server group. +QList readServerArray(QSettings &settings) +{ + QList servers; + + settings.beginGroup(SERVER_DETAILS_GROUP); + const int size = settings.beginReadArray(SERVERS_ARRAY); + servers.reserve(size); + for (int i = 0; i < size; ++i) { + settings.setArrayIndex(i); + ServersSettings::ServerEntry entry; + entry.saveName = settings.value("saveName").toString(); + entry.host = settings.value("server").toString(); + entry.port = settings.value("port").toString(); + entry.username = settings.value("username").toString(); + entry.password = settings.value("password").toString(); + entry.savePassword = settings.value("savePassword").toBool(); + entry.site = settings.value("site").toString(); + servers.append(entry); + } + settings.endArray(); + settings.endGroup(); + + return servers; +} + +//! Expects \a settings to be positioned on the server group. +QList readLegacyServers(QSettings &settings, int lastIndex) +{ + QList servers; + + for (int i = 0; i <= lastIndex; ++i) { + ServersSettings::ServerEntry entry; + entry.saveName = settings.value(legacyKey("saveName", i)).toString(); + entry.host = settings.value(legacyKey("server", i)).toString(); + entry.port = settings.value(legacyKey("port", i)).toString(); + entry.username = settings.value(legacyKey("username", i)).toString(); + entry.password = settings.value(legacyKey("password", i)).toString(); + entry.savePassword = settings.value(legacyKey("savePassword", i)).toBool(); + entry.site = settings.value(legacyKey("site", i)).toString(); + + // Older builds could leave the unused index 0 behind. + if (!entry.saveName.isEmpty() || !entry.host.isEmpty()) { + servers.append(entry); + } + } + + return servers; +} + +void writeServerArray(QSettings &settings, const QList &servers) +{ + settings.beginGroup(SERVER_DETAILS_GROUP); + // beginWriteArray() never drops entries past the new size, so without this + // a removed profile would keep its slot - including its saved password - + // in servers.ini forever. The legacy keys live beside the array, not in it. + settings.remove(SERVERS_ARRAY); + settings.beginWriteArray(SERVERS_ARRAY, servers.size()); + for (int i = 0; i < servers.size(); ++i) { + settings.setArrayIndex(i); + const ServersSettings::ServerEntry &entry = servers.at(i); + settings.setValue("saveName", entry.saveName); + settings.setValue("server", entry.host); + settings.setValue("port", entry.port); + settings.setValue("username", entry.username); + settings.setValue("password", entry.password); + settings.setValue("savePassword", entry.savePassword); + settings.setValue("site", entry.site); + } + settings.endArray(); + settings.endGroup(); +} +} // namespace ServersSettings::ServersSettings(const QString &settingPath, QObject *parent) - : SettingsManager(settingPath + "servers.ini", "server", QString(), parent) + : SettingsManager(settingPath + "servers.ini", SERVER_GROUP, QString(), parent) { + migrateLegacyServers(); +} + +QList ServersSettings::getServers() const +{ + return loadServers(); +} + +std::optional ServersSettings::getServer(int index) const +{ + const QList servers = loadServers(); + if (index < 0 || index >= servers.size()) { + return std::nullopt; + } + return servers.at(index); } void ServersSettings::setPreviousHostLogin(int previous) @@ -45,16 +147,14 @@ void ServersSettings::setPrevioushostName(const QString &name) QString ServersSettings::getSaveName(QString defaultname) { - int index = getPrevioushostindex(getPrevioushostName()); - QVariant saveName = getValue(QString("saveName%1").arg(index), "server", "server_details"); - return saveName == QVariant() ? std::move(defaultname) : saveName.toString(); + const auto server = getServer(getPrevioushostindex(getPrevioushostName())); + return server ? server->saveName : std::move(defaultname); } QString ServersSettings::getSite(QString defaultSite) { - int index = getPrevioushostindex(getPrevioushostName()); - QVariant site = getValue(QString("site%1").arg(index), "server", "server_details"); - return site == QVariant() ? std::move(defaultSite) : site.toString(); + const auto server = getServer(getPrevioushostindex(getPrevioushostName())); + return server ? server->site : std::move(defaultSite); } QString ServersSettings::getPrevioushostName() const @@ -65,10 +165,9 @@ QString ServersSettings::getPrevioushostName() const int ServersSettings::getPrevioushostindex(const QString &saveName) const { - int size = getValue("totalServers", "server", "server_details").toInt(); - - for (int i = 0; i <= size; ++i) { - if (saveName == getValue(QString("saveName%1").arg(i), "server", "server_details").toString()) { + const QList servers = loadServers(); + for (int i = 0; i < servers.size(); ++i) { + if (servers.at(i).saveName == saveName) { return i; } } @@ -78,43 +177,38 @@ int ServersSettings::getPrevioushostindex(const QString &saveName) const QString ServersSettings::getHostname(QString defaultHost) const { - int index = getPrevioushostindex(getPrevioushostName()); - QVariant hostname = getValue(QString("server%1").arg(index), "server", "server_details"); - return hostname == QVariant() ? std::move(defaultHost) : hostname.toString(); + const auto server = getServer(getPrevioushostindex(getPrevioushostName())); + return server ? server->host : std::move(defaultHost); } QString ServersSettings::getPort(QString defaultPort) const { - int index = getPrevioushostindex(getPrevioushostName()); - QVariant port = getValue(QString("port%1").arg(index), "server", "server_details"); - qCDebug(ServersSettingsLog) << "getPort() index = " << index << " port.val = " << port.toString(); - return port == QVariant() ? std::move(defaultPort) : port.toString(); + const auto server = getServer(getPrevioushostindex(getPrevioushostName())); + qCDebug(ServersSettingsLog) << "getPort() found server = " << server.has_value(); + return server ? server->port : std::move(defaultPort); } QString ServersSettings::getPlayerName(QString defaultName) const { - int index = getPrevioushostindex(getPrevioushostName()); - QVariant name = getValue(QString("username%1").arg(index), "server", "server_details"); - qCDebug(ServersSettingsLog) << "getPlayerName() index = " << index << " name.val = " << name.toString(); - return name == QVariant() ? std::move(defaultName) : name.toString(); + const auto server = getServer(getPrevioushostindex(getPrevioushostName())); + qCDebug(ServersSettingsLog) << "getPlayerName() found server = " << server.has_value(); + return server ? server->username : std::move(defaultName); } QString ServersSettings::getPassword() { - int index = getPrevioushostindex(getPrevioushostName()); - - if (getSavePassword()) { - return getValue(QString("password%1").arg(index), "server", "server_details").toString(); + const auto server = getServer(getPrevioushostindex(getPrevioushostName())); + if (!server || !server->savePassword) { + return QString(); } - return QString(); + return server->password; } bool ServersSettings::getSavePassword() const { - int index = getPrevioushostindex(getPrevioushostName()); - bool save = getValue(QString("savePassword%1").arg(index), "server", "server_details").toBool(); - return save; + const auto server = getServer(getPrevioushostindex(getPrevioushostName())); + return server && server->savePassword; } void ServersSettings::setAutoConnect(int autoconnect) @@ -155,6 +249,23 @@ void ServersSettings::setFPPlayerName(QString playerName) setValue(playerName, "fpPlayerName"); } +void ServersSettings::setServerPassword(const QString &saveName, const QString &password) +{ + QList servers = loadServers(); + for (ServerEntry &server : servers) { + if (server.saveName == saveName) { + server.password = password; + storeServers(servers); + return; + } + } + + // A credential write that silently no-ops is hard to diagnose from a bug + // report; surface a mismatched profile name instead. + qCWarning(ServersSettingsLog) << "setServerPassword() could not find profile:" << saveName + << "- password not saved"; +} + QString ServersSettings::getFPPlayerName(QString defaultName) const { QVariant name = getValue("fpPlayerName"); @@ -189,62 +300,29 @@ void ServersSettings::addNewServer(const QString &saveName, return; } - int index = getValue("totalServers", "server", "server_details").toInt() + 1; - - setValue(saveName, QString("saveName%1").arg(index), "server", "server_details"); - setValue(serv, QString("server%1").arg(index), "server", "server_details"); - setValue(port, QString("port%1").arg(index), "server", "server_details"); - setValue(username, QString("username%1").arg(index), "server", "server_details"); - setValue(savePassword, QString("savePassword%1").arg(index), "server", "server_details"); - setValue(index, "totalServers", "server", "server_details"); - setValue(password, QString("password%1").arg(index), "server", "server_details"); - setValue(site, QString("site%1").arg(index), "server", "server_details"); + QList servers = loadServers(); + ServerEntry entry; + entry.saveName = saveName; + entry.host = serv; + entry.port = port; + entry.username = username; + entry.password = password; + entry.savePassword = savePassword; + entry.site = site; + servers.append(entry); + storeServers(servers); } void ServersSettings::removeServer(QString servAddr) { - int size = getValue("totalServers", "server", "server_details").toInt(); - - bool found = false; - for (int i = 0; i <= size; ++i) { - if (!found) { - // find entry and overwrite it - if (servAddr == getValue(QString("server%1").arg(i), "server", "server_details").toString()) { - found = true; - } - } else { - // move all other entries after it one back, overwriting the previous one - int previous = i - 1; // we delete only one entry - setValue(getValue(QString("server%1").arg(i), "server", "server_details"), - QString("server%1").arg(previous), "server", "server_details"); - setValue(getValue(QString("port%1").arg(i), "server", "server_details"), QString("port%1").arg(previous), - "server", "server_details"); - setValue(getValue(QString("username%1").arg(i), "server", "server_details"), - QString("username%1").arg(previous), "server", "server_details"); - setValue(getValue(QString("savePassword%1").arg(i), "server", "server_details"), - QString("savePassword%1").arg(previous), "server", "server_details"); - setValue(getValue(QString("password%1").arg(i), "server", "server_details"), - QString("password%1").arg(previous), "server", "server_details"); - setValue(getValue(QString("saveName%1").arg(i), "server", "server_details"), - QString("saveName%1").arg(previous), "server", "server_details"); - setValue(getValue(QString("site%1").arg(i), "server", "server_details"), QString("site%1").arg(previous), - "server", "server_details"); + QList servers = loadServers(); + for (int i = 0; i < servers.size(); ++i) { + if (servers.at(i).host == servAddr) { + servers.removeAt(i); + storeServers(servers); + return; } } - - // if we have deleted an entry, adjust the total - if (found) { - setValue(size - 1, "totalServers", "server", "server_details"); - - // delete last value - deleteValue(QString("server%1").arg(size), "server", "server_details"); - deleteValue(QString("port%1").arg(size), "server", "server_details"); - deleteValue(QString("username%1").arg(size), "server", "server_details"); - deleteValue(QString("savePassword%1").arg(size), "server", "server_details"); - deleteValue(QString("password%1").arg(size), "server", "server_details"); - deleteValue(QString("saveName%1").arg(size), "server", "server_details"); - deleteValue(QString("site%1").arg(size), "server", "server_details"); - } } /** @@ -252,23 +330,23 @@ void ServersSettings::removeServer(QString servAddr) */ bool ServersSettings::updateExistingServerWithoutLoss(QString saveName, QString serv, QString port, QString site) { - int size = getValue("totalServers", "server", "server_details").toInt(); - - for (int i = 0; i <= size; ++i) { - if (serv == getValue(QString("server%1").arg(i), "server", "server_details").toString()) { - if (!port.isEmpty()) { - setValue(port, QString("port%1").arg(i), "server", "server_details"); - } - - if (!site.isEmpty()) { - setValue(site, QString("site%1").arg(i), "server", "server_details"); - } - - setValue(saveName, QString("saveName%1").arg(i), "server", "server_details"); - - return true; + QList servers = loadServers(); + for (ServerEntry &server : servers) { + if (server.host != serv) { + continue; } + + if (!port.isEmpty()) { + server.port = port; + } + if (!site.isEmpty()) { + server.site = site; + } + server.saveName = saveName; + storeServers(servers); + return true; } + return false; } @@ -280,17 +358,13 @@ bool ServersSettings::updateExistingServer(QString saveName, bool savePassword, QString site) { - int size = getValue("totalServers", "server", "server_details").toInt(); - - for (int i = 0; i <= size; ++i) { - if (serv == getValue(QString("server%1").arg(i), "server", "server_details").toString()) { - setValue(port, QString("port%1").arg(i), "server", "server_details"); - updateServerFields(i, saveName, username, password, savePassword, site); - - return true; - } + const int index = findServerIndex(serv, port); + if (index < 0) { + return false; } - return false; + + updateServerFields(index, saveName, username, password, savePassword, site); + return true; } void ServersSettings::updateServerFields(int index, @@ -300,33 +374,33 @@ void ServersSettings::updateServerFields(int index, bool savePassword, const QString &site) { + QList servers = loadServers(); + if (index < 0 || index >= servers.size()) { + return; + } + + ServerEntry &server = servers[index]; if (!username.isEmpty()) { - setValue(username, QString("username%1").arg(index), "server", "server_details"); + server.username = username; } - if (savePassword && !password.isEmpty()) { - setValue(password, QString("password%1").arg(index), "server", "server_details"); + server.password = password; } else { - setValue(QString(), QString("password%1").arg(index), "server", "server_details"); + server.password.clear(); } - if (!site.isEmpty()) { - setValue(site, QString("site%1").arg(index), "server", "server_details"); + server.site = site; } - - setValue(savePassword, QString("savePassword%1").arg(index), "server", "server_details"); - setValue(saveName, QString("saveName%1").arg(index), "server", "server_details"); + server.savePassword = savePassword; + server.saveName = saveName; + storeServers(servers); } int ServersSettings::findServerIndex(const QString &host, const QString &port) const { - int size = getValue("totalServers", "server", "server_details").toInt(); - - for (int i = 0; i <= size; ++i) { - QString storedHost = getValue(QString("server%1").arg(i), "server", "server_details").toString(); - QString storedPort = getValue(QString("port%1").arg(i), "server", "server_details").toString(); - - if (storedHost == host && storedPort == port) { + const QList servers = loadServers(); + for (int i = 0; i < servers.size(); ++i) { + if (servers.at(i).host == host && servers.at(i).port == port) { return i; } } @@ -336,12 +410,9 @@ int ServersSettings::findServerIndex(const QString &host, const QString &port) c int ServersSettings::findHostIndex(const QString &host) const { - int size = getValue("totalServers", "server", "server_details").toInt(); - - for (int i = 0; i <= size; ++i) { - QString storedHost = getValue(QString("server%1").arg(i), "server", "server_details").toString(); - - if (storedHost.compare(host, Qt::CaseInsensitive) == 0) { + const QList servers = loadServers(); + for (int i = 0; i < servers.size(); ++i) { + if (servers.at(i).host.compare(host, Qt::CaseInsensitive) == 0) { return i; } } @@ -351,29 +422,100 @@ int ServersSettings::findHostIndex(const QString &host) const bool ServersSettings::hasUsername(const QString &host, const QString &port) const { - int index = findServerIndex(host, port); - if (index < 0) { - return false; - } - - QString user = getValue(QString("username%1").arg(index), "server", "server_details").toString(); - return !user.isEmpty(); + const auto server = getServer(findServerIndex(host, port)); + return server && !server->username.isEmpty(); } bool ServersSettings::hasCredentials(const QString &host, const QString &port) const { - int index = findServerIndex(host, port); - if (index < 0) { - return false; - } - - bool save = getValue(QString("savePassword%1").arg(index), "server", "server_details").toBool(); - QString password = getValue(QString("password%1").arg(index), "server", "server_details").toString(); - - return save && !password.isEmpty(); + const auto server = getServer(findServerIndex(host, port)); + return server && server->savePassword && !server->password.isEmpty(); } bool ServersSettings::hasLoginData(const QString &host, const QString &port) const { return hasUsername(host, port) && hasCredentials(host, port); } + +QList ServersSettings::loadServers() const +{ + QSettings settings = getSettings(); + settings.beginGroup(SERVER_GROUP); + const QList servers = readServerArray(settings); + settings.endGroup(); + + return servers; +} + +void ServersSettings::storeServers(const QList &servers) +{ + batchWrite([&servers](QSettings &settings) { + settings.beginGroup(SERVER_GROUP); + writeServerArray(settings, servers); + settings.endGroup(); + }); +} + +void ServersSettings::migrateLegacyServers() +{ + { + QSettings settings = getSettings(); + settings.beginGroup(SERVER_GROUP); + // Only builds that predate the array format write this marker. Once it + // is gone there is nothing left to import and no reason to touch the + // file (a sync on every start is a needless open-and-write). + const bool hasLegacy = settings.contains(QStringLiteral("%1/totalServers").arg(SERVER_DETAILS_GROUP)); + settings.endGroup(); + + if (!hasLegacy) { + return; + } + } + + const QSettings::Status status = batchWrite([](QSettings &settings) { + settings.beginGroup(SERVER_GROUP); + + const int lastLegacyIndex = + settings.value(QStringLiteral("%1/totalServers").arg(SERVER_DETAILS_GROUP), -1).toInt(); + + QList servers = readServerArray(settings); + + // An older build can write the legacy keys again after the array + // migration, so the array is not automatically complete: append every + // legacy profile the array does not already hold instead of dropping it. + int imported = 0; + const QList legacyServers = readLegacyServers(settings, lastLegacyIndex); + for (const ServersSettings::ServerEntry &entry : legacyServers) { + const bool known = + std::any_of(servers.cbegin(), servers.cend(), [&entry](const ServersSettings::ServerEntry &existing) { + return existing.host == entry.host && existing.port == entry.port; + }); + if (!known) { + servers.append(entry); + ++imported; + } + } + + writeServerArray(settings, servers); + if (imported > 0) { + qCDebug(ServersSettingsLog) << "Migrated" << imported << "server profiles to the array format"; + } + + // The array (freshly written or already present) is authoritative now. + for (int i = 0; i <= lastLegacyIndex; ++i) { + settings.remove(legacyKey("saveName", i)); + settings.remove(legacyKey("server", i)); + settings.remove(legacyKey("port", i)); + settings.remove(legacyKey("username", i)); + settings.remove(legacyKey("password", i)); + settings.remove(legacyKey("savePassword", i)); + settings.remove(legacyKey("site", i)); + } + settings.remove(QStringLiteral("%1/totalServers").arg(SERVER_DETAILS_GROUP)); + settings.endGroup(); + }); + + if (status != QSettings::NoError) { + qCWarning(ServersSettingsLog) << "Failed to migrate server profiles:" << status; + } +} diff --git a/libcockatrice_settings/libcockatrice/settings/servers_settings.h b/libcockatrice_settings/libcockatrice/settings/servers_settings.h index 40648baa9..b403b1cea 100644 --- a/libcockatrice_settings/libcockatrice/settings/servers_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/servers_settings.h @@ -9,9 +9,11 @@ #include "settings_manager.h" +#include #include #include #include +#include #include class QObject; @@ -27,6 +29,22 @@ class ServersSettings : public SettingsManager friend class SettingsCache; public: + struct ServerEntry + { + QString saveName; + QString host; + QString port; + QString username; + QString password; + bool savePassword = false; + QString site; + }; + + explicit ServersSettings(const QString &settingPath, QObject *parent = nullptr); + + QList getServers() const; + std::optional getServer(int index) const; + int getPreviousHostLogin() const; int getPrevioushostindex(const QString &) const; QStringList getPreviousHostList() const; @@ -51,6 +69,8 @@ public: void setFPHostName(QString hostname); void setFPPort(QString port); void setFPPlayerName(QString playerName); + //! \brief Store a password (or a "$scrypt$..." verifier) for the given saved server. + void setServerPassword(const QString &saveName, const QString &password); void addNewServer(const QString &saveName, const QString &serv, const QString &port, @@ -86,8 +106,11 @@ public: bool getClearDebugLogStatus(bool abDefaultValue) const; private: - explicit ServersSettings(const QString &settingPath, QObject *parent = nullptr); ServersSettings(const ServersSettings & /*other*/); + + QList loadServers() const; + void storeServers(const QList &servers); + void migrateLegacyServers(); }; #endif // SERVERSSETTINGS_H diff --git a/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp b/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp index f98778ca0..7cc1fd7be 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp +++ b/libcockatrice_settings/libcockatrice/settings/settings_manager.cpp @@ -193,13 +193,12 @@ QVariant SettingsManager::getValue(const QString &name, return value; } -void SettingsManager::batchWrite(std::function batchWriteFunction) +QSettings::Status SettingsManager::batchWrite(std::function batchWriteFunction) { auto settings = getSettings(); - settings.setAtomicSyncRequired(false); batchWriteFunction(settings); settings.sync(); // single flush - settings.setAtomicSyncRequired(true); + return settings.status(); } /** diff --git a/libcockatrice_settings/libcockatrice/settings/settings_manager.h b/libcockatrice_settings/libcockatrice/settings/settings_manager.h index 8d3110106..760d095e6 100644 --- a/libcockatrice_settings/libcockatrice/settings/settings_manager.h +++ b/libcockatrice_settings/libcockatrice/settings/settings_manager.h @@ -28,7 +28,9 @@ public: QVariant getValue(const QString &name, const QString &group, const QString &subGroup = QString()) const; QVariant getValue(const QString &name, const QString &group, const QString &subGroup, const QVariant &defaultValue) const; - void batchWrite(std::function batchWriteFunction); + //! \brief Runs \a batchWriteFunction on a temporary QSettings and syncs once. + //! \return the status of the underlying file after the final sync. + QSettings::Status batchWrite(std::function batchWriteFunction); void sync(); diff --git a/libcockatrice_utility/CMakeLists.txt b/libcockatrice_utility/CMakeLists.txt index 5e6fa986d..8d2cb5b92 100644 --- a/libcockatrice_utility/CMakeLists.txt +++ b/libcockatrice_utility/CMakeLists.txt @@ -1,10 +1,6 @@ cmake_minimum_required(VERSION 3.16) project(Utility VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTOUIC ON) -set(CMAKE_AUTORCC ON) - set(UTILITY_SOURCES libcockatrice/utility/cryptoutil.cpp libcockatrice/utility/expression.cpp @@ -13,6 +9,7 @@ set(UTILITY_SOURCES libcockatrice/utility/report_categories.cpp libcockatrice/utility/server_rate_limiter.cpp libcockatrice/utility/translation_loader.cpp + libcockatrice/utility/user_data_backup.cpp libcockatrice/utility/warning_categories.cpp ) @@ -20,12 +17,15 @@ set(UTILITY_HEADERS libcockatrice/utility/card_ref.h libcockatrice/utility/color.h libcockatrice/utility/cryptoutil.h + libcockatrice/utility/debouncer.h libcockatrice/utility/expression.h + libcockatrice/utility/installer_close_protocol.h libcockatrice/utility/levenshtein.h libcockatrice/utility/local_server_name.h libcockatrice/utility/macros.h libcockatrice/utility/passwordhasher.h libcockatrice/utility/playmat_params.h + libcockatrice/utility/qt_utils.h libcockatrice/utility/report_categories.h libcockatrice/utility/string_limits.h libcockatrice/utility/dice_limits.h @@ -35,6 +35,7 @@ set(UTILITY_HEADERS libcockatrice/utility/days_years_between.h libcockatrice/utility/server_rate_limiter.h libcockatrice/utility/translation_loader.h + libcockatrice/utility/user_data_backup.h libcockatrice/utility/warning_categories.h ) diff --git a/libcockatrice_utility/libcockatrice/utility/cryptoutil.cpp b/libcockatrice_utility/libcockatrice/utility/cryptoutil.cpp index fb59022de..871ee1357 100644 --- a/libcockatrice_utility/libcockatrice/utility/cryptoutil.cpp +++ b/libcockatrice_utility/libcockatrice/utility/cryptoutil.cpp @@ -1,8 +1,16 @@ #include "cryptoutil.h" +#include +#include #include #include +// Sealed secret layout: "enc1::". +static constexpr int SECRET_KEY_LENGTH = 32; +static constexpr int SECRET_NONCE_LENGTH = 12; +static constexpr int SECRET_TAG_LENGTH = 16; +static const char *SECRET_FORMAT_PREFIX = "enc1"; + namespace CryptoUtil { QByteArray randomBytes(int count) @@ -23,4 +31,99 @@ quint64 randomUInt64() } return value; } + +QString encryptSecret(const QByteArray &plaintext, const QByteArray &key, const QByteArray &aad) +{ + if (key.size() != SECRET_KEY_LENGTH) { + return {}; + } + const QByteArray nonce = randomBytes(SECRET_NONCE_LENGTH); + QByteArray ciphertext(plaintext.size() + SECRET_TAG_LENGTH, Qt::Uninitialized); + QByteArray tag(SECRET_TAG_LENGTH, Qt::Uninitialized); + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (ctx == nullptr) { + return {}; + } + int written = 0; + int finalWritten = 0; + bool ok = EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) == 1 && + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, nonce.size(), nullptr) == 1 && + EVP_EncryptInit_ex(ctx, nullptr, nullptr, reinterpret_cast(key.constData()), + reinterpret_cast(nonce.constData())) == 1; + if (ok && !aad.isEmpty()) { + ok = EVP_EncryptUpdate(ctx, nullptr, &written, reinterpret_cast(aad.constData()), + aad.size()) == 1; + } + if (ok) { + ok = EVP_EncryptUpdate(ctx, reinterpret_cast(ciphertext.data()), &written, + reinterpret_cast(plaintext.constData()), plaintext.size()) == 1; + } + if (ok) { + ok = EVP_EncryptFinal_ex(ctx, reinterpret_cast(ciphertext.data() + written), &finalWritten) == + 1 && + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, SECRET_TAG_LENGTH, tag.data()) == 1; + } + EVP_CIPHER_CTX_free(ctx); + if (!ok) { + return {}; + } + ciphertext.truncate(written + finalWritten); + ciphertext.append(tag); + + return QString::fromLatin1(SECRET_FORMAT_PREFIX) + QLatin1Char(':') + nonce.toBase64() + QLatin1Char(':') + + ciphertext.toBase64(); +} + +QByteArray decryptSecret(const QString &sealed, const QByteArray &key, const QByteArray &aad) +{ + if (key.size() != SECRET_KEY_LENGTH) { + return {}; + } + const QStringList parts = sealed.split(QLatin1Char(':')); + if (parts.size() != 3 || parts.at(0) != QLatin1String(SECRET_FORMAT_PREFIX)) { + return {}; + } + const QByteArray nonce = QByteArray::fromBase64(parts.at(1).toUtf8()); + const QByteArray ciphertext = QByteArray::fromBase64(parts.at(2).toUtf8()); + if (nonce.size() != SECRET_NONCE_LENGTH || ciphertext.size() < SECRET_TAG_LENGTH) { + return {}; + } + const int plaintextLength = ciphertext.size() - SECRET_TAG_LENGTH; + QByteArray plaintext(qMax(plaintextLength, 1), Qt::Uninitialized); + QByteArray tag = ciphertext.right(SECRET_TAG_LENGTH); + const QByteArray body = ciphertext.left(plaintextLength); + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + if (ctx == nullptr) { + return {}; + } + int written = 0; + int finalWritten = 0; + bool ok = EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr) == 1 && + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, nonce.size(), nullptr) == 1 && + EVP_DecryptInit_ex(ctx, nullptr, nullptr, reinterpret_cast(key.constData()), + reinterpret_cast(nonce.constData())) == 1; + if (ok && !aad.isEmpty()) { + ok = EVP_DecryptUpdate(ctx, nullptr, &written, reinterpret_cast(aad.constData()), + aad.size()) == 1; + } + if (ok) { + // The tag must be set before the final call, which authenticates it; a + // mismatch makes EVP_DecryptFinal_ex fail and the secret stays opaque. + ok = EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, tag.size(), tag.data()) == 1 && + EVP_DecryptUpdate(ctx, reinterpret_cast(plaintext.data()), &written, + reinterpret_cast(body.constData()), body.size()) == 1; + } + if (ok) { + ok = + EVP_DecryptFinal_ex(ctx, reinterpret_cast(plaintext.data() + written), &finalWritten) == 1; + } + EVP_CIPHER_CTX_free(ctx); + if (!ok) { + return {}; + } + plaintext.truncate(written + finalWritten); + return plaintext; +} } // namespace CryptoUtil diff --git a/libcockatrice_utility/libcockatrice/utility/cryptoutil.h b/libcockatrice_utility/libcockatrice/utility/cryptoutil.h index dba9dc37d..d12200bc7 100644 --- a/libcockatrice_utility/libcockatrice/utility/cryptoutil.h +++ b/libcockatrice_utility/libcockatrice/utility/cryptoutil.h @@ -2,12 +2,21 @@ #define CRYPTOUTIL_H #include +#include #include namespace CryptoUtil { QByteArray randomBytes(int count); quint64 randomUInt64(); +/** @brief Seal a secret with AES-256-GCM as "enc1::". + * Returns an empty string unless @p key is a valid 32-byte key and the cipher + * completes. @p aad is authenticated but not stored (bind the ciphertext to its + * context, e.g. the user name it belongs to). */ +QString encryptSecret(const QByteArray &plaintext, const QByteArray &key, const QByteArray &aad = {}); +/** @brief Open a string produced by encryptSecret(). Returns an empty array on any + * failure: unknown format, malformed base64, wrong key, wrong aad, or tampering. */ +QByteArray decryptSecret(const QString &sealed, const QByteArray &key, const QByteArray &aad = {}); } // namespace CryptoUtil #endif diff --git a/libcockatrice_utility/libcockatrice/utility/debouncer.h b/libcockatrice_utility/libcockatrice/utility/debouncer.h new file mode 100644 index 000000000..54635db4f --- /dev/null +++ b/libcockatrice_utility/libcockatrice/utility/debouncer.h @@ -0,0 +1,62 @@ +#ifndef COCKATRICE_DEBOUNCER_H +#define COCKATRICE_DEBOUNCER_H + +#include +#include +#include +#include + +namespace QtUtils +{ +/** + * @brief Runs the most recent call() after a period without further calls. + * + * Typical use is coalescing rapid model or editor updates into one refresh: + * + * debouncer.call([this] { refresh(); }); + * + * The pending callback is owned by the debouncer and replaced when call() is invoked + * again. The debouncer must outlive any callback it schedules; parenting it to the + * owning widget is usually enough. + */ +class Debouncer : public QObject +{ +public: + explicit Debouncer(int intervalMs, QObject *parent = nullptr) : QObject(parent) + { + timer.setSingleShot(true); + timer.setInterval(intervalMs); + connect(&timer, &QTimer::timeout, this, [this] { + if (callback) { + auto pending = std::move(callback); + callback = nullptr; + pending(); + } + }); + } + + /** + * @brief Schedules @p newCallback, replacing any callback not run yet. + */ + void call(std::function newCallback) + { + callback = std::move(newCallback); + timer.start(); + } + + /** + * @brief Drops the pending callback and stops the timer. + */ + void cancel() + { + timer.stop(); + callback = nullptr; + } + +private: + QTimer timer; + std::function callback; +}; +} // namespace QtUtils + +#endif // COCKATRICE_DEBOUNCER_H diff --git a/libcockatrice_utility/libcockatrice/utility/installer_close_protocol.h b/libcockatrice_utility/libcockatrice/utility/installer_close_protocol.h new file mode 100644 index 000000000..1db467b2f --- /dev/null +++ b/libcockatrice_utility/libcockatrice/utility/installer_close_protocol.h @@ -0,0 +1,18 @@ +#ifndef COCKATRICE_INSTALLER_CLOSE_PROTOCOL_H +#define COCKATRICE_INSTALLER_CLOSE_PROTOCOL_H + +/** + * @brief The one line the installer's holder probe writes to a running instance. + * + * Both ends include this header: the probe writes the line + * (tools/installer-holder-probe/installer_close_request.cpp) and the client's + * InstallerCloseServer reads it. Keeping the bytes in one place means the two ends cannot + * drift apart, and the test pins the exact line so a change to it is a deliberate protocol + * change rather than a typo in one copy. + * + * Plain C++ on purpose: the probe links no Qt and no part of this project, so this header + * must stay free of both. + */ +inline constexpr char INSTALLER_CLOSE_COMMAND[] = "COCKATRICE_INSTALLER_CLOSE\n"; + +#endif // COCKATRICE_INSTALLER_CLOSE_PROTOCOL_H diff --git a/libcockatrice_utility/libcockatrice/utility/levenshtein.cpp b/libcockatrice_utility/libcockatrice/utility/levenshtein.cpp index f7d8459cb..11eeef19b 100644 --- a/libcockatrice_utility/libcockatrice/utility/levenshtein.cpp +++ b/libcockatrice_utility/libcockatrice/utility/levenshtein.cpp @@ -7,23 +7,40 @@ int levenshteinDistance(const QString &s1, const QString &s2) { - int len1 = s1.size(); - int len2 = s2.size(); - std::vector> dp(len1 + 1, std::vector(len2 + 1)); + const qsizetype len1 = s1.size(); + const qsizetype len2 = s2.size(); - for (int i = 0; i <= len1; i++) { - dp[i][0] = i; + if (len1 == 0) { + return static_cast(len2); } - for (int j = 0; j <= len2; j++) { - dp[0][j] = j; + if (len2 == 0) { + return static_cast(len1); + } + if (s1 == s2) { + return 0; } - for (int i = 1; i <= len1; i++) { - for (int j = 1; j <= len2; j++) { - int cost = (s1[i - 1] == s2[j - 1]) ? 0 : 1; - dp[i][j] = std::min({dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost}); + // Only the previous row of the distance matrix is needed to compute the + // next one, so two rows replace the full len1 x len2 matrix. The card + // search runs this for every matching card on each keystroke. + std::vector previous(len2 + 1); + std::vector current(len2 + 1); + + for (qsizetype j = 0; j <= len2; j++) { + previous[j] = static_cast(j); + } + + for (qsizetype i = 1; i <= len1; i++) { + current[0] = static_cast(i); + const QChar c1 = s1.at(i - 1); + + for (qsizetype j = 1; j <= len2; j++) { + const int cost = (c1 == s2.at(j - 1)) ? 0 : 1; + current[j] = std::min(previous[j] + 1, std::min(current[j - 1] + 1, previous[j - 1] + cost)); } + + std::swap(previous, current); } - return dp[len1][len2]; + return previous[len2]; } diff --git a/libcockatrice_utility/libcockatrice/utility/passwordhasher.cpp b/libcockatrice_utility/libcockatrice/utility/passwordhasher.cpp index b4a412fa6..8e876d5bc 100644 --- a/libcockatrice_utility/libcockatrice/utility/passwordhasher.cpp +++ b/libcockatrice_utility/libcockatrice/utility/passwordhasher.cpp @@ -4,6 +4,9 @@ #include #include #include +#include +#include +#include QString PasswordHasher::computeHash(const QString &password, const QString &salt) { @@ -54,3 +57,116 @@ QString PasswordHasher::generateActivationToken() { return QString(CryptoUtil::randomBytes(16).toBase64().left(16)); } + +QByteArray PasswordHasher::deriveKey(const QString &password, const QByteArray &salt, int n, int r, int p) +{ + QByteArray key(SCRYPT_VERIFIER_LENGTH, '\0'); + const QByteArray passwordUtf8 = password.toUtf8(); + // EVP_PBE_scrypt aborts unless maxmem covers the required working memory: V is 128 * n * + // r bytes, B is 128 * r * p, and XY is 256 * r. Under-budgeting XY makes OpenSSL fail + // for r >= 17, which costParamsAreSane still accepts. + const auto maxmem = + static_cast(128) * n * r + static_cast(128) * r * p + static_cast(256) * r + 4096; + if (EVP_PBE_scrypt(passwordUtf8.constData(), passwordUtf8.size(), + reinterpret_cast(salt.constData()), salt.size(), n, r, p, maxmem, + reinterpret_cast(key.data()), key.size()) != 1) { + return QByteArray(); + } + return key; +} + +bool PasswordHasher::costParamsAreSane(int n, int r, int p) +{ + // Bounds adopted during review: n in [1024, 2**20] and a power of two, r in [1, 32], + // p in [1, 16]. Anything else is rejected before we allocate or derive for it. + return n >= 1024 && n <= (1 << 20) && (n & (n - 1)) == 0 && r >= 1 && r <= 32 && p >= 1 && p <= 16; +} + +QString PasswordHasher::generatePasswordVerifier(const QString &password) +{ + const QByteArray salt = CryptoUtil::randomBytes(SCRYPT_SALT_LENGTH); + const QByteArray verifier = deriveKey(password, salt, SCRYPT_N, SCRYPT_R, SCRYPT_P); + return QString("$scrypt$%1$%2$%3$%4$%5") + .arg(SCRYPT_N) + .arg(SCRYPT_R) + .arg(SCRYPT_P) + .arg(QString(salt.toBase64())) + .arg(QString(verifier.toBase64())); +} + +PasswordVerifier PasswordHasher::parsePasswordVerifier(const QString &stored) +{ + PasswordVerifier result; + const QStringList parts = stored.split("$"); + if (parts.size() != 7 || parts.at(1) != "scrypt") { + return result; + } + + bool ok = false; + const int n = parts.at(2).toInt(&ok); + if (!ok) { + return result; + } + const int r = parts.at(3).toInt(&ok); + if (!ok) { + return result; + } + const int p = parts.at(4).toInt(&ok); + if (!ok || !costParamsAreSane(n, r, p)) { + return result; + } + + const QByteArray salt = QByteArray::fromBase64(parts.at(5).toUtf8()); + const QByteArray verifier = QByteArray::fromBase64(parts.at(6).toUtf8()); + if (salt.isEmpty() || verifier.size() != SCRYPT_VERIFIER_LENGTH) { + return result; + } + + result.format = PasswordFormat::Scrypt; + result.n = n; + result.r = r; + result.p = p; + result.salt = salt; + result.verifier = verifier; + result.isValid = true; + return result; +} + +bool PasswordHasher::isLegacyFormat(const QString &stored) +{ + return !stored.startsWith("$"); +} + +bool PasswordHasher::verifyPassword(const QString &password, const QString &storedPasswordData) +{ + if (isLegacyFormat(storedPasswordData)) { + return storedPasswordData == computeHash(password, storedPasswordData.left(16)); + } + + const PasswordVerifier verifier = parsePasswordVerifier(storedPasswordData); + if (!verifier.isValid) { + return false; + } + const QByteArray derived = deriveKey(password, verifier.salt, verifier.n, verifier.r, verifier.p); + return !derived.isEmpty() && constantTimeEquals(derived, verifier.verifier); +} + +QByteArray PasswordHasher::computeResponse(const QByteArray &key, const QByteArray &nonce) +{ + QByteArray response(EVP_MAX_MD_SIZE, '\0'); + unsigned int responseLength = 0; + if (HMAC(EVP_sha256(), key.constData(), key.size(), reinterpret_cast(nonce.constData()), + nonce.size(), reinterpret_cast(response.data()), &responseLength) == nullptr) { + qFatal("PasswordHasher::computeResponse: HMAC failed"); + } + response.resize(responseLength); + return response; +} + +bool PasswordHasher::constantTimeEquals(const QByteArray &a, const QByteArray &b) +{ + if (a.size() != b.size()) { + return false; + } + return CRYPTO_memcmp(a.constData(), b.constData(), a.size()) == 0; +} diff --git a/libcockatrice_utility/libcockatrice/utility/passwordhasher.h b/libcockatrice_utility/libcockatrice/utility/passwordhasher.h index 8ad83f089..f46d2f21a 100644 --- a/libcockatrice_utility/libcockatrice/utility/passwordhasher.h +++ b/libcockatrice_utility/libcockatrice/utility/passwordhasher.h @@ -1,14 +1,58 @@ #ifndef PASSWORDHASHER_H #define PASSWORDHASHER_H +#include +#include #include +// scrypt cost parameters used for newly created password verifiers. These match +// the RFC 7914 recommended parameters for interactive use. +constexpr int SCRYPT_N = 32768; +constexpr int SCRYPT_R = 8; +constexpr int SCRYPT_P = 1; +constexpr int SCRYPT_SALT_LENGTH = 16; +constexpr int SCRYPT_VERIFIER_LENGTH = 64; + +enum class PasswordFormat +{ + None = 0, + Scrypt +}; + +struct PasswordVerifier +{ + PasswordFormat format = PasswordFormat::None; + int n = 0; + int r = 0; + int p = 0; + QByteArray salt; + QByteArray verifier; + bool isValid = false; +}; + class PasswordHasher { public: static QString computeHash(const QString &password, const QString &salt); static QString generateRandomSalt(const int len = 16); static QString generateActivationToken(); + + /** @brief Derive the scrypt verifier for the given password, salt and cost parameters. Empty on failure. */ + static QByteArray deriveKey(const QString &password, const QByteArray &salt, int n, int r, int p); + /** @brief True if the scrypt cost parameters are acceptable for server and client use. */ + static bool costParamsAreSane(int n, int r, int p); + /** @brief Build a "$scrypt$$$

$$" string with a fresh random salt. */ + static QString generatePasswordVerifier(const QString &password); + /** @brief Parse a stored "$scrypt$..." string into its components. */ + static PasswordVerifier parsePasswordVerifier(const QString &stored); + /** @brief True if the stored value is not in the scrypt format (legacy salt+hash). */ + static bool isLegacyFormat(const QString &stored); + /** @brief True if the password matches the stored credential, whether legacy salt+hash or scrypt. */ + static bool verifyPassword(const QString &password, const QString &storedPasswordData); + /** @brief HMAC-SHA256 of nonce keyed with the password verifier, used for challenge-response logins. */ + static QByteArray computeResponse(const QByteArray &key, const QByteArray &nonce); + /** @brief Constant-time byte comparison. */ + static bool constantTimeEquals(const QByteArray &a, const QByteArray &b); }; #endif diff --git a/libcockatrice_utility/libcockatrice/utility/qt_utils.h b/libcockatrice_utility/libcockatrice/utility/qt_utils.h index 8e5212031..1d06c4d72 100644 --- a/libcockatrice_utility/libcockatrice/utility/qt_utils.h +++ b/libcockatrice_utility/libcockatrice/utility/qt_utils.h @@ -1,13 +1,33 @@ #ifndef COCKATRICE_QT_UTILS_H #define COCKATRICE_QT_UTILS_H +#include +#include +#include #include +#include #include +#include +#include +#include +#include +#include namespace QtUtils { -template T *findParentOfType(const QObject *obj) +/** + * @brief Returns the nearest ancestor of @p obj that casts to @c T. + * + * Walks the QObject parent chain. QWidget::parentWidget() is the same pointer as + * QObject::parent() for widgets, so this also covers the common "find the parent tab, + * splitter or scroll area" lookups. + * + * @param obj Object to start from. May be null. + * @param includeSelf Also test @p obj itself before walking upwards. + * @return The first matching ancestor, or nullptr if there is none. + */ +template T *findParentOfType(const QObject *obj, bool includeSelf = false) { - const QObject *p = obj ? obj->parent() : nullptr; + const QObject *p = includeSelf ? obj : (obj ? obj->parent() : nullptr); while (p) { if (auto casted = qobject_cast(const_cast(p))) { return casted; @@ -17,22 +37,172 @@ template T *findParentOfType(const QObject *obj) return nullptr; } -static inline void clearLayoutRec(QLayout *l) +/** + * @brief How clearLayoutRec() disposes of the widgets it removes. + * + * Deferred is the safe default: deleteLater() lets a widget finish handling the event that + * triggered the rebuild. Use Immediate when the caller rebuilds the same layout right away + * and a lingering widget would be visible or otherwise in the way until the next event loop + * turn. + */ +enum class DeletePolicy +{ + Deferred, + Immediate +}; + +/** + * @brief Removes and deletes every item in @p l, recursing into nested layouts. + * + * Widgets are disposed of according to @p policy; spacers and nested layouts are simply + * deleted. + */ +static inline void clearLayoutRec(QLayout *l, DeletePolicy policy = DeletePolicy::Deferred) { if (!l) { return; } - QLayoutItem *it; - while ((it = l->takeAt(0)) != nullptr) { + while (QLayoutItem *it = l->takeAt(0)) { if (QWidget *w = it->widget()) { - w->deleteLater(); + if (policy == DeletePolicy::Immediate) { + delete w; + } else { + w->deleteLater(); + } } if (QLayout *sub = it->layout()) { - clearLayoutRec(sub); + clearLayoutRec(sub, policy); } delete it; } } + +/** + * @brief Creates a layout of type @c LayoutT with zero margins. + * + * @param parent Parent widget, or nullptr when the layout is added later. + * @param spacing Optional spacing. Values below zero leave the style's default spacing + * untouched; pass a non-negative value to call setSpacing(). + * @param margin Uniform margin. Defaults to zero. + */ +template LayoutT *makeLayout(QWidget *parent = nullptr, int spacing = -1, int margin = 0) +{ + auto *layout = new LayoutT(parent); + layout->setContentsMargins(margin, margin, margin, margin); + if (spacing >= 0) { + layout->setSpacing(spacing); + } + return layout; +} + +/** + * @brief Forces @p layout to recompute and repaint immediately. + * + * Convenience for the invalidate/activate/update sequence used after changing a layout's + * contents outside of a resize event. + */ +inline void relayout(QLayout *layout) +{ + if (!layout) { + return; + } + layout->invalidate(); + layout->activate(); + layout->update(); +} + +/** + * @brief Creates a QScrollArea using the conventions the client applies nearly everywhere: + * the inner widget resizes with the viewport and the horizontal bar is disabled. + * + * @param parent Parent widget, or nullptr for a parentless scroll area. + * @param verticalPolicy Vertical scroll bar policy. + * @param horizontalPolicy Horizontal scroll bar policy. + */ +static inline QScrollArea *makeScrollArea(QWidget *parent = nullptr, + Qt::ScrollBarPolicy verticalPolicy = Qt::ScrollBarAsNeeded, + Qt::ScrollBarPolicy horizontalPolicy = Qt::ScrollBarAlwaysOff) +{ + auto *scrollArea = new QScrollArea(parent); + scrollArea->setWidgetResizable(true); + scrollArea->setVerticalScrollBarPolicy(verticalPolicy); + scrollArea->setHorizontalScrollBarPolicy(horizontalPolicy); + return scrollArea; +} + +/** + * @brief A monospace font at the system UI font's point size. + * + * Used for log dumps, deck codes and other text that has to line up. + */ +static inline QFont monospaceFont() +{ + QFont font(QStringLiteral("monospace")); + font.setStyleHint(QFont::Monospace); + const int systemPointSize = QFontDatabase::systemFont(QFontDatabase::GeneralFont).pointSize(); + if (systemPointSize > 0) { + font.setPointSize(systemPointSize); + } + return font; +} + +/** + * @brief Fades @p window between two opacity values. + * + * @param window Window to animate. + * @param startOpacity Starting opacity. + * @param endOpacity Ending opacity. + * @param durationMs Duration in milliseconds. + * @param hideWhenFinished Hide the window when the animation finishes. + * @return The running animation. It is owned by @p window and deletes itself when stopped. + */ +static inline QPropertyAnimation * +fadeWindow(QWidget *window, qreal startOpacity, qreal endOpacity, int durationMs, bool hideWhenFinished = false) +{ + auto *animation = new QPropertyAnimation(window, "windowOpacity", window); + animation->setDuration(durationMs); + animation->setStartValue(startOpacity); + animation->setEndValue(endOpacity); + if (hideWhenFinished) { + QObject::connect(animation, &QPropertyAnimation::finished, window, &QWidget::hide); + } + animation->start(QAbstractAnimation::DeleteWhenStopped); + return animation; +} + +/** + * @brief Opens @p path with the platform's file manager or default handler. + */ +static inline bool openPathInFileManager(const QString &path) +{ + return QDesktopServices::openUrl(QUrl::fromLocalFile(path)); +} + +/** + * @brief Returns @p font with bold styling enabled. + */ +static inline QFont boldFont(QFont font) +{ + font.setBold(true); + return font; +} + +/** + * @brief Shows a Yes/No confirmation dialog and returns true when the user confirms. + * + * @param parent Parent widget. + * @param title Dialog title. + * @param text Dialog text. + * @param defaultButton Button focused by default. NoButton lets Qt choose. + */ +static inline bool confirm(QWidget *parent, + const QString &title, + const QString &text, + QMessageBox::StandardButton defaultButton = QMessageBox::NoButton) +{ + return QMessageBox::question(parent, title, text, QMessageBox::Yes | QMessageBox::No, defaultButton) == + QMessageBox::Yes; +} } // namespace QtUtils #endif // COCKATRICE_QT_UTILS_H diff --git a/libcockatrice_utility/libcockatrice/utility/translation_loader.cpp b/libcockatrice_utility/libcockatrice/utility/translation_loader.cpp index 41c6107b3..e5761fcaf 100644 --- a/libcockatrice_utility/libcockatrice/utility/translation_loader.cpp +++ b/libcockatrice_utility/libcockatrice/utility/translation_loader.cpp @@ -5,8 +5,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -100,6 +102,60 @@ QList TranslationLoader::availableLanguages(const QString &_prefix, con return sortedLanguages; } +QString TranslationLoader::languageDisplayName(const QString &_lang) +{ + if (_lang.isEmpty()) { + return QString(); + } + + // The native name first, so a speaker finds their own language, then the English one, so everyone + // else does. A language shipped with a .qm file belongs in here: the names used to come from the + // translation files, where a missing translation left the selector blank. + static const QHash displayNames = { + {QStringLiteral("ca"), QStringLiteral("Català (Catalan)")}, + {QStringLiteral("cs"), QStringLiteral("Česky (Czech)")}, + {QStringLiteral("de"), QStringLiteral("Deutsch (German)")}, + {QStringLiteral("el"), QStringLiteral("Ελληνικά (Greek)")}, + {QStringLiteral("en@pirate"), QStringLiteral("English, arr! (Pirate English)")}, + {QStringLiteral("en_US"), QStringLiteral("English (United States)")}, + {QStringLiteral("es"), QStringLiteral("Español (Spanish)")}, + {QStringLiteral("et"), QStringLiteral("Eesti Keel (Estonian)")}, + {QStringLiteral("fi"), QStringLiteral("Suomi (Finnish)")}, + {QStringLiteral("fr"), QStringLiteral("Français (French)")}, + {QStringLiteral("hu"), QStringLiteral("Magyar (Hungarian)")}, + {QStringLiteral("it"), QStringLiteral("Italiano (Italian)")}, + {QStringLiteral("ja"), QStringLiteral("日本語 (Japanese)")}, + {QStringLiteral("ko"), QStringLiteral("한국어 (Korean)")}, + {QStringLiteral("nb"), QStringLiteral("Norsk Bokmål (Norwegian Bokmål)")}, + {QStringLiteral("nl"), QStringLiteral("Nederlands (Dutch)")}, + {QStringLiteral("pl"), QStringLiteral("Polski (Polish)")}, + {QStringLiteral("pt"), QStringLiteral("Português (Portuguese)")}, + {QStringLiteral("pt_BR"), QStringLiteral("Português do Brasil (Brazilian Portuguese)")}, + {QStringLiteral("ru"), QStringLiteral("Русский (Russian)")}, + {QStringLiteral("sr"), QStringLiteral("Srpski (Serbian)")}, + {QStringLiteral("sv"), QStringLiteral("Svenska (Swedish)")}, + {QStringLiteral("tr"), QStringLiteral("Türkçe (Turkish)")}, + {QStringLiteral("yue"), QStringLiteral("粵語 (Cantonese)")}, + {QStringLiteral("zh-Hans"), QStringLiteral("简体中文 (Chinese Simplified)")}, + {QStringLiteral("zh-Hant"), QStringLiteral("繁體中文 (Chinese Traditional)")}, + }; + + const QString displayName = displayNames.value(_lang); + if (!displayName.isEmpty()) { + return displayName; + } + + // A language added after this table still gets a label instead of the blank one this function exists + // to prevent. Qt knows no name for an unknown code and answers with an empty string. + const QString nativeName = QLocale(_lang).nativeLanguageName(); + return nativeName.isEmpty() ? _lang : nativeName; +} + +QString TranslationLoader::defaultLanguageCode() +{ + return QStringLiteral("en_US"); +} + TranslationLoadResult TranslationLoader::loadFrom(QTranslator &_translator, const QString &_nameHint, const QList &_directories) { diff --git a/libcockatrice_utility/libcockatrice/utility/translation_loader.h b/libcockatrice_utility/libcockatrice/utility/translation_loader.h index 645489696..53f419cf2 100644 --- a/libcockatrice_utility/libcockatrice/utility/translation_loader.h +++ b/libcockatrice_utility/libcockatrice/utility/translation_loader.h @@ -74,6 +74,26 @@ TranslationLoadResult loadQt(QTranslator &_translator, const QString &_lang); */ QList availableLanguages(const QString &_prefix, const QList &_directories); +/** + * @brief A human-readable name for a language, e.g. "Deutsch (German)". + * + * The names are data, not translations: a language selector has to be readable before any translation + * is loaded, and its labels must not depend on the translation of the language they name. + * + * @param _lang The language code, e.g. "de" or "pt_BR". + * @return The curated name; for a code it does not know, the native language name; the code itself when + * Qt has no name for it either; an empty string when _lang is empty. + */ +QString languageDisplayName(const QString &_lang); + +/** + * @brief The language a client runs with when none is configured, for preselecting a selector entry. + * + * English is the untranslated default and ships no translation file of its own, so a selector has nothing + * of its own to point at and uses the entry that carries an English translation instead. + */ +QString defaultLanguageCode(); + /** * @brief Loads the <_prefix>_<_lang>.qm translation file from _directories into _translator. * diff --git a/libcockatrice_utility/libcockatrice/utility/user_data_backup.cpp b/libcockatrice_utility/libcockatrice/utility/user_data_backup.cpp new file mode 100644 index 000000000..aa7095545 --- /dev/null +++ b/libcockatrice_utility/libcockatrice/utility/user_data_backup.cpp @@ -0,0 +1,405 @@ +#include "user_data_backup.h" + +#include +#include +#include +#include +#include +#include + +namespace UserDataBackup +{ + +namespace +{ + +const QString COMPLETE_MARKER = QStringLiteral("backup.complete"); +const QString INCOMPLETE_MARKER = QStringLiteral("backup.incomplete"); +const QString SOURCES_MANIFEST = QStringLiteral("backup.sources"); + +bool isMarkerFile(const QString &fileName) +{ + return fileName == COMPLETE_MARKER || fileName == INCOMPLETE_MARKER || fileName == SOURCES_MANIFEST; +} + +bool pathsEqual(const QString &a, const QString &b) +{ +#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) + return QDir::cleanPath(a).compare(QDir::cleanPath(b), Qt::CaseInsensitive) == 0; +#else + return QDir::cleanPath(a) == QDir::cleanPath(b); +#endif +} + +void writeCompletionMarker(const QString &sessionDir, bool complete) +{ + // Best effort: a missing marker only means isBackupSessionComplete() + // reports the session as incomplete. + QFile marker(QDir(sessionDir).absoluteFilePath(complete ? COMPLETE_MARKER : INCOMPLETE_MARKER)); + if (marker.open(QIODevice::WriteOnly | QIODevice::Text)) { + marker.write(complete ? "complete\n" : "incomplete\n"); + } +} + +QString uniqueTargetPath(const QString &sessionDir, const QString &fileName) +{ + QDir dir(sessionDir); + QString candidate = dir.absoluteFilePath(fileName); + if (!QFileInfo::exists(candidate)) { + return candidate; + } + const QFileInfo info(fileName); + const QString base = info.completeBaseName(); + const QString suffix = info.suffix(); + for (int i = 2;; ++i) { + QString numbered = suffix.isEmpty() ? QStringLiteral("%1 (%2)").arg(base).arg(i) + : QStringLiteral("%1 (%2).%3").arg(base).arg(i).arg(suffix); + candidate = dir.absoluteFilePath(numbered); + if (!QFileInfo::exists(candidate)) { + return candidate; + } + } +} + +} // namespace + +bool copyDirectoryRecursively(const QString &sourceDir, const QString &destDir) +{ + QDir source(sourceDir); + if (!source.exists()) { + return false; + } + if (!QDir().mkpath(destDir)) { + return false; + } + + bool ok = true; + QDirIterator it(sourceDir, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden, + QDirIterator::Subdirectories); + while (it.hasNext()) { + it.next(); + QString entryRel = source.relativeFilePath(it.filePath()); + QString target = QDir(destDir).absoluteFilePath(entryRel); + if (it.fileInfo().isSymLink()) { + // Copy the link itself best-effort without dereferencing it. QDirIterator does not + // follow symlinks here, so a symlink-to-directory must not take the isDir() branch + // below (which would create an empty dir and silently drop the contents). + QDir().mkpath(QFileInfo(target).absolutePath()); + if (QFile::exists(target) && !QFile::remove(target)) { + ok = false; + continue; + } + if (!QFile::copy(it.filePath(), target)) { + ok = false; + } + } else if (it.fileInfo().isDir()) { + if (!QDir().mkpath(target)) { + ok = false; + } + } else { + // Ensure the parent exists even if the iterator yields a file + // before its directory entry. + QDir().mkpath(QFileInfo(target).absolutePath()); + if (QFile::exists(target) && !QFile::remove(target)) { + ok = false; + continue; + } + if (!QFile::copy(it.filePath(), target)) { + ok = false; + } + } + } + return ok; +} + +bool createBackupSessionDir(const QString &backupRoot, QString *sessionDirOut) +{ + if (!QDir().mkpath(backupRoot)) { + return false; + } + const QString timestamp = QDateTime::currentDateTime().toString(QStringLiteral("yyyyMMdd-hhmmss-zzz")); + const QDir root(backupRoot); + QString sessionDir = root.absoluteFilePath(QStringLiteral("CockatriceDataBackup-") + timestamp); + for (int i = 1; QFileInfo::exists(sessionDir); ++i) { + sessionDir = root.absoluteFilePath(QStringLiteral("CockatriceDataBackup-%1-%2").arg(timestamp).arg(i)); + } + if (!QDir().mkpath(sessionDir)) { + return false; + } + if (sessionDirOut) { + *sessionDirOut = sessionDir; + } + return true; +} + +bool backupAtRiskPaths(const QStringList &sourcePaths, + const QString &backupRoot, + QStringList *backupLocationsOut, + QString *sessionDirOut) +{ + QStringList existing; + for (const QString &sourcePath : sourcePaths) { + if (QFileInfo::exists(sourcePath)) { + existing.append(sourcePath); + } + } + if (existing.isEmpty()) { + if (backupLocationsOut) { + backupLocationsOut->clear(); + } + if (sessionDirOut) { + sessionDirOut->clear(); + } + return false; + } + + QString sessionDir; + if (!createBackupSessionDir(backupRoot, &sessionDir)) { + if (backupLocationsOut) { + backupLocationsOut->clear(); + } + if (sessionDirOut) { + sessionDirOut->clear(); + } + return false; + } + if (sessionDirOut) { + *sessionDirOut = sessionDir; + } + + bool allOk = true; + QStringList locations; + for (const QString &sourcePath : existing) { + const QFileInfo source(sourcePath); + const QString backupLocation = uniqueTargetPath(sessionDir, source.fileName()); + bool ok; + if (source.isDir()) { + ok = copyDirectoryRecursively(sourcePath, backupLocation); + } else { + ok = QFile::copy(sourcePath, backupLocation); + } + // Report the location whenever a target was created, even for partial + // copies, so callers can point the user at what was salvaged. + if (QFileInfo::exists(backupLocation)) { + locations.append(backupLocation); + } else { + // Total failure with no target created (e.g. QFile::copy failed). + ok = false; + } + allOk = allOk && ok; + } + if (locations.isEmpty()) { + // Nothing salvaged: remove the session again so the empty directory + // neither blocks a retry via the recent-session reuse nor evicts a + // good session in pruneOldBackupSessions(). + QDir(sessionDir).removeRecursively(); + if (backupLocationsOut) { + backupLocationsOut->clear(); + } + if (sessionDirOut) { + sessionDirOut->clear(); + } + return false; + } + writeCompletionMarker(sessionDir, allOk); + // Record which sources this session covers so callers can reuse it only + // when it actually covers the current source set (see sessionCoversSources). + QFile manifest(QDir(sessionDir).absoluteFilePath(SOURCES_MANIFEST)); + if (manifest.open(QIODevice::WriteOnly | QIODevice::Text)) { + for (const QString &sourcePath : existing) { + manifest.write(QDir::cleanPath(sourcePath).toUtf8() + "\n"); + } + } + if (backupLocationsOut) { + *backupLocationsOut = locations; + } + return allOk; +} + +qint64 estimateBackupSize(const QStringList &sourcePaths, qint64 capBytes) +{ + qint64 total = 0; + for (const QString &sourcePath : sourcePaths) { + const QFileInfo source(sourcePath); + if (!source.exists()) { + continue; + } + if (source.isDir()) { + QDirIterator it(sourcePath, QDir::Files | QDir::Hidden | QDir::NoDotAndDotDot, + QDirIterator::Subdirectories); + while (it.hasNext()) { + it.next(); + total += it.fileInfo().size(); + if (total >= capBytes) { + return total; + } + } + } else { + total += source.size(); + if (total >= capBytes) { + return total; + } + } + } + return total; +} + +QString defaultBackupRoot() +{ + QString documents = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); + if (!documents.isEmpty()) { + return QDir(documents).absoluteFilePath("CockatriceBackups"); + } + QString appData = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); + if (!appData.isEmpty()) { + return QDir::cleanPath(QDir(appData).absoluteFilePath("../CockatriceBackups")); + } + return QDir::temp().absoluteFilePath("CockatriceBackups"); +} + +QStringList listBackupSessions(const QString &backupRoot) +{ + QDir root(backupRoot); + if (!root.exists()) { + return {}; + } + const QStringList names = root.entryList(QStringList() << QStringLiteral("CockatriceDataBackup-*"), + QDir::Dirs | QDir::NoDotAndDotDot, QDir::Name | QDir::Reversed); + QStringList sessions; + sessions.reserve(names.size()); + for (const QString &name : names) { + sessions.append(root.absoluteFilePath(name)); + } + return sessions; +} + +QString findRecentCoveringBackupSession(const QString &backupRoot, const QStringList &sourcePaths, int maxAgeHours) +{ + const QStringList sessions = listBackupSessions(backupRoot); + const QDateTime now = QDateTime::currentDateTime(); + for (const QString &session : sessions) { + const QFileInfo info(session); + if (!info.exists()) { + continue; + } + if (info.lastModified().secsTo(now) >= static_cast(maxAgeHours) * 3600) { + continue; + } + if (listSessionBackupLocations(session).isEmpty()) { + continue; + } + if (!sessionCoversSources(session, sourcePaths)) { + continue; + } + return session; + } + return {}; +} + +QString findRecentUsableBackupSession(const QString &backupRoot, const QStringList &sourcePaths, int maxAgeHours) +{ + if (sourcePaths.isEmpty()) { + return {}; + } + const QStringList sessions = listBackupSessions(backupRoot); + const QDateTime now = QDateTime::currentDateTime(); + for (const QString &session : sessions) { + const QFileInfo info(session); + if (!info.exists()) { + continue; + } + if (info.lastModified().secsTo(now) >= static_cast(maxAgeHours) * 3600) { + continue; + } + if (listSessionBackupLocations(session).isEmpty()) { + continue; + } + if (!isBackupSessionComplete(session)) { + continue; + } + if (!sessionCoversSources(session, sourcePaths)) { + continue; + } + return session; + } + return {}; +} + +bool isBackupSessionComplete(const QString &sessionDir) +{ + const QDir session(sessionDir); + if (!session.exists()) { + return false; + } + return QFileInfo::exists(session.absoluteFilePath(COMPLETE_MARKER)); +} + +void pruneOldBackupSessions(const QString &backupRoot, int keepCount) +{ + const QStringList sessions = listBackupSessions(backupRoot); + QStringList kept; + kept.reserve(sessions.size()); + for (const QString &session : sessions) { + // Empty sessions are junk from failed attempts: drop them outright so + // they can never evict a good session from the kept set. + if (listSessionBackupLocations(session).isEmpty()) { + QDir(session).removeRecursively(); + continue; + } + if (kept.size() < keepCount) { + kept.append(session); + continue; + } + QDir(session).removeRecursively(); + } +} + +QStringList listSessionBackupLocations(const QString &sessionDir) +{ + const QDir session(sessionDir); + if (!session.exists()) { + return {}; + } + const QStringList entries = session.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden); + QStringList locations; + locations.reserve(entries.size()); + for (const QString &entry : entries) { + if (isMarkerFile(entry)) { + continue; + } + locations.append(session.absoluteFilePath(entry)); + } + return locations; +} + +bool sessionCoversSources(const QString &sessionDir, const QStringList &sourcePaths) +{ + QFile manifest(QDir(sessionDir).absoluteFilePath(SOURCES_MANIFEST)); + if (!manifest.open(QIODevice::ReadOnly | QIODevice::Text)) { + // No readable manifest (e.g. the write failed): coverage cannot be + // verified, so report not-covered and let the caller take a fresh copy. + return false; + } + QStringList recorded; + while (!manifest.atEnd()) { + const QString line = QString::fromUtf8(manifest.readLine()).trimmed(); + if (!line.isEmpty()) { + recorded.append(line); + } + } + for (const QString &source : sourcePaths) { + bool found = false; + for (const QString &entry : recorded) { + if (pathsEqual(source, entry)) { + found = true; + break; + } + } + if (!found) { + return false; + } + } + return true; +} + +} // namespace UserDataBackup diff --git a/libcockatrice_utility/libcockatrice/utility/user_data_backup.h b/libcockatrice_utility/libcockatrice/utility/user_data_backup.h new file mode 100644 index 000000000..4a4dc1d7f --- /dev/null +++ b/libcockatrice_utility/libcockatrice/utility/user_data_backup.h @@ -0,0 +1,132 @@ +/** + * @file user_data_backup.h + * @ingroup Utility + * @brief Best-effort safety copies of user data that lives inside the install directory. + * + * Background: Windows uninstallers shipped in 3.1.0/3.1.1 ran a blanket + * "RMDir /r $INSTDIR". For a portable install that deletes $INSTDIR\data + * (decks, settings, pictures) along with the payload, and for a normal install + * it deletes anything the user pointed into $INSTDIR. The fixed uninstaller + * preserves foreign files, but the destructive one is already on disk on + * affected machines, so the client keeps a safety copy outside the install + * directory before warning the user. Detection of at-risk paths lives in + * PathsSettings::pathsInsideDir; this helper only copies. + */ + +#ifndef USER_DATA_BACKUP_H +#define USER_DATA_BACKUP_H + +#include +#include + +namespace UserDataBackup +{ + +/** @brief Above this total, the client skips the automatic startup backup and asks for a manual copy. + * Kept small on purpose: the copy runs synchronously on the UI thread at startup. */ +constexpr qint64 MAX_AUTO_BACKUP_BYTES = 200LL * 1024 * 1024; + +/** + * @brief Recursively copies @p sourceDir into @p destDir, creating it as needed. + * @returns true when every file was copied (or there was nothing to copy). + * Symlinks (file or directory) are copied best-effort via QFile::copy without + * dereferencing them; a dangling link fails that entry only. + */ +bool copyDirectoryRecursively(const QString &sourceDir, const QString &destDir); + +/** + * @brief Creates a fresh timestamped session directory under @p backupRoot. + * @param backupRoot directory OUTSIDE the install tree that will hold timestamped backups. + * @param sessionDirOut receives the created directory on success. + * @returns true on success. Millisecond timestamps plus a numeric suffix keep rapid + * successive calls (e.g. one per installer phase) from sharing a directory. + */ +bool createBackupSessionDir(const QString &backupRoot, QString *sessionDirOut); + +/** + * @brief Copies several at-risk paths into a single timestamped session directory. + * @param sourcePaths files or directories holding user data; missing entries are skipped. + * @param backupRoot directory OUTSIDE the install tree that will hold timestamped backups. + * @param backupLocationsOut receives one entry per source that produced a backup target + * (including partial copies). + * @param sessionDirOut optionally receives the shared session directory; set to empty + * when nothing was backed up (all sources missing, or every copy failed outright). + * @returns true when every existing source was copied completely. + * A false return with a non-empty @p backupLocationsOut means a partial backup exists. + * When nothing at all could be salvaged the session directory is removed again, so a + * failed attempt never leaves an empty session behind that would block retries. + * Completeness is recorded with a marker file; see isBackupSessionComplete(). + */ +bool backupAtRiskPaths(const QStringList &sourcePaths, + const QString &backupRoot, + QStringList *backupLocationsOut, + QString *sessionDirOut = nullptr); + +/** + * @brief Estimates the total bytes that backing up @p sourcePaths would copy. + * @param capBytes stop counting once the total exceeds this; the returned value is then + * at least @p capBytes (used to skip the scan early on huge trees). + * Missing entries contribute 0. Best effort only. + */ +qint64 estimateBackupSize(const QStringList &sourcePaths, qint64 capBytes = MAX_AUTO_BACKUP_BYTES); + +/** + * @brief Default backup root guaranteed to live outside the install directory. + */ +QString defaultBackupRoot(); + +/** + * @brief Lists existing backup session directories under @p backupRoot, newest first. + */ +QStringList listBackupSessions(const QString &backupRoot); + +/** + * @brief Returns the newest session that is non-empty, complete and covering @p sourcePaths, or empty. + * + * An incomplete newest session (partial failure) or one covering a different + * source set does not block reuse of an older good session. + */ +QString findRecentUsableBackupSession(const QString &backupRoot, const QStringList &sourcePaths, int maxAgeHours = 24); + +/** + * @brief Returns the newest non-empty session covering @p sourcePaths, regardless of completeness. + * + * Used as a fallback pointer when a fresh copy fails entirely, so the dialog can still + * point at salvaged content. Completeness is left to the caller to report. + */ +QString +findRecentCoveringBackupSession(const QString &backupRoot, const QStringList &sourcePaths, int maxAgeHours = 24); + +/** + * @brief Reports whether @p sessionDir holds a complete backup. + * @returns true when the session carries the complete marker, false otherwise + * (partial backups and empty/missing sessions). + */ +bool isBackupSessionComplete(const QString &sessionDir); + +/** + * @brief Deletes oldest sessions keeping only the newest @p keepCount ones. + * Empty sessions are junk from failed attempts and are always removed, so they + * can never evict a good session from the kept set. + */ +void pruneOldBackupSessions(const QString &backupRoot, int keepCount = 5); + +/** + * @brief Lists the backup targets inside an existing session directory. + * @returns absolute paths of the entries, for reuse messages without a new copy. + * Completeness marker and source manifest files are internal bookkeeping and are never listed. + */ +QStringList listSessionBackupLocations(const QString &sessionDir); + +/** + * @brief Reports whether @p sessionDir was recorded as covering all of @p sourcePaths. + * @returns true when the session's source manifest lists every entry of @p sourcePaths + * (normalized via QDir::cleanPath, case-insensitive on Windows/macOS). Sessions + * without a readable manifest return false so a changed source set always triggers + * a fresh copy instead of reusing a backup that may not cover it. + */ +bool sessionCoversSources(const QString &sessionDir, const QStringList &sourcePaths); + +} // namespace UserDataBackup + +#endif // USER_DATA_BACKUP_H diff --git a/oracle/CMakeLists.txt b/oracle/CMakeLists.txt index 0df345812..587463572 100644 --- a/oracle/CMakeLists.txt +++ b/oracle/CMakeLists.txt @@ -63,7 +63,7 @@ set(oracle_RESOURCES oracle.qrc) # ------------------------ # Qt resources # ------------------------ -qt6_add_resources(oracle_RESOURCES_RCC ${oracle_RESOURCES}) +qt_add_resources(oracle_RESOURCES_RCC ${oracle_RESOURCES}) # ------------------------ # Include directories @@ -108,7 +108,7 @@ set(ORACLE_UNIX_QM_INSTALL_DIR "share/oracle/translations") set(ORACLE_WIN32_QM_INSTALL_DIR "translations") # Qt6 Translations are linked after the executable is created in manual mode -qt6_add_executable( +qt_add_executable( oracle WIN32 MACOSX_BUNDLE @@ -283,7 +283,7 @@ endif() if(Qt6LinguistTools_FOUND) #Qt6 Translations happen after the executable is built up if(UPDATE_TRANSLATIONS) - qt6_add_translations( + qt_add_translations( oracle TS_FILES ${oracle_TS} @@ -293,7 +293,7 @@ if(Qt6LinguistTools_FOUND) oracle_QM ) else() - qt6_add_translations(oracle TS_FILES ${oracle_TS} QM_FILES_OUTPUT_VARIABLE oracle_QM) + qt_add_translations(oracle TS_FILES ${oracle_TS} QM_FILES_OUTPUT_VARIABLE oracle_QM) endif() if(UNIX) @@ -319,4 +319,4 @@ if(Qt6LinguistTools_FOUND) endforeach() endif() -qt6_finalize_target(oracle) +qt_finalize_target(oracle) diff --git a/oracle/src/oracleimporter.cpp b/oracle/src/oracleimporter.cpp index 3382e36d6..477c54d21 100644 --- a/oracle/src/oracleimporter.cpp +++ b/oracle/src/oracleimporter.cpp @@ -486,7 +486,7 @@ int OracleImporter::importCardsFromSet(const CardSetPtr ¤tSet, const QJson } } - PrintingInfo printingInfo(currentSet, LazyPropertiesHash(printingProps)); + PrintingInfo printingInfo(currentSet, LazyPropertiesHash::serializedFrom(printingProps)); QString numComponent; const QString numProperty = printingInfo.getProperty("num"); diff --git a/oracle/src/oraclewizard.cpp b/oracle/src/oraclewizard.cpp index d2b3a71a4..c33d821a5 100644 --- a/oracle/src/oraclewizard.cpp +++ b/oracle/src/oraclewizard.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -31,9 +32,6 @@ class QWidget; OracleWizard::OracleWizard(QWidget *parent) : QWizard(parent) { - // define a dummy context that will be used where needed - QString dummy = QT_TRANSLATE_NOOP("i18n", "English"); - #ifdef Q_OS_WIN setWizardStyle(QWizard::ModernStyle); #endif @@ -53,7 +51,7 @@ OracleWizard::OracleWizard(QWidget *parent) : QWizard(parent) // foreignData for any other language is never imported. importer->setCardLang(SettingsCache::instance().cardsDisplay().getCardLang()); - nam = new QNetworkAccessManager(this); + nam = NetworkManagerFactory::create(this); QList pages; diff --git a/oracle/src/pages.cpp b/oracle/src/pages.cpp index ed8e04d1a..4c664a38c 100644 --- a/oracle/src/pages.cpp +++ b/oracle/src/pages.cpp @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -39,7 +38,6 @@ #include #include #include -#include #include #include #include @@ -203,16 +201,23 @@ IntroPage::IntroPage(QWidget *parent) : OracleWizardPage(parent) QStringList languageCodes = findQmFiles(); for (const QString &code : languageCodes) { - QString langName = languageName(code); - languageBox->addItem(langName, code); + languageBox->addItem(TranslationLoader::languageDisplayName(code), code); } - QString setLanguage = QCoreApplication::translate("i18n", DEFAULT_LANG_NAME); - int index = languageBox->findText(setLanguage, Qt::MatchExactly); - if (index == -1) { - qWarning() << "could not find language" << setLanguage; - } else { + // Select the language the wizard runs with: its own entry, the base language for a variant without a + // file of its own (de_CH -> de), and the default language for an unset setting, which names no file. + const QString configuredLanguage = SettingsCache::instance().personal().getLang(); + int index = languageBox->findData(configuredLanguage); + if (index < 0) { + index = languageBox->findData(configuredLanguage.section('_', 0, 0)); + } + if (index < 0) { + index = languageBox->findData(TranslationLoader::defaultLanguageCode()); + } + if (index >= 0) { languageBox->setCurrentIndex(index); + } else { + qWarning() << "could not find language" << configuredLanguage; } connect(languageBox, qOverload(&QComboBox::currentIndexChanged), this, &IntroPage::languageBoxChanged); @@ -239,18 +244,6 @@ QStringList IntroPage::findQmFiles() TranslationLoader::applicationTranslationPaths(translationPath)); } -QString IntroPage::languageName(const QString &lang) -{ - QTranslator qTranslator; - - // Only the language name is needed here, so the file is loaded without logging a line per - // language: findQmFiles() asked for languages whose files exist in the first place. - TranslationLoader::loadFrom(qTranslator, translationPrefix + "_" + lang, - TranslationLoader::applicationTranslationPaths(translationPath)); - - return qTranslator.translate("i18n", DEFAULT_LANG_NAME); -} - void IntroPage::languageBoxChanged(int index) { SettingsCache::instance().personal().setLang(languageBox->itemData(index).toString()); diff --git a/oracle/src/pages.h b/oracle/src/pages.h index 221f032d3..0530010ba 100644 --- a/oracle/src/pages.h +++ b/oracle/src/pages.h @@ -31,7 +31,6 @@ public: private: QStringList findQmFiles(); - QString languageName(const QString &lang); private: QLabel *label, *languageLabel, *versionLabel; diff --git a/oracle/translations/oracle_ca.ts b/oracle/translations/oracle_ca.ts new file mode 100644 index 000000000..f2e534409 --- /dev/null +++ b/oracle/translations/oracle_ca.ts @@ -0,0 +1,626 @@ + + + IntroPage + + + Introduction + Introducció + + + + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. + Aquest assistent importarà les llistes de col·leccions, cartes, i fitxes que seran utilitzades per Cockatrice. + + + + Interface language: + Llenguatge de la interfície: + + + + Version: + Versió: + + + + LoadSetsPage + + + Source selection + Selecció de font + + + + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. + + + + + Download URL: + Descarregar URL: + + + + Local file: + Fitxer local: + + + + Restore default URL + Restablir URL per defecte + + + + Choose file... + Tria un fitxer... + + + + Load sets file + + + + + Sets file (%1) + Sets JSON file (%1) + + + + + + + + + Error + Error + + + + The provided URL is not valid. + L'URL proporcionat no és vàlid. + + + + Please choose a file. + Si us plau trieu un fitxer. + + + + Cannot open file '%1'. + No es pot obrir el fitxer '%1'. + + + + Network error: %1. + Error de xarxa: %1. + + + + Parsing file + Analitzant el fitxer + + + + Xz extraction failed. + L'extracció Xz ha fallat. + + + + Sorry, this version of Oracle does not support xz compressed files. + Ho sento, aquesta versió d'Oracle no admet fitxers comprimits amb xz. + + + + Failed to open Zip archive: %1. + No s'ha pogut obrir l'arxiu Zip: %1. + + + + Zip extraction failed: the Zip archive doesn't contain exactly one file. + L'extracció del fitxer Zip ha fallat: l'arxiu Zip no conté exactament un fitxer. + + + + Zip extraction failed: %1. + L'extracció del fitxer Zip ha fallat: %1. + + + + Sorry, this version of Oracle does not support zipped files. + Ho sento, aquesta versió d'Oracle no admet fitxers comprimits. + + + + Failed to interpret downloaded data. + No s'han pogut interpretar les dades descarregades. + + + + Downloading (0 MB) + Descarregant (0 MB) + + + + Downloading (%1 MB) + Descarregant (%1 MB) + + + + Parsing file (%1%) + Analitzant el fitxer (%1%) + + + + Extracting file... + Extraient el fitxer... + + + + Do you want to download the uncompressed file instead? + Vols descarregar el fitxer sense comprimir en comptes d'això? + + + + + The file was retrieved successfully, but it does not contain any sets data. + + + + + Parsing file (100%) + Analitzant el fitxer (100%) + + + + LoadSpoilersPage + + + Save spoiler database + Desa la base de dades d'espòilers + + + + XML; spoiler database (*.xml) + XML; base de dades d'espòilers (*.xml) + + + + spoiler + espòiler + + + + Spoilers import + + + + + Please specify a compatible source for spoiler data. + + + + + Download URL: + Descarregar URL: + + + + Local file: + Fitxer local: + + + + Restore default URL + Restablir URL per defecte + + + + Choose file... + Tria un fitxer... + + + + The spoiler database will be saved at the following location: + + + + + Save to a custom path (not recommended) + Desa en una ruta personalitzada (no recomanat) + + + + LoadTokensPage + + + Save token database + Desa la base de dades de fitxes + + + + XML; token database (*.xml) + XML; base de dades de fitxes (*.xml) + + + + tokens + fitxes + + + + Tokens import + Importa fitxes + + + + Please specify a compatible source for token data. + Si us plau, especifiqueu una font compatible per a les dades de les fitxes. + + + + Download URL: + Descarregar URL: + + + + Local file: + Fitxer local: + + + + Restore default URL + Restablir URL per defecte + + + + Choose file... + Tria un fitxer... + + + + The token database will be saved at the following location: + La base de dades de fitxes es desarà a la ubicació següent: + + + + Save to a custom path (not recommended) + Desa en una ruta personalitzada (no recomanat) + + + + OracleImporter + + + Dummy set containing tokens + + + + + OracleWizard + + + Oracle Importer + Importador d'Oracle + + + + OutroPage + + + Finished + Finalitzat + + + + The wizard has finished. + + + + + You can now start using Cockatrice with the newly updated cards. + Ara ja pots començar a utilitzar Cockatrice amb les cartes recentment actualitzades. + + + + If the card databases don't reload automatically, restart the Cockatrice client. + Si les bases de dades de cartes no es recarreguen automàticament, reinicia el client de Cockatrice. + + + + SaveSetsPage + + + + Error + Error + + + + + No set has been imported. + No s'ha importat cap col·lecció. + + + + Sets imported + Col·leccions importades + + + + A Cockatrice card database file of %1 MB has been downloaded. + S'ha descarregat un fitxer de base de dades de cartes de Cockatrice de %1 MB + + + + The following sets have been found: + S'han trobat les col·leccions següents: + + + + Press "Save" to store the imported cards in the Cockatrice database. + Prem "Desa" per a desar les cartes importades a la base de dades de Cockatrice. + + + + The card database will be saved at the following location: + La base de dades de cartes es desarà a la ubicació següent: + + + + Save to a custom path (not recommended) + Desa en una ruta personalitzada (no recomanat) + + + + &Save + &Desa + + + + + Import finished: %1 cards. + Importació finalitzada: %1 cartes. + + + + + %1: %2 cards imported + %1: %2 cartes importades + + + + Save card database + Desa la base de dades de cartes + + + + XML; card database (*.xml) + XML; base de dades de cartes (*.xml) + + + + The file could not be saved to %1 + No s'ha pogut desar el fitxer a %1 + + + + SimpleDownloadFilePage + + + Load %1 file + + + + + %1 file (%1) + + + + + + + + + Error + Error + + + + The provided URL is not valid: + L'URL proporcionat no és vàlid. + + + + Downloading (0 MB) + Downloading (0MB) + Descarregant (0 MB) + + + + Please choose a file. + Si us plau trieu un fitxer. + + + + Cannot open file '%1'. + No es pot obrir el fitxer '%1'. + + + + Downloading (%1MB) + Descarregant (%1 MB) + + + + Network error: %1. + Error de xarxa: %1. + + + + The file could not be saved to %1 + No s'ha pogut desar el fitxer a %1 + + + + UnZip + + + ZIP operation completed successfully. + L'operació ZIP s'ha completat correctament. + + + + Failed to initialize or load zlib library. + No s'ha pogut inicialitzar o carregar la llibreria zlib. + + + + zlib library error. + Error de la llibreria zlib. + + + + Unable to create or open file. + No es pot crear ni obrir el fitxer + + + + Partially corrupted archive. Some files might be extracted. + + + + + Corrupted archive. + + + + + Wrong password. + Contrasenya incorrecta. + + + + No archive has been created yet. + Encara no s'ha creat cap arxiu. + + + + File or directory does not exist. + El fitxer o el directori no existeix. + + + + File read error. + Error de lectura del fitxer. + + + + File write error. + Error d'escriptura del fitxer + + + + File seek error. + Error de cerca del fitxer. + + + + Unable to create a directory. + No es pot crear un directori. + + + + Invalid device. + Dispositiu no vàlid. + + + + Invalid or incompatible zip archive. + Arxiu zip no vàlid o incompatible. + + + + Inconsistent headers. Archive might be corrupted. + + + + + Unknown error. + Error desconegut. + + + + Zip + + + ZIP operation completed successfully. + L'operació ZIP s'ha completat correctament. + + + + Failed to initialize or load zlib library. + No s'ha pogut inicialitzar o carregar la llibreria zlib. + + + + zlib library error. + Error de la llibreria zlib. + + + + Unable to create or open file. + No es pot crear ni obrir el fitxer + + + + No archive has been created yet. + Encara no s'ha creat cap arxiu. + + + + File or directory does not exist. + El fitxer o el directori no existeix. + + + + File read error. + Error de lectura del fitxer. + + + + File write error. + Error d'escriptura del fitxer + + + + File seek error. + Error de cerca del fitxer. + + + + Unknown error. + Error desconegut. + + + + i18n + + + English + Català (Catalan) + + + + main + + + Only run in spoiler mode + + + + + Run in no-confirm background mode + + + + \ No newline at end of file diff --git a/oracle/translations/oracle_de.ts b/oracle/translations/oracle_de.ts index 196315a36..faf08d5ce 100644 --- a/oracle/translations/oracle_de.ts +++ b/oracle/translations/oracle_de.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Einführung - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Dieser Assistent wird eine Liste aller Editionen, Karten und Spielsteine importieren, die von Cockatrice genutzt werden. - + Interface language: Interfacesprache: - + Version: Version: @@ -25,192 +25,206 @@ LoadSetsPage - + Source selection Quellenauswahl - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Bitte geben Sie eine kompatible Quelle für die Liste der Editionen und Karten an. Sie können eine URL-Adresse zum Herunterladen angeben oder eine existierende Datei von Ihrem Computer verwenden. - + Download URL: Download URL: - + Local file: Lokale Datei: - + Restore default URL Standard-URL wiederherstellen - + Choose file... Datei auswählen... - + Load sets file Editionsdatei wird geladen - + Sets file (%1) Sets JSON file (%1) Sets Datei (%1) - - - - - - - + + + + + Error Fehler - + The provided URL is not valid. Die eingegebene URL ist nicht gültig. - - Downloading (0MB) - Herunterladen (0MB) - - - + Please choose a file. Bitte wähle eine Datei aus. - + Cannot open file '%1'. Datei '%1' kann nicht geöffnet werden. - - Downloading (%1MB) - Herunterladen (%1MB) - - - + Network error: %1. Netzwerkfehler: %1. - + Parsing file Datei wird verarbeitet - + Xz extraction failed. Fehler beim Extrahieren der xz Datei. - + Sorry, this version of Oracle does not support xz compressed files. Es tut uns Leid, diese Version von Oracle unterstützt keine xz komprimierten Dateien. - + Failed to open Zip archive: %1. Fehler beim Öffnen des Zip Archivs: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Fehler beim Extrahieren: Das Zip Archiv enthält mehr als eine Datei. - + Zip extraction failed: %1. Fehler beim Extrahieren: %1. - + Sorry, this version of Oracle does not support zipped files. Es tut uns Leid, diese Version von Oracle unterstützt keine Zip Archive. - + Failed to interpret downloaded data. Interpretation der heruntergeladenen Daten fehlgeschlagen. - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? Möchten Sie stattdessen die unkomprimierte Datei herunterladen? - + + The file was retrieved successfully, but it does not contain any sets data. Die Datei wurde erfolgreich abgerufen, sie enthält aber keine Editionsdaten. + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database Speichere Spoilerdatenbank - + XML; spoiler database (*.xml) XML; Spoilerdatenbank (*.xml) - + spoiler Spoiler - + Spoilers import Spoilerimport - + Please specify a compatible source for spoiler data. Bitte geben Sie eine kompatible Quelle für Spoilerdaten an. - + Download URL: Download URL: - + Local file: Lokale Datei: - + Restore default URL Standard-URL wiederherstellen - + Choose file... Datei auswählen... - + The spoiler database will be saved at the following location: Die Spoilerdatenbank wird in folgendem Pfad gespeichert: - + Save to a custom path (not recommended) Speichere in benutzerdefiniertem Pfad (nicht empfohlen) @@ -218,57 +232,57 @@ LoadTokensPage - + Save token database Speichere Spielsteindatenbank - + XML; token database (*.xml) XML; Spielsteindatenbank (*.xml) - + tokens Spielsteine - + Tokens import Spielsteinimport - + Please specify a compatible source for token data. Bitte geben Sie eine kompatible Quelle für Spielsteindaten an. - + Download URL: Download URL: - + Local file: Lokale Datei: - + Restore default URL Standard-URL wiederherstellen - + Choose file... Datei auswählen... - + The token database will be saved at the following location: Die Spielsteindatenbank wird in folgendem Pfad gespeichert: - + Save to a custom path (not recommended) Speichere in benutzerdefiniertem Pfad (nicht empfohlen) @@ -276,7 +290,7 @@ OracleImporter - + Dummy set containing tokens Platzhalter Edition mit Spielsteinen @@ -284,7 +298,7 @@ OracleWizard - + Oracle Importer Oracle Importer @@ -292,22 +306,22 @@ OutroPage - + Finished Fertig - + The wizard has finished. Der Wizard ist fertig. - + You can now start using Cockatrice with the newly updated cards. Sie können nun Cockatrice mit den aktuellen Karten verwenden. - + If the card databases don't reload automatically, restart the Cockatrice client. Falls die Datenbanken nicht automatisch neu geladen werden, starten Sie bitte Cockatrice neu. @@ -315,73 +329,76 @@ SaveSetsPage - - + + Error Fehler - + + No set has been imported. Es wurden keine Editionen importiert. - + Sets imported Editionen wurden importiert - - A cockatrice database file of %1 MB has been downloaded. - Eine Cockatrice-Datenbankdatei der Größe %1 MB wurde heruntergeladen. + + A Cockatrice card database file of %1 MB has been downloaded. + - + The following sets have been found: Die folgenden Sets wurden gefunden: - + Press "Save" to store the imported cards in the Cockatrice database. Drücken Sie "Speichern", um die importierten Karten in der Datenbank zu speichern. - + The card database will be saved at the following location: Die Kartendatenbank wird in folgendem Pfad gespeichert: - + Save to a custom path (not recommended) Speichere in benutzerdefiniertem Pfad (nicht empfohlen) - + &Save &Speichern - + + Import finished: %1 cards. Importieren abgeschlossen: %1 Karten. - + + %1: %2 cards imported %1: %2 Karten importiert. - + Save card database Kartendatenbank speichern - + XML; card database (*.xml) XML; Kartendatenbank (*.xml) - + The file could not be saved to %1 Die Datei konnte nicht gespeichert werden: %1 @@ -415,8 +432,9 @@ - Downloading (0MB) - Herunterladen (0MB) + Downloading (0 MB) + Downloading (0MB) + @@ -588,7 +606,7 @@ i18n - + English Deutsch (German) @@ -596,12 +614,12 @@ main - + Only run in spoiler mode Nur im Spoiler Modus ausführen - + Run in no-confirm background mode Ausführen im Hintergrund-Modus ohne Bestätigung diff --git a/oracle/translations/oracle_el.ts b/oracle/translations/oracle_el.ts index d3a2e081c..8b07a5ace 100644 --- a/oracle/translations/oracle_el.ts +++ b/oracle/translations/oracle_el.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Εισαγωγή - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Αυτός ο αυτόματος οδηγός θα εισάγει τη λίστα των σετ, καρτών και δειγμάτων (tokens) που θα χρησιμοποιηθούν από το Cockatrice. - + Interface language: - + Version: Έκδοση: @@ -25,192 +25,206 @@ LoadSetsPage - + Source selection Επιλογή πηγής - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. - + Download URL: URL λήψης: - + Local file: Τοπικό αρχείο: - + Restore default URL Επαναφορά προκαθορισμένης διεύθυνσης URL - + Choose file... Επιλέξτε αρχείο... - + Load sets file Φόρτωση αρχείων - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + Error Σφάλμα - + The provided URL is not valid. Η παρεχόμενη διεύθυνση URL δεν είναι έγκυρη. - - Downloading (0MB) - Λήψη (0MB) - - - + Please choose a file. Παρακαλώ διαλέξτε έναν αρχείο. - + Cannot open file '%1'. Δεν είναι δυνατό να ανοίξει το αρχείο '% 1'. - - Downloading (%1MB) - Λήψη (% 1MB) - - - + Network error: %1. Σφάλμα δικτύου: % 1. - + Parsing file Ανάλυση αρχείου - + Xz extraction failed. - + Sorry, this version of Oracle does not support xz compressed files. - + Failed to open Zip archive: %1. Αποτυχία ανοίγματος αρχείου Zip:% 1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Η εξαγωγή zip απέτυχε: το αρχείο Zip δεν περιέχει ακριβώς ένα αρχείο. - + Zip extraction failed: %1. Η εξαγωγή zip απέτυχε: % 1. - + Sorry, this version of Oracle does not support zipped files. Λυπούμαστε, αυτή η έκδοση του Oracle δεν υποστηρίζει αρχεία τύπου zipped. - + Failed to interpret downloaded data. - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? - + + The file was retrieved successfully, but it does not contain any sets data. Το αρχείο ανακτήθηκε με επιτυχία, αλλά δεν περιέχει σύνολα δεδομένων. + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database - + XML; spoiler database (*.xml) - + spoiler - + Spoilers import - + Please specify a compatible source for spoiler data. - + Download URL: - + Local file: - + Restore default URL - + Choose file... - + The spoiler database will be saved at the following location: - + Save to a custom path (not recommended) @@ -218,57 +232,57 @@ LoadTokensPage - + Save token database - + XML; token database (*.xml) - + tokens - + Tokens import - + Please specify a compatible source for token data. - + Download URL: URL λήψης: - + Local file: - + Restore default URL Επαναφορά προκαθορισμένης διεύθυνσης URL - + Choose file... - + The token database will be saved at the following location: - + Save to a custom path (not recommended) @@ -276,7 +290,7 @@ OracleImporter - + Dummy set containing tokens Εικονικό σετ που περιέχει tokens @@ -284,7 +298,7 @@ OracleWizard - + Oracle Importer Εισαγωγέας Oracle @@ -292,22 +306,22 @@ OutroPage - + Finished - + The wizard has finished. - + You can now start using Cockatrice with the newly updated cards. - + If the card databases don't reload automatically, restart the Cockatrice client. @@ -315,73 +329,76 @@ SaveSetsPage - - + + Error Σφάλμα - + + No set has been imported. Δεν έχει εισαχθεί κανένα σετ. - + Sets imported Εισαγόμενα σετς - - A cockatrice database file of %1 MB has been downloaded. + + A Cockatrice card database file of %1 MB has been downloaded. - + The following sets have been found: - + Press "Save" to store the imported cards in the Cockatrice database. - + The card database will be saved at the following location: - + Save to a custom path (not recommended) - + &Save - + + Import finished: %1 cards. Η εισαγωγή ολοκληρώθηκε: % 1 κάρτες. - + + %1: %2 cards imported %1: %2 κάρτες εισήχθησαν - + Save card database Αποθήκευση κάρτας βάσης δεδομένων - + XML; card database (*.xml) XML; κάρτα βάσης δεδομένων (* .xml) - + The file could not be saved to %1 Δεν ήταν δυνατή η αποθήκευση του αρχείου στο %1 @@ -414,7 +431,8 @@ - Downloading (0MB) + Downloading (0 MB) + Downloading (0MB) @@ -587,7 +605,7 @@ i18n - + English Ελληνικά (Greek) @@ -595,12 +613,12 @@ main - + Only run in spoiler mode - + Run in no-confirm background mode diff --git a/oracle/translations/oracle_en@pirate.ts b/oracle/translations/oracle_en@pirate.ts index 13a57750b..a31b9d332 100644 --- a/oracle/translations/oracle_en@pirate.ts +++ b/oracle/translations/oracle_en@pirate.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. - + Interface language: - + Version: @@ -25,177 +25,206 @@ LoadSetsPage - + Source selection - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. - + Download URL: - + Local file: - + Restore default URL - + Choose file... - + Load sets file - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + Error Cap'n? Thar be a problem - + The provided URL is not valid. - - Downloading (0MB) - - - - + Please choose a file. - + Cannot open file '%1'. - - Downloading (%1MB) - - - - + Network error: %1. Cap'n? Thar be a problem wi' t' smoke signals: %1. - + Parsing file - + Xz extraction failed. - + Sorry, this version of Oracle does not support xz compressed files. - + Failed to open Zip archive: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. - + Zip extraction failed: %1. - + Sorry, this version of Oracle does not support zipped files. - + Failed to interpret downloaded data. - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? - + + The file was retrieved successfully, but it does not contain any sets data. + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database - + XML; spoiler database (*.xml) - + + spoiler + + + + Spoilers import - + Please specify a compatible source for spoiler data. - + Download URL: - + + Local file: + + + + Restore default URL - + + Choose file... + + + + The spoiler database will be saved at the following location: - + Save to a custom path (not recommended) @@ -203,42 +232,57 @@ LoadTokensPage - + Save token database - + XML; token database (*.xml) - + + tokens + + + + Tokens import - + Please specify a compatible source for token data. - + Download URL: - + + Local file: + + + + Restore default URL - + + Choose file... + + + + The token database will be saved at the following location: - + Save to a custom path (not recommended) @@ -246,7 +290,7 @@ OracleImporter - + Dummy set containing tokens @@ -262,22 +306,22 @@ OutroPage - + Finished - + The wizard has finished. - + You can now start using Cockatrice with the newly updated cards. - + If the card databases don't reload automatically, restart the Cockatrice client. @@ -285,73 +329,76 @@ SaveSetsPage - - + + Error Cap'n? Thar be a problem - + + No set has been imported. - + Sets imported - - A cockatrice database file of %1 MB has been downloaded. + + A Cockatrice card database file of %1 MB has been downloaded. - + The following sets have been found: - + Press "Save" to store the imported cards in the Cockatrice database. - + The card database will be saved at the following location: - + Save to a custom path (not recommended) - + &Save - + + Import finished: %1 cards. - + + %1: %2 cards imported - + Save card database - + XML; card database (*.xml) - + The file could not be saved to %1 @@ -359,34 +406,57 @@ SimpleDownloadFilePage - - - + + Load %1 file + + + + + %1 file (%1) + + + + + + + + Error - + The provided URL is not valid: - - Downloading (0MB) + + Downloading (0 MB) + Downloading (0MB) - + + Please choose a file. + + + + + Cannot open file '%1'. + + + + Downloading (%1MB) - + Network error: %1. - + The file could not be saved to %1 @@ -535,7 +605,7 @@ i18n - + English English, arr! (Pirate English) @@ -543,12 +613,12 @@ main - + Only run in spoiler mode - + Run in no-confirm background mode diff --git a/oracle/translations/oracle_en_US.ts b/oracle/translations/oracle_en_US.ts index 394e10b92..b91d56230 100644 --- a/oracle/translations/oracle_en_US.ts +++ b/oracle/translations/oracle_en_US.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Introduction - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. - + Interface language: Interface language: - + Version: Version: @@ -25,192 +25,206 @@ LoadSetsPage - + Source selection Source selection - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. - + Download URL: Download URL: - + Local file: Local file: - + Restore default URL Restore default URL - + Choose file... Choose file... - + Load sets file Load sets file - + Sets file (%1) Sets JSON file (%1) Sets file (%1) - - - - - - - + + + + + Error Error - + The provided URL is not valid. The provided URL is not valid. - - Downloading (0MB) - Downloading (0MB) - - - + Please choose a file. Please choose a file. - + Cannot open file '%1'. Cannot open file '%1'. - - Downloading (%1MB) - Downloading (%1MB) - - - + Network error: %1. Network error: %1. - + Parsing file Parsing file - + Xz extraction failed. Xz extraction failed. - + Sorry, this version of Oracle does not support xz compressed files. Sorry, this version of Oracle does not support xz compressed files. - + Failed to open Zip archive: %1. Failed to open Zip archive: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Zip extraction failed: the Zip archive doesn't contain exactly one file. - + Zip extraction failed: %1. Zip extraction failed: %1. - + Sorry, this version of Oracle does not support zipped files. Sorry, this version of Oracle does not support zipped files. - + Failed to interpret downloaded data. Failed to interpret downloaded data. - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? Do you want to download the uncompressed file instead? - + + The file was retrieved successfully, but it does not contain any sets data. The file was retrieved successfully, but it does not contain any sets data. + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database Save spoiler database - + XML; spoiler database (*.xml) XML; spoiler database (*.xml) - + spoiler spoiler - + Spoilers import Spoilers import - + Please specify a compatible source for spoiler data. Please specify a compatible source for spoiler data. - + Download URL: Download URL: - + Local file: Local file: - + Restore default URL Restore default URL - + Choose file... Choose file... - + The spoiler database will be saved at the following location: The spoiler database will be saved at the following location: - + Save to a custom path (not recommended) Save to a custom path (not recommended) @@ -218,57 +232,57 @@ LoadTokensPage - + Save token database Save token database - + XML; token database (*.xml) XML; token database (*.xml) - + tokens tokens - + Tokens import Tokens import - + Please specify a compatible source for token data. Please specify a compatible source for token data. - + Download URL: Download URL: - + Local file: Local file: - + Restore default URL Restore default URL - + Choose file... Choose file... - + The token database will be saved at the following location: The token database will be saved at the following location: - + Save to a custom path (not recommended) Save to a custom path (not recommended) @@ -276,7 +290,7 @@ OracleImporter - + Dummy set containing tokens Dummy set containing tokens @@ -284,7 +298,7 @@ OracleWizard - + Oracle Importer Oracle Importer @@ -292,22 +306,22 @@ OutroPage - + Finished Finished - + The wizard has finished. The wizard has finished. - + You can now start using Cockatrice with the newly updated cards. You can now start using Cockatrice with the newly updated cards. - + If the card databases don't reload automatically, restart the Cockatrice client. If the card databases don't reload automatically, restart the Cockatrice client. @@ -315,73 +329,76 @@ SaveSetsPage - - + + Error Error - + + No set has been imported. No set has been imported. - + Sets imported Sets imported - - A cockatrice database file of %1 MB has been downloaded. - A cockatrice database file of %1 MB has been downloaded. + + A Cockatrice card database file of %1 MB has been downloaded. + - + The following sets have been found: The following sets have been found: - + Press "Save" to store the imported cards in the Cockatrice database. Press "Save" to store the imported cards in the Cockatrice database. - + The card database will be saved at the following location: The card database will be saved at the following location: - + Save to a custom path (not recommended) Save to a custom path (not recommended) - + &Save &Save - + + Import finished: %1 cards. Import finished: %1 cards. - + + %1: %2 cards imported %1: %2 cards imported - + Save card database Save card database - + XML; card database (*.xml) XML; card database (*.xml) - + The file could not be saved to %1 The file could not be saved to %1 @@ -414,8 +431,9 @@ - Downloading (0MB) - Downloading (0MB) + Downloading (0 MB) + Downloading (0MB) + @@ -587,7 +605,7 @@ i18n - + English English @@ -595,12 +613,12 @@ main - + Only run in spoiler mode Only run in spoiler mode - + Run in no-confirm background mode Run in no-confirm background mode diff --git a/oracle/translations/oracle_es.ts b/oracle/translations/oracle_es.ts index c11c1740a..141c5b26f 100644 --- a/oracle/translations/oracle_es.ts +++ b/oracle/translations/oracle_es.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Introducción - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Este instalador importará la lista de ediciones, cartas y fichas que va a usar Cockatrice. - + Interface language: Idioma de la interfaz: - + Version: Versión: @@ -25,192 +25,206 @@ LoadSetsPage - + Source selection Seleccionar origen - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Por favor especifica un origen compatible para la lista de sets y cartas. Puedes especificar la URL de donde descargarla o usar un archivo existente de tu ordenador. - + Download URL: URL de descarga: - + Local file: Archivo local: - + Restore default URL Restablecer URL predeterminada - + Choose file... Elegir archivo... - + Load sets file Cargar archivo de ediciones - + Sets file (%1) Sets JSON file (%1) Archivo de ediciones (%1) - - - - - - - + + + + + Error Error - + The provided URL is not valid. La URL suministrada no es válida. - - Downloading (0MB) - Descargando (0MB) - - - + Please choose a file. Por favor elija un archivo. - + Cannot open file '%1'. No se puede abrir el archivo '%1' - - Downloading (%1MB) - Descargando (%1MB) - - - + Network error: %1. Error de red: %1 - + Parsing file Procesando archivo - + Xz extraction failed. Extracción de Xz fallida - + Sorry, this version of Oracle does not support xz compressed files. Lo sentimos, esta versión de Oracle no soporta archivos comprimidos xz - + Failed to open Zip archive: %1. Error al abrir el archivo Zip: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Fallo al extraer el contenido: el Zip contiene más de un archivo. - + Zip extraction failed: %1. Error al extraer el contenido del Zip: %1. - + Sorry, this version of Oracle does not support zipped files. Lo sentimos, esta versión de Oracle no soporta archivos comprimidos. - + Failed to interpret downloaded data. No se pudieron interpretar los datos descargados. - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? ¿Prefieres descargar el archivo sin comprimir? - + + The file was retrieved successfully, but it does not contain any sets data. El archivo fue cargado correctamente pero no contiene datos sobre ningún set. + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database Guardar la base de datos de spoilers - + XML; spoiler database (*.xml) XML; base de datos de spoilers (*.xml) - + spoiler spoiler - + Spoilers import Importar spoilers - + Please specify a compatible source for spoiler data. Por favor elija un origen compatible para los datos de los spoilers. - + Download URL: URL de descarga: - + Local file: Archivo local: - + Restore default URL Restaurar URL por defecto - + Choose file... Elegir archivo... - + The spoiler database will be saved at the following location: La base de datos de los spoilers será guardada en la siguiente ubicación: - + Save to a custom path (not recommended) Guardar en una ruta personalizada (no recomendado) @@ -218,57 +232,57 @@ LoadTokensPage - + Save token database Guardar base de datos de fichas - + XML; token database (*.xml) XML; base de datos de fichas (*.xml) - + tokens fichas - + Tokens import Importar tokens - + Please specify a compatible source for token data. Por favor elija un origen compatible para los datos de las fichas. - + Download URL: URL de descarga: - + Local file: Archivo local: - + Restore default URL Restablecer URL predeterminada - + Choose file... Elegir archivo... - + The token database will be saved at the following location: La base de datos de las fichas será guardada en la siguiente ubicación: - + Save to a custom path (not recommended) Guardar en una ruta personalizada (no recomendado) @@ -276,7 +290,7 @@ OracleImporter - + Dummy set containing tokens Set dedicado para tokens @@ -284,7 +298,7 @@ OracleWizard - + Oracle Importer Importador de Oracle @@ -292,22 +306,22 @@ OutroPage - + Finished Completado - + The wizard has finished. El asistente ha terminado. - + You can now start using Cockatrice with the newly updated cards. Ahora puedes usar Cockatrice con las cartas actualizadas. - + If the card databases don't reload automatically, restart the Cockatrice client. Si las bases de datos de cartas no se recargan automáticamente, reinicia el cliente de Cockatrice. @@ -315,73 +329,76 @@ SaveSetsPage - - + + Error Error - + + No set has been imported. Ningún set ha sido importado. - + Sets imported Sets importados - - A cockatrice database file of %1 MB has been downloaded. - Se ha descargado un archivo de base de datos cockatrice de %1 MB. + + A Cockatrice card database file of %1 MB has been downloaded. + - + The following sets have been found: Las siguientes ediciones han sido encontradas: - + Press "Save" to store the imported cards in the Cockatrice database. Pulsa "Guardar" para guardar las cartas importadas en la base de datos de Cockatrice. - + The card database will be saved at the following location: La base de datos de cartas ha sido guardada en la siguiente ubicación: - + Save to a custom path (not recommended) Guardar en una ruta personalizada (no recomendado) - + &Save &Guardar - + + Import finished: %1 cards. Importación terminada: %1 cartas. - + + %1: %2 cards imported %1: %2 cartas importadas - + Save card database Guardar base de datos de cartas - + XML; card database (*.xml) XML; base de datos de cartas (*.xml) - + The file could not be saved to %1 El archivo no ha podido ser guardado en %1 @@ -414,8 +431,9 @@ - Downloading (0MB) - Descargando (0MB) + Downloading (0 MB) + Downloading (0MB) + @@ -587,7 +605,7 @@ i18n - + English Español (Spanish) @@ -595,12 +613,12 @@ main - + Only run in spoiler mode Sólo ejecutar en modo spoiler - + Run in no-confirm background mode Ejecutar en modo del segundo plano sin confirmación diff --git a/oracle/translations/oracle_et.ts b/oracle/translations/oracle_et.ts index 5a5e915db..1f06f14df 100644 --- a/oracle/translations/oracle_et.ts +++ b/oracle/translations/oracle_et.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Sissejuhatus - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. See võlur impordib Cockatrice’is kasutatava nimekirja komplektidest, kaartidest ja märgistustest. - + Interface language: - + Version: Versioon: @@ -25,192 +25,206 @@ LoadSetsPage - + Source selection Allika valik - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Palun täpsusta ühilduv allikas komplektide ja kaartide nimekirja jaoks. Võid täpsustada URL-i aadressi, mis laaditakse alla või kasutada olemasolevat faili oma arvutist. - + Download URL: Allalaadimise URL: - + Local file: Fail arvutis: - + Restore default URL Taasta tavaURL - + Choose file... Valige fail.... - + Load sets file Lae komplektide kaust - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + Error Viga - + The provided URL is not valid. Antud URl pole kehtiv. - - Downloading (0MB) - Allalaadimine (0MB) - - - + Please choose a file. Palun valige fail. - + Cannot open file '%1'. Ei suudeta avada '%1'. - - Downloading (%1MB) - Allalaadimine (%1MB) - - - + Network error: %1. Võrgu viga: %1. - + Parsing file Faili hankimine - + Xz extraction failed. Xzi lahtipakkimine nurjus. - + Sorry, this version of Oracle does not support xz compressed files. Vabandame, aga antud Oracle’i versioon ei toeta xz kokkupakitud faile. - + Failed to open Zip archive: %1. Zip-arhiivi avamine ebaõnnestus: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Zip-i lahtipakkimine ebaõnnestus: Zip-arhiiv sisaldab rohkem faile kui üks. - + Zip extraction failed: %1. Zipi lahtipakkimine ebaõnnestus: %1. - + Sorry, this version of Oracle does not support zipped files. Vabandame, aga antud Oracle versioon ei toeta kokkupakitud faile. - + Failed to interpret downloaded data. - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? Soovid alla laadida hoopis pakkimata faili? - + + The file was retrieved successfully, but it does not contain any sets data. Fail on edukalt alla laetud, ent ei sisalda andmeid. + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database Salvesta spoileri andmebaas - + XML; spoiler database (*.xml) XML; spoileri andmebaas (*.xml) - + spoiler - + Spoilers import Spoilerite import - + Please specify a compatible source for spoiler data. Täpsusta spoileri andmetega ühilduv allikas. - + Download URL: Allalaadimise URL: - + Local file: - + Restore default URL Taasta tavaURL - + Choose file... - + The spoiler database will be saved at the following location: Spoileri andmebaas salvestatakse järgmisesse asukohta: - + Save to a custom path (not recommended) Salvesta enda määratud asukohta (pole soovitatav) @@ -218,57 +232,57 @@ LoadTokensPage - + Save token database Salvesta märgistuste andmebaas - + XML; token database (*.xml) XML; märgistuste andmebaas (*.xml) - + tokens - + Tokens import Märgistuste importimine - + Please specify a compatible source for token data. Täpsusta märgistuste andmetega ühilduv allikas. - + Download URL: Allalaadimise URL: - + Local file: - + Restore default URL Taasta tavaURL - + Choose file... - + The token database will be saved at the following location: Märgistuste andmebaas salvestatakse järgmisesse asukohta: - + Save to a custom path (not recommended) Salvesta enda määratud asukohta (pole soovitatav) @@ -276,7 +290,7 @@ OracleImporter - + Dummy set containing tokens Nukk-komplekt mis sisaldab märgistusi @@ -284,7 +298,7 @@ OracleWizard - + Oracle Importer Oracle sissetooja @@ -292,22 +306,22 @@ OutroPage - + Finished Valmis - + The wizard has finished. Võlur on lõpetanud. - + You can now start using Cockatrice with the newly updated cards. Nüüd saad Cockatrice’i kasutada uhiuute kaartidega. - + If the card databases don't reload automatically, restart the Cockatrice client. Kui kaardi andmebaasid ei lae ise, taaskäivita Cockatrice’i klient. @@ -315,73 +329,76 @@ SaveSetsPage - - + + Error Viga - + + No set has been imported. Komplekti pole sisse toodud. - + Sets imported Allalaetud komplektid - - A cockatrice database file of %1 MB has been downloaded. + + A Cockatrice card database file of %1 MB has been downloaded. - + The following sets have been found: Leiti järgmised komplektid: - + Press "Save" to store the imported cards in the Cockatrice database. Vajuta „Salvesta“, et salvestada imporditud kaardid Cockatrice’i andmebaasi. - + The card database will be saved at the following location: Kaardi andmebaas salvestatakse järgmisesse asukohta: - + Save to a custom path (not recommended) Salvesta enda määratud asukohta (pole soovitatav) - + &Save &Salvesta - + + Import finished: %1 cards. %1 kaarti imporditi edukalt. - + + %1: %2 cards imported %1: imporditi %2 kaarti - + Save card database Salvesta kaartide andmebaas - + XML; card database (*.xml) XML; kaartide andmebaas (*.xml) - + The file could not be saved to %1 Faili salvestamine asukohta %1 ebaõnnestus @@ -414,8 +431,9 @@ - Downloading (0MB) - Allalaadimine (0MB) + Downloading (0 MB) + Downloading (0MB) + @@ -587,20 +605,20 @@ i18n - + English - Eesti Keel (Estonian) + Eesti (Estonian) main - + Only run in spoiler mode Käivita vaid spoileri režiimis - + Run in no-confirm background mode diff --git a/oracle/translations/oracle_fi.ts b/oracle/translations/oracle_fi.ts index e2a193ca4..95c223703 100644 --- a/oracle/translations/oracle_fi.ts +++ b/oracle/translations/oracle_fi.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Johdanto - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Tämä työkalu asentaa Cockatricessa käytettävät setit, kortit ja tokenit. - + Interface language: Käyttöliittymän kieli: - + Version: Versio: @@ -25,192 +25,206 @@ LoadSetsPage - + Source selection Lähteen määritys - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Ole hyvä ja määritä lähde setti- ja korttilistalle. Voit määrittää ladattavan URL-osoitteen tai käyttää tietokoneellasi olevaa tiedostoa. - + Download URL: Ladattavan tiedoston URL-osoite: - + Local file: Paikallinen tiedosto: - + Restore default URL Palauta oletus-URL - + Choose file... Valitse tiedosto... - + Load sets file Ladatta ekspansiotiedosto - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + Error Virhe - + The provided URL is not valid. Määritetty URL on virheellinen. - - Downloading (0MB) - Ladataan (0MB) - - - + Please choose a file. Valitse tiedosto. - + Cannot open file '%1'. Ei voida avata tiedostoa '%1'. - - Downloading (%1MB) - Ladataan (%1MB) - - - + Network error: %1. Yhteysvirhe: %1. - + Parsing file Jäsennellään tiedostoa - + Xz extraction failed. Xz purku epäonnistui. - + Sorry, this version of Oracle does not support xz compressed files. Pahoittelut. Tämä versio Oraclesta ei tue xz-pakattuja tiedostoja. - + Failed to open Zip archive: %1. Zip-tiedoston avaaminen epäonnistui: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Zip-tiedoston purkaminen epäonnistui: Zip-tiedosto ei sisällä tarkalleen yhtä tiedostoa. - + Zip extraction failed: %1. Zip-tiedoston purkaminen epäonnistuie: %1. - + Sorry, this version of Oracle does not support zipped files. Pahoittelut. Tämä versio Oraclesta ei tue Zip-pakattuja tiedostoja. - + Failed to interpret downloaded data. Ladattujen tietojen tulkitseminen epäonnistui. - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? Haluatko sensijaan ladata pakkaamattoman tiedoston? - + + The file was retrieved successfully, but it does not contain any sets data. Tiedosto palautettiin onnistuneesti, mutta se ei sisällä yhdenkään ekspansion dataa. + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database Tallenna spoileritietokanta - + XML; spoiler database (*.xml) XML; spoileritietokanta (*.xml) - + spoiler - + Spoilers import Spoilerien lataus - + Please specify a compatible source for spoiler data. Ole hyvä ja osoita yhteensopiva spoileridatan lähde. - + Download URL: Ladattavan tiedoston URL-osoite: - + Local file: - + Restore default URL Palauta oletus-URL - + Choose file... - + The spoiler database will be saved at the following location: Spoileritietokanta tallennetaan seuraavaan sijaintiin: - + Save to a custom path (not recommended) Tallenna mukautettuun sijaintiin (ei-suositeltu) @@ -218,57 +232,57 @@ LoadTokensPage - + Save token database Tallenna tokenitietokanta - + XML; token database (*.xml) XML; tokenitietokanta (*.xml) - + tokens - + Tokens import Tokenien lataus - + Please specify a compatible source for token data. Ole hyvä ja osoita yhteensopiva tokenidatan lähde. - + Download URL: Ladattavan tiedoston URL-osoite: - + Local file: - + Restore default URL Palauta oletus-URL - + Choose file... - + The token database will be saved at the following location: Tokenitietokanta tallennetaan seuraavaan sijaintiin: - + Save to a custom path (not recommended) Tallenna mukautettuun sijaintiin (ei-suositeltu) @@ -276,7 +290,7 @@ OracleImporter - + Dummy set containing tokens Tokeneja sisältävä mallisetti @@ -284,7 +298,7 @@ OracleWizard - + Oracle Importer Oracle-lataaja @@ -292,22 +306,22 @@ OutroPage - + Finished Valmis - + The wizard has finished. Asennusohjelma on valmis. - + You can now start using Cockatrice with the newly updated cards. Voit nyt käyttää Cockatricea juuri päivitetyillä korteilla. - + If the card databases don't reload automatically, restart the Cockatrice client. Jos korttitietokannat eivät päivity automaattisesti, käynnistä Cockatrice uudelleen. @@ -315,73 +329,76 @@ SaveSetsPage - - + + Error Virhe - + + No set has been imported. Yhtään settiä ei ladattu. - + Sets imported Ladatut ekspansiot - - A cockatrice database file of %1 MB has been downloaded. - Cockatrice tietokantatiedosto kooltaan %1 MB on ladattu. + + A Cockatrice card database file of %1 MB has been downloaded. + - + The following sets have been found: Seuraavat setit löydettiin: - + Press "Save" to store the imported cards in the Cockatrice database. Paina "Tallenna" varastoidaksesi ladatut kortit Cockatricen tietokantaan. - + The card database will be saved at the following location: Korttitietokanta tallennetaan seuraavaan sijaintiin: - + Save to a custom path (not recommended) Tallenna mukautettuun sijaintiin (ei-suositeltu) - + &Save &Tallenna - + + Import finished: %1 cards. Lataaminen valmis: %1 kortit. - + + %1: %2 cards imported %1: %2 kortit ladattu - + Save card database Tallenna korttitietokanta - + XML; card database (*.xml) XML; korttitietokanta (*.xml) - + The file could not be saved to %1 Tiedostoa ei voitu tallentaa osoitteeseen %1 @@ -414,8 +431,9 @@ - Downloading (0MB) - Ladataan (0 MB) + Downloading (0 MB) + Downloading (0MB) + @@ -587,7 +605,7 @@ i18n - + English Suomi (Finnish) @@ -595,12 +613,12 @@ main - + Only run in spoiler mode Suorita vain spoileritilassa - + Run in no-confirm background mode diff --git a/oracle/translations/oracle_fr.ts b/oracle/translations/oracle_fr.ts index 590a0b879..6e384576f 100644 --- a/oracle/translations/oracle_fr.ts +++ b/oracle/translations/oracle_fr.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Introduction - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Cet assistant va importer la liste des éditions, cartes et jetons qui seront utilisés par Cockatrice. - + Interface language: Langage de l'interface : - + Version: Version : @@ -25,192 +25,206 @@ LoadSetsPage - + Source selection Choix du fichier source - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Veuillez spécifier une source compatible pour la liste d'éditions et de cartes. Vous pouvez spécifier une URL qui sera utilisée pour télécharger un fichier ou utiliser un fichier existant sur votre ordinateur. - + Download URL: URL de téléchargement : - + Local file: Fichier local : - + Restore default URL Restaurer l'URL par défaut - + Choose file... Choisissez un fichier... - + Load sets file Charger une liste d'éditions - + Sets file (%1) Sets JSON file (%1) Choisir le fichier (%1) - - - - - - - + + + + + Error Erreur - + The provided URL is not valid. L'URL fournie n'est pas valable - - Downloading (0MB) - Téléchargement en cours (0MB) - - - + Please choose a file. Choisissez un fichier. - + Cannot open file '%1'. Impossible d'ouvrir le fichier '%1'. - - Downloading (%1MB) - Téléchargement (%1MB) - - - + Network error: %1. Erreur réseau : %1. - + Parsing file Traitement du fichier. - + Xz extraction failed. L'extraction du zip à échoué. - + Sorry, this version of Oracle does not support xz compressed files. Désolé, cette version d'Oracle ne supporte pas les fichiers zip. - + Failed to open Zip archive: %1. Impossible d'ouvrir l'archive zip: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Extraction zip échouée: l'archive zip contient plus qu'un fichier. - + Zip extraction failed: %1. L'extraction du zip a échoué : %1. - + Sorry, this version of Oracle does not support zipped files. Désolé, cette version d'Oracle ne supporte pas les fichiers zip. - + Failed to interpret downloaded data. Échec lors de l'interprétation des données téléchargées. - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? Voulez-vous télécharger le fichier non compressé à la place ? - + + The file was retrieved successfully, but it does not contain any sets data. Le fichier a été trouvé, mais ne contient aucune éditions. + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database Enregistrer la base de données des spoilers - + XML; spoiler database (*.xml) XML ; base de données des spoilers (*.xml) - + spoiler spoiler - + Spoilers import Importation des spoilers - + Please specify a compatible source for spoiler data. Veuillez spécifier une source compatible pour les spoilers. - + Download URL: URL de téléchargement : - + Local file: Fichier local: - + Restore default URL Restaurer l'URL par défaut - + Choose file... Choisissez un fichier... - + The spoiler database will be saved at the following location: La base de données des spoilers sera enregistrée à l'emplacement suivant : - + Save to a custom path (not recommended) Sauvegarder à un emplacement personnalisé (non recommandé) @@ -218,57 +232,57 @@ LoadTokensPage - + Save token database Enregistrer la base de données des jetons - + XML; token database (*.xml) XML ; base de données des jetons (*.xml) - + tokens jetons - + Tokens import Importation des jetons - + Please specify a compatible source for token data. Veuillez spécifier une source compatible pour les jetons. - + Download URL: URL de téléchargement: - + Local file: Ficher local: - + Restore default URL Restaurer l'URL par défaut - + Choose file... Choisissez un ficher... - + The token database will be saved at the following location: La base de données des jetons sera enregistrée à l'emplacement suivant : - + Save to a custom path (not recommended) Sauvegarder à un emplacement personnalisé (non recommandé) @@ -276,7 +290,7 @@ OracleImporter - + Dummy set containing tokens Fausse édition contenant les jetons @@ -284,7 +298,7 @@ OracleWizard - + Oracle Importer Importateur Oracle @@ -292,22 +306,22 @@ OutroPage - + Finished Terminé - + The wizard has finished. L'assistant a terminé. - + You can now start using Cockatrice with the newly updated cards. Vous pouvez maintenant commencer à utiliser Cockatrice avec les cartes mises à jour. - + If the card databases don't reload automatically, restart the Cockatrice client. Si les bases de données de cartes ne rechargent pas automatiquement, redémarrez le client Cockatrice. @@ -315,73 +329,76 @@ SaveSetsPage - - + + Error Erreur - + + No set has been imported. Aucune édition n'a été importé. - + Sets imported Éditions importées - - A cockatrice database file of %1 MB has been downloaded. - Un fichier de base de données Cockatrice de %1 MB a été téléchargé. + + A Cockatrice card database file of %1 MB has been downloaded. + - + The following sets have been found: Les éditions suivantes ont été trouvées : - + Press "Save" to store the imported cards in the Cockatrice database. Cliquez sur « Enregistrer » pour enregistrer les cartes importées dans la base de données de Cockatrice. - + The card database will be saved at the following location: La base de données des cartes sera enregistrée à l'emplacement suivant : - + Save to a custom path (not recommended) Sauvegarder à un emplacement personnalisé (non recommandé) - + &Save Sauvegarder - + + Import finished: %1 cards. Import terminé: %1 cartes. - + + %1: %2 cards imported %1: %2 cartes ajoutées. - + Save card database Sauvegarder la base de carte - + XML; card database (*.xml) XML ; base de données de cartes (*.xml) - + The file could not be saved to %1 Le fichier n'a pu être sauvegarder au chemin '%1' @@ -414,8 +431,9 @@ - Downloading (0MB) - Téléchargement (0 Mo) + Downloading (0 MB) + Downloading (0MB) + @@ -587,7 +605,7 @@ i18n - + English Français (French) @@ -595,12 +613,12 @@ main - + Only run in spoiler mode Ne fonctionne qu'en mode spoiler - + Run in no-confirm background mode S'exécuter en mode tâche de fond sans demande de confirmation diff --git a/oracle/translations/oracle_it.ts b/oracle/translations/oracle_it.ts index e266209d1..1f4f84d78 100644 --- a/oracle/translations/oracle_it.ts +++ b/oracle/translations/oracle_it.ts @@ -2,23 +2,23 @@ IntroPage - + Introduction Introduzione - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Questo wizard importerà la lista di set, carte e pedine che verranno usate da Cockatrice. - + Interface language: Lingua dell'interfaccia: - + Version: Versione: @@ -26,192 +26,206 @@ e pedine che verranno usate da Cockatrice. LoadSetsPage - + Source selection Selezione sorgente - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Specifica una sorgente compatibile per la lista dei set e delle carte. Puoi specificare un indirizzo URL da cui scaricare il file o selezionare un file già presente nel tuo computer. - + Download URL: Indirizzo download: - + Local file: File nel pc: - + Restore default URL Usa l'indirizzo predefinito - + Choose file... Scegli file... - + Load sets file Carica file dei set - + Sets file (%1) Sets JSON file (%1) File dei set (%1) - - - - - - - + + + + + Error Errore - + The provided URL is not valid. L'indirizzo specificato non è valido. - - Downloading (0MB) - Scaricamento (0MB) - - - + Please choose a file. Seleziona un file. - + Cannot open file '%1'. Impossibile aprire il file '%1'. - - Downloading (%1MB) - Scaricamento (%1MB) - - - + Network error: %1. Errore di rete: %1 - + Parsing file Analisi dei file - + Xz extraction failed. Estrazione da file xz fallita. - + Sorry, this version of Oracle does not support xz compressed files. Spiacente, ma questa versione di Oracle non supporta i file xz. - + Failed to open Zip archive: %1. Impossibile aprire il file Zip: %1 - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Estrazione file Zip fallita: lo Zip non contiene un solo file. - + Zip extraction failed: %1. Estrazione file Zip fallita: %1 - + Sorry, this version of Oracle does not support zipped files. Spiacente, ma questa versione di Oracle non supporta i file zip. - + Failed to interpret downloaded data. Impossibile interpretare i dati scaricati. - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? Vuoi provare a scaricare il file non compresso? - + + The file was retrieved successfully, but it does not contain any sets data. Il file è stato analizzato correttamente, ma non contiene i dati di nessun set. + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database Salva archivio spoiler - + XML; spoiler database (*.xml) XML; archivio spoiler (*.xml) - + spoiler spoiler - + Spoilers import Importazione spoiler - + Please specify a compatible source for spoiler data. Specifica una sorgente compatibile per gli spoiler. - + Download URL: Indirizzo download: - + Local file: File nel pc: - + Restore default URL Usa l'indirizzo predefinito - + Choose file... Scegli file... - + The spoiler database will be saved at the following location: L'archivio degli spoiler verrà salvato nel seguente percorso: - + Save to a custom path (not recommended) Salva in un percorso diverso (sconsigliato) @@ -219,57 +233,57 @@ e pedine che verranno usate da Cockatrice. LoadTokensPage - + Save token database Salva archivio pedine - + XML; token database (*.xml) XML; archivio pedine (*.xml) - + tokens pedine - + Tokens import Importazione pedine - + Please specify a compatible source for token data. Specifica una sorgente compatibile per le pedine. - + Download URL: Indirizzo download: - + Local file: File nel pc: - + Restore default URL Usa l'indirizzo predefinito - + Choose file... Scegli file... - + The token database will be saved at the following location: L'archivio delle pedine verrà salvato nel seguente percorso: - + Save to a custom path (not recommended) Salva in un percorso diverso (sconsigliato) @@ -277,7 +291,7 @@ e pedine che verranno usate da Cockatrice. OracleImporter - + Dummy set containing tokens Set finto contenente i token @@ -285,7 +299,7 @@ e pedine che verranno usate da Cockatrice. OracleWizard - + Oracle Importer Oracle Importer @@ -293,22 +307,22 @@ e pedine che verranno usate da Cockatrice. OutroPage - + Finished Finito - + The wizard has finished. Il wizard è completato. - + You can now start using Cockatrice with the newly updated cards. Adesso puoi iniziare ad usare Cockatrice con le nuove carte aggiornate. - + If the card databases don't reload automatically, restart the Cockatrice client. Se il database delle carte non si ricarica in automatico, riavvia il programma Cockatrice. @@ -316,73 +330,76 @@ e pedine che verranno usate da Cockatrice. SaveSetsPage - - + + Error Errore - + + No set has been imported. Nessun set importato. - + Sets imported Set importati - - A cockatrice database file of %1 MB has been downloaded. - È stato scaricato un file database di Cockatrice di %1 MB. + + A Cockatrice card database file of %1 MB has been downloaded. + - + The following sets have been found: Sono stati trovati i seguenti set: - + Press "Save" to store the imported cards in the Cockatrice database. Premi "Salva" per salvare le carte importate nel database di Cockatrice. - + The card database will be saved at the following location: L'archivio delle carte verrà salvato nel seguente percorso: - + Save to a custom path (not recommended) Salva in un percorso diverso (sconsigliato) - + &Save &Salva - + + Import finished: %1 cards. Importazione conclusa: %1 carte. - + + %1: %2 cards imported %1: %2 carte importate - + Save card database Salva archivio carte - + XML; card database (*.xml) XML; archivio carte (*.xml) - + The file could not be saved to %1 Impossibile salvare il file su %1 @@ -415,8 +432,9 @@ e pedine che verranno usate da Cockatrice. - Downloading (0MB) - Scaricamento (0MB) + Downloading (0 MB) + Downloading (0MB) + @@ -588,7 +606,7 @@ e pedine che verranno usate da Cockatrice. i18n - + English Italiano (Italian) @@ -596,12 +614,12 @@ e pedine che verranno usate da Cockatrice. main - + Only run in spoiler mode Avvia solo in modalità spoiler - + Run in no-confirm background mode Esegui in background senza conferma diff --git a/oracle/translations/oracle_ja.ts b/oracle/translations/oracle_ja.ts index a437db796..6086ceb74 100644 --- a/oracle/translations/oracle_ja.ts +++ b/oracle/translations/oracle_ja.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction はじめに - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. このウィザードでは、Cockatriceで使用されるカードやトークン、セットのリストをインポートします。 - + Interface language: インターフェース言語: - + Version: バージョン: @@ -25,192 +25,206 @@ LoadSetsPage - + Source selection ソース選択 - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. セットとカードのリストの互換性のあるソースを指定してください。ダウンロードするURLアドレスを指定するか、コンピューターから既存のファイルを使用できます。 - + Download URL: ダウンロードURL: - + Local file: ローカルファイル: - + Restore default URL デフォルトのURLを復元 - + Choose file... ファイルを選択... - + Load sets file カードセットファイルを開く - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + Error エラー - + The provided URL is not valid. 指定されたURLは無効です。 - - Downloading (0MB) - ダウンロード中 (0MB) - - - + Please choose a file. ファイルを選択してください。 - + Cannot open file '%1'. '%1'を開けませんでした。 - - Downloading (%1MB) - ダウンロード中 (%1MB) - - - + Network error: %1. ネットワークエラー: %1。 - + Parsing file ファイルの解析 - + Xz extraction failed. Xz展開に失敗。 - + Sorry, this version of Oracle does not support xz compressed files. このバージョンのOracleはxz圧縮ファイルをサポートしていません。 - + Failed to open Zip archive: %1. ZIPアーカイブの展開に失敗: %1。 - + Zip extraction failed: the Zip archive doesn't contain exactly one file. ZIP展開に失敗:Zipアーカイブに含まれるファイルが1つだけではありません。 - + Zip extraction failed: %1. ZIP展開に失敗: %1。 - + Sorry, this version of Oracle does not support zipped files. 申し訳ありませんが現バージョンのOracleはzip形式のファイルをサポートしていません。 - + Failed to interpret downloaded data. - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? 代わりに非圧縮ファイルをダウンロードしますか? - + + The file was retrieved successfully, but it does not contain any sets data. ファイルは正常に取得されましたが、カードセットのデータが含まれていませんでした。 + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database スポイラーデータベースを保存 - + XML; spoiler database (*.xml) XML; スポイラーデータベース (*.xml) - + spoiler - + Spoilers import スポイラーインポート - + Please specify a compatible source for spoiler data. 互換性のあるスポイラーデータのソースを指定してください。 - + Download URL: ダウンロードURL: - + Local file: - + Restore default URL デフォルトのURLを復元 - + Choose file... - + The spoiler database will be saved at the following location: スポイラーデータベースは、以下の場所に保存されます: - + Save to a custom path (not recommended) 別のパスに保存(非推奨) @@ -218,57 +232,57 @@ LoadTokensPage - + Save token database トークンデータベースを保存 - + XML; token database (*.xml) XML; トークンデータベース (*.xml) - + tokens - + Tokens import トークンのインポート - + Please specify a compatible source for token data. トークンデータのソースを選択してください。 - + Download URL: ダウンロードURL: - + Local file: - + Restore default URL デフォルトのURLを復元 - + Choose file... - + The token database will be saved at the following location: トークンデータベースは以下の場所に保存されます。 - + Save to a custom path (not recommended) 別のパスに保存(非推奨) @@ -276,7 +290,7 @@ OracleImporter - + Dummy set containing tokens ダミーセットを含むトークン @@ -284,7 +298,7 @@ OracleWizard - + Oracle Importer Oracle Importer - オラクル・インポーター @@ -292,22 +306,22 @@ OutroPage - + Finished 完了しました! - + The wizard has finished. ウィザードが完了しました。 - + You can now start using Cockatrice with the newly updated cards. Cockatriceで新しく更新されたカードを使うことが出来ます。 - + If the card databases don't reload automatically, restart the Cockatrice client. カードデータベースが自動的に再読込されない場合は、Cockatriceを再起動して下さい。 @@ -315,73 +329,76 @@ SaveSetsPage - - + + Error エラー - + + No set has been imported. セットはインポートされませんでした。 - + Sets imported カードセットインポート - - A cockatrice database file of %1 MB has been downloaded. + + A Cockatrice card database file of %1 MB has been downloaded. - + The following sets have been found: 次のセットが見つかりました: - + Press "Save" to store the imported cards in the Cockatrice database. ”保存”をクリックするとインポートしたカードをCockatriceデータベースに保存します。 - + The card database will be saved at the following location: カードデータベースは以下の場所に保存されます: - + Save to a custom path (not recommended) 別のパスに保存(非推奨) - + &Save 保存 - + + Import finished: %1 cards. %1枚のカードがインポートされました。 - + + %1: %2 cards imported %1: %2枚のカードがインポートされました。 - + Save card database カードデータベースを保存 - + XML; card database (*.xml) XML; card database (*.xml) - + The file could not be saved to %1 %1に保存できませんでした。 @@ -414,8 +431,9 @@ - Downloading (0MB) - ダウンロード中 (0MB) + Downloading (0 MB) + Downloading (0MB) + @@ -587,7 +605,7 @@ i18n - + English 日本語 (Japanese) @@ -595,12 +613,12 @@ main - + Only run in spoiler mode スポイラーモードでのみ起動 - + Run in no-confirm background mode diff --git a/oracle/translations/oracle_ko.ts b/oracle/translations/oracle_ko.ts index 3b0b0187c..e8dd4defb 100644 --- a/oracle/translations/oracle_ko.ts +++ b/oracle/translations/oracle_ko.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction 개요 - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. - + Interface language: - + Version: @@ -25,192 +25,206 @@ LoadSetsPage - + Source selection 확장판 목록 파일 주소 입력 - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. - + Download URL: 다운로드 주소: - + Local file: 파일 위치: - + Restore default URL 기본 주소로 복원 - + Choose file... 파일 선택... - + Load sets file 확장판 목록 파일 불러오기 - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + Error 오류 - + The provided URL is not valid. 잘못된 주소를 입력하셨습니다. - - Downloading (0MB) - 다운로드 중 (0MB) - - - + Please choose a file. 확장판 목록 파일을 선택해 주세요. - + Cannot open file '%1'. 파일 '%1'을(를) 열 수 없습니다. - - Downloading (%1MB) - 다운로드 중 (%1MB) - - - + Network error: %1. 네트워크 오류 : %1. - + Parsing file 목록 파싱중 - + Xz extraction failed. - + Sorry, this version of Oracle does not support xz compressed files. - + Failed to open Zip archive: %1. 압축파일 열기 실패 : %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. 압축 풀기 실패 : 압축 파일에 확장판 목록 파일 이외의 파일이 있습니다. - + Zip extraction failed: %1. 압축 풀기 실패 : %1. - + Sorry, this version of Oracle does not support zipped files. 죄송합니다. 본 버전에서는 압축 파일을 지원하지 않습니다. - + Failed to interpret downloaded data. - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? - + + The file was retrieved successfully, but it does not contain any sets data. 파일을 성공적으로 다운로드 하였으나 확장판 정보가 들어있지 않습니다. + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database - + XML; spoiler database (*.xml) - + spoiler - + Spoilers import - + Please specify a compatible source for spoiler data. - + Download URL: - + Local file: - + Restore default URL - + Choose file... - + The spoiler database will be saved at the following location: - + Save to a custom path (not recommended) @@ -218,57 +232,57 @@ LoadTokensPage - + Save token database - + XML; token database (*.xml) - + tokens - + Tokens import - + Please specify a compatible source for token data. - + Download URL: 웹 주소: - + Local file: - + Restore default URL 기본 주소로 복원 - + Choose file... - + The token database will be saved at the following location: - + Save to a custom path (not recommended) @@ -276,7 +290,7 @@ OracleImporter - + Dummy set containing tokens 토큰 정보가 들어있는 더미 확장판 @@ -284,7 +298,7 @@ OracleWizard - + Oracle Importer 오라클 @@ -292,22 +306,22 @@ OutroPage - + Finished - + The wizard has finished. - + You can now start using Cockatrice with the newly updated cards. - + If the card databases don't reload automatically, restart the Cockatrice client. @@ -315,73 +329,76 @@ SaveSetsPage - - + + Error 오류 - + + No set has been imported. 아무 확장판도 불러오지 못했습니다. - + Sets imported 확장판 불러오기 완료 - - A cockatrice database file of %1 MB has been downloaded. + + A Cockatrice card database file of %1 MB has been downloaded. - + The following sets have been found: - + Press "Save" to store the imported cards in the Cockatrice database. - + The card database will be saved at the following location: - + Save to a custom path (not recommended) - + &Save - + + Import finished: %1 cards. 총 %1장의 카드 불러오기 완료 - + + %1: %2 cards imported %1에서 %2장의 카드 불러옴 - + Save card database 카드 데이터베이스 저장 - + XML; card database (*.xml) 카드 데이터베이스 XML 파일 (*.xml) - + The file could not be saved to %1 파일을 %1에 저장 할 수 없습니다. @@ -414,7 +431,8 @@ - Downloading (0MB) + Downloading (0 MB) + Downloading (0MB) @@ -587,7 +605,7 @@ i18n - + English 한국어 (Korean) @@ -595,12 +613,12 @@ main - + Only run in spoiler mode - + Run in no-confirm background mode diff --git a/oracle/translations/oracle_nb.ts b/oracle/translations/oracle_nb.ts index 698076fd5..2d13e2687 100644 --- a/oracle/translations/oracle_nb.ts +++ b/oracle/translations/oracle_nb.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Introduksjon - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. - + Interface language: - + Version: @@ -25,192 +25,206 @@ LoadSetsPage - + Source selection Kilde valg - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. - + Download URL: Nedlastings URL - + Local file: Lokal fil - + Restore default URL Gjenopprett standard URL - + Choose file... Velg fil... - + Load sets file Last set fil... - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + Error Feil - + The provided URL is not valid. URL du anga er ikke gyldig. - - Downloading (0MB) - Laster ned (0MB) - - - + Please choose a file. Vennligst velg en fil - + Cannot open file '%1'. Kunne ikke åpme '%1' - - Downloading (%1MB) - Laster ned (%1MB) - - - + Network error: %1. Nettverks feil: %1 - + Parsing file Tolker fil. - + Xz extraction failed. - + Sorry, this version of Oracle does not support xz compressed files. - + Failed to open Zip archive: %1. Kunne ikke åpne Zip Arkiv: %1 - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Zip ekstraksjon feilet: Zip arkivet inneholder ikke nøyaktig en fil - + Zip extraction failed: %1. Zip ekstraksjon feilet: %1 - + Sorry, this version of Oracle does not support zipped files. Beklager, denne versjonen av Oracle støtter ikke Zip filer. - + Failed to interpret downloaded data. - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? - + + The file was retrieved successfully, but it does not contain any sets data. Filen ble hentet riktig, men den inneholder ikke noe set data + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database - + XML; spoiler database (*.xml) - + spoiler - + Spoilers import - + Please specify a compatible source for spoiler data. - + Download URL: - + Local file: - + Restore default URL - + Choose file... - + The spoiler database will be saved at the following location: - + Save to a custom path (not recommended) @@ -218,57 +232,57 @@ LoadTokensPage - + Save token database - + XML; token database (*.xml) - + tokens - + Tokens import - + Please specify a compatible source for token data. - + Download URL: Nedlastings URL - + Local file: - + Restore default URL Gjenopprett standard URL - + Choose file... - + The token database will be saved at the following location: - + Save to a custom path (not recommended) @@ -276,7 +290,7 @@ OracleImporter - + Dummy set containing tokens Dummy sett som inneholder tokens @@ -284,7 +298,7 @@ OracleWizard - + Oracle Importer Oracle importerer @@ -292,22 +306,22 @@ OutroPage - + Finished - + The wizard has finished. - + You can now start using Cockatrice with the newly updated cards. - + If the card databases don't reload automatically, restart the Cockatrice client. @@ -315,73 +329,76 @@ SaveSetsPage - - + + Error Feil - + + No set has been imported. Ingen set har blitt importert. - + Sets imported Set importert - - A cockatrice database file of %1 MB has been downloaded. + + A Cockatrice card database file of %1 MB has been downloaded. - + The following sets have been found: - + Press "Save" to store the imported cards in the Cockatrice database. - + The card database will be saved at the following location: - + Save to a custom path (not recommended) - + &Save - + + Import finished: %1 cards. Importering fullført: %1 kort. - + + %1: %2 cards imported %1: %2 kort importert. - + Save card database Lagre kort database. - + XML; card database (*.xml) XML; kort database (*.xml) - + The file could not be saved to %1 Filen kunne ikke lagres til %1 @@ -414,7 +431,8 @@ - Downloading (0MB) + Downloading (0 MB) + Downloading (0MB) @@ -587,7 +605,7 @@ i18n - + English Norsk Bokmål (Norwegian Bokmål) @@ -595,12 +613,12 @@ main - + Only run in spoiler mode - + Run in no-confirm background mode diff --git a/oracle/translations/oracle_nl.ts b/oracle/translations/oracle_nl.ts index 3fd3cc543..7558e7281 100644 --- a/oracle/translations/oracle_nl.ts +++ b/oracle/translations/oracle_nl.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Introductie - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Deze wizard importeert de lijst met sets, kaarten en tokens die door Cockatrice zullen worden gebruikt. - + Interface language: Interface taal: - + Version: Versie: @@ -25,192 +25,206 @@ LoadSetsPage - + Source selection Bron selectie - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Gelieve een compatibele bron te vermelden voor de lijst van sets en kaarten. U kunt een URL-adres opgeven dat wordt gedownload of een bestaand bestand van uw computer gebruiken. - + Download URL: Download URL: - + Local file: Lokaal bestand - + Restore default URL Herstel standaard URL - + Choose file... Bestand kiezen... - + Load sets file Bestand laden - + Sets file (%1) Sets JSON file (%1) Sets bestand (%1) - - - - - - - + + + + + Error Fout - + The provided URL is not valid. De ingevoerde URL is niet geldig. - - Downloading (0MB) - Downloaden (0MB) - - - + Please choose a file. Gelieve een bestand te kiezen. - + Cannot open file '%1'. Bestand '%1' kan niet geopend worden. - - Downloading (%1MB) - Downloaden (%1MB) - - - + Network error: %1. Netwerk fout: %1. - + Parsing file Parsen van bestand - + Xz extraction failed. Xz extractie mislukt. - + Sorry, this version of Oracle does not support xz compressed files. Sorry, deze versie van Oracle ondersteunt geen xz gecomprimeerde bestanden. - + Failed to open Zip archive: %1. Zip archief kan niet geopend worden: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Uitpakken van Zip niet gelukt: het archief moet precies één bestand bevatten. - + Zip extraction failed: %1. Uitpakken van Zip niet gelukt: %1. - + Sorry, this version of Oracle does not support zipped files. Sorry, deze versie van Oracle ondersteunt geen gecomprimeerde bestanden. - + Failed to interpret downloaded data. Lezen van gedownloade data mislukt. - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? Wilt u het ongecomprimeerde bestand downloaden? - + + The file was retrieved successfully, but it does not contain any sets data. Het bestand is succesvol binnengehaald, maar bevat geen set data. + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database Opslaan spoiler database - + XML; spoiler database (*.xml) XML; spoiler database (*.xml) - + spoiler - + Spoilers import Spoilers import - + Please specify a compatible source for spoiler data. Gelieve een compatibele bron voor spoilergegevens te specificeren. - + Download URL: Download URL: - + Local file: - + Restore default URL Herstel standaard URL - + Choose file... - + The spoiler database will be saved at the following location: De spoiler database wordt op de volgende locatie opgeslagen: - + Save to a custom path (not recommended) Opslaan naar een aangepaste locatie (niet aanbevolen) @@ -218,57 +232,57 @@ LoadTokensPage - + Save token database token database opslaan - + XML; token database (*.xml) XML; token database (*.xml) - + tokens - + Tokens import Tokens import - + Please specify a compatible source for token data. Gelieve een compatibele bron voor tokengegevens op te geven. - + Download URL: Download URL: - + Local file: - + Restore default URL Herstel standaard URL - + Choose file... - + The token database will be saved at the following location: De token database wordt op de volgende locatie opgeslagen: - + Save to a custom path (not recommended) Opslaan naar een aangepaste locatie (niet aanbevolen) @@ -276,7 +290,7 @@ OracleImporter - + Dummy set containing tokens Token voorbeeldset @@ -284,7 +298,7 @@ OracleWizard - + Oracle Importer Oracle importer @@ -292,22 +306,22 @@ OutroPage - + Finished Klaar - + The wizard has finished. De wizard is klaar. - + You can now start using Cockatrice with the newly updated cards. U kunt nu beginnen met het gebruik van Cockatrice met de nieuw bijgewerkte kaarten. - + If the card databases don't reload automatically, restart the Cockatrice client. Als de kaartendatabases niet automatisch herladen, start dan de Cockatrice client opnieuw op. @@ -315,73 +329,76 @@ SaveSetsPage - - + + Error Fout - + + No set has been imported. Er zijn geen sets geïmporteerd. - + Sets imported Sets geïmporteerd - - A cockatrice database file of %1 MB has been downloaded. - Een cockatrice database bestand van %1 MB is gedownload. + + A Cockatrice card database file of %1 MB has been downloaded. + - + The following sets have been found: De volgende sets zijn gevonden: - + Press "Save" to store the imported cards in the Cockatrice database. Druk op "Opslaan" om de geïmporteerde kaarten op te slaan in de Cockatrice database. - + The card database will be saved at the following location: De kaartendatabase wordt op de volgende locatie opgeslagen: - + Save to a custom path (not recommended) Opslaan naar een aangepaste locatie (niet aanbevolen) - + &Save &Opslaan - + + Import finished: %1 cards. Import klaar: %1 kaarten. - + + %1: %2 cards imported %1: %2 kaarten geïmporteerd - + Save card database Kaartendatabase opslaan - + XML; card database (*.xml) XML; kaart database (*.xml) - + The file could not be saved to %1 Het bestand kon niet worden opgeslagen in %1 @@ -414,8 +431,9 @@ - Downloading (0MB) - Downloaden (0MB) + Downloading (0 MB) + Downloading (0MB) + @@ -587,7 +605,7 @@ i18n - + English Nederlands (Dutch) @@ -595,12 +613,12 @@ main - + Only run in spoiler mode Alleen in spoiler-modus werken - + Run in no-confirm background mode Uitvoeren in achtergrondmodus zonder bevestiging diff --git a/oracle/translations/oracle_pl.ts b/oracle/translations/oracle_pl.ts index cad6309a9..567044f74 100644 --- a/oracle/translations/oracle_pl.ts +++ b/oracle/translations/oracle_pl.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Wprowadzenie - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Ten kreator zaimportuje listę dodatków, kart oraz tokenów które zostaną użyte przez Cockatrice. - + Interface language: Język interfejsu: - + Version: Wersja: @@ -25,192 +25,206 @@ LoadSetsPage - + Source selection Wybór źródła - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Proszę podać źródło listy edycji i kart. Można podać adres URL, z którego zostanie pobrana, lub istniejący plik na komputerze. - + Download URL: Pobierz URL - + Local file: Plik lokalny: - + Restore default URL Przywróć domyślny URL - + Choose file... Wybierz plik… - + Load sets file Wczytaj listę dodatków - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + Error Błąd - + The provided URL is not valid. Podano nieprawidłowy URL. - - Downloading (0MB) - Pobieranie (0MB) - - - + Please choose a file. Proszę wybrać plik. - + Cannot open file '%1'. Nie można otworzyć pliku '%1'. - - Downloading (%1MB) - Pobieranie (%1MB) - - - + Network error: %1. Błąd sieci: %1. - + Parsing file Analizowanie pliku - + Xz extraction failed. Rozpakowanie XZ nie udało się. - + Sorry, this version of Oracle does not support xz compressed files. Przepraszamy, ta wersja Oracle nie obsługuje plików spakowanych w archiwum xz. - + Failed to open Zip archive: %1. Otwieranie archiwum Zip zakończone niepowodzeniem: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Rozpakowanie pliku Zip nieudane: archiwum nie zawiera dokładnie jednego pliku. - + Zip extraction failed: %1. Rozpakowanie Zip nieudane: %1. - + Sorry, this version of Oracle does not support zipped files. Przepraszamy, ta wersja Oracle nie obsługuje plików spakowanych w archiwum Zip. - + Failed to interpret downloaded data. Interpretacja pobranych danych nie udała się. - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? Czy chcesz zamiast tego pobrać plik nieskompresowany? - + + The file was retrieved successfully, but it does not contain any sets data. Plik został pobrany z powodzeniem, ale nie zawiera informacji o dodatkach. + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database Zapisz bazę danych spoilerów. - + XML; spoiler database (*.xml) XML; baza danych spoilerów (*.xml) - + spoiler - + Spoilers import Import spoilerów - + Please specify a compatible source for spoiler data. Proszę wybrać kompatybilne źródło danych ze spoilerami. - + Download URL: URL pobierania: - + Local file: - + Restore default URL Przywróć domyślny URL - + Choose file... - + The spoiler database will be saved at the following location: Baza danych spoilerów zostanie zapisana w tym miejscu: - + Save to a custom path (not recommended) Zapisz do niestandardowej ścieżki (niezalecane) @@ -218,57 +232,57 @@ LoadTokensPage - + Save token database Zapisz bazę danych tokenów. - + XML; token database (*.xml) XML; baza danych tokenów (*.xml) - + tokens - + Tokens import Import tokenów - + Please specify a compatible source for token data. Proszę wybrać kompatybilne źródło danych z tokenami. - + Download URL: Pobierz odnośnik: - + Local file: - + Restore default URL Przywróć domyślny URL - + Choose file... - + The token database will be saved at the following location: Baza danych tokenów zostanie zapisana w tym miejscu: - + Save to a custom path (not recommended) Zapisz do niestandardowej ścieżki (niezalecane) @@ -276,7 +290,7 @@ OracleImporter - + Dummy set containing tokens Dodatek-atrapa, zawierający tokeny. @@ -284,7 +298,7 @@ OracleWizard - + Oracle Importer Oracle – kreator importu @@ -292,22 +306,22 @@ OutroPage - + Finished Zakończone - + The wizard has finished. Kreator zakończył swoją pracę. - + You can now start using Cockatrice with the newly updated cards. Możesz teraz korzystać z Cockatrice z nowo zaktualizowanymi kartami. - + If the card databases don't reload automatically, restart the Cockatrice client. Jeżeli bazy kart nie przeładują się automatycznie, zrestartuj klienta Cockatrice. @@ -315,73 +329,76 @@ SaveSetsPage - - + + Error Błąd - + + No set has been imported. Nie zaimportowano żadnego dodatku. - + Sets imported Zaimportowane dodatki - - A cockatrice database file of %1 MB has been downloaded. - Baza danych Cockatrice mająca %1 MB została pobrana. + + A Cockatrice card database file of %1 MB has been downloaded. + - + The following sets have been found: Następujące dodatki zostały znalezione: - + Press "Save" to store the imported cards in the Cockatrice database. Kliknij "Zapisz" aby zapisać zaimportowane karty w bazie danych Cockatrice. - + The card database will be saved at the following location: Baza danych kart zostanie zapisana w tym miejscu: - + Save to a custom path (not recommended) Zapisz do niestandardowej ścieżki (niezalecane) - + &Save Zapisz - + + Import finished: %1 cards. Import zakończony: %1 kart. - + + %1: %2 cards imported %1: zaimportowano %2 kart - + Save card database Zapisz bazę kart - + XML; card database (*.xml) XML, baza kart (*.xml) - + The file could not be saved to %1 Plik nie mógł zostać zapisany do %1 @@ -414,8 +431,9 @@ - Downloading (0MB) - Pobieranie (0MB) + Downloading (0 MB) + Downloading (0MB) + @@ -587,7 +605,7 @@ i18n - + English Polski (Polish) @@ -595,12 +613,12 @@ main - + Only run in spoiler mode Tylko działaj w trybie spoilerów - + Run in no-confirm background mode diff --git a/oracle/translations/oracle_pt.ts b/oracle/translations/oracle_pt.ts index 069f3013e..c0fb580c4 100644 --- a/oracle/translations/oracle_pt.ts +++ b/oracle/translations/oracle_pt.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Introdução - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. - + Interface language: - + Version: @@ -25,192 +25,206 @@ LoadSetsPage - + Source selection Selecção da fonte - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. - + Download URL: URL de download: - + Local file: Ficheiro local: - + Restore default URL URL para repor definições de origem - + Choose file... Escolher ficheiro... - + Load sets file Carregar ficheiro das edições - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + Error Erro - + The provided URL is not valid. O URL fornecido não é válido. - - Downloading (0MB) - A efectuar download (0MB) - - - + Please choose a file. Por favor escolha um ficheiro. - + Cannot open file '%1'. Impossível abrir ficheiro '%1'. - - Downloading (%1MB) - A efectuar download (%1MB) - - - + Network error: %1. Erro da rede: %1. - + Parsing file Ficheiro de análise - + Xz extraction failed. - + Sorry, this version of Oracle does not support xz compressed files. - + Failed to open Zip archive: %1. Abrir archivo zip falhou: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Extracção do ZIP falhada: o arquivo ZIP não contem exactamente um ficheiro. - + Zip extraction failed: %1. Extração do Zip falhada: %1. - + Sorry, this version of Oracle does not support zipped files. Pedimos desculpa, mas esta versão do Oracle não suporta ficheiros zipados. - + Failed to interpret downloaded data. - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? - + + The file was retrieved successfully, but it does not contain any sets data. O ficheiro foi recuperado com sucesso, mas não contem nenhum dado sobre expansões. + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database - + XML; spoiler database (*.xml) - + spoiler - + Spoilers import - + Please specify a compatible source for spoiler data. - + Download URL: - + Local file: - + Restore default URL - + Choose file... - + The spoiler database will be saved at the following location: - + Save to a custom path (not recommended) @@ -218,57 +232,57 @@ LoadTokensPage - + Save token database - + XML; token database (*.xml) - + tokens - + Tokens import - + Please specify a compatible source for token data. - + Download URL: URL de download: - + Local file: - + Restore default URL URL para repor definições de origem - + Choose file... - + The token database will be saved at the following location: - + Save to a custom path (not recommended) @@ -276,7 +290,7 @@ OracleImporter - + Dummy set containing tokens Set básico contendo fichas @@ -284,7 +298,7 @@ OracleWizard - + Oracle Importer Importar Oracle @@ -292,22 +306,22 @@ OutroPage - + Finished - + The wizard has finished. - + You can now start using Cockatrice with the newly updated cards. - + If the card databases don't reload automatically, restart the Cockatrice client. @@ -315,73 +329,76 @@ SaveSetsPage - - + + Error Erro - + + No set has been imported. Nenhuma expansão foi importada. - + Sets imported Edições importadas - - A cockatrice database file of %1 MB has been downloaded. + + A Cockatrice card database file of %1 MB has been downloaded. - + The following sets have been found: - + Press "Save" to store the imported cards in the Cockatrice database. - + The card database will be saved at the following location: - + Save to a custom path (not recommended) - + &Save - + + Import finished: %1 cards. Importação terminada: %1 cartas. - + + %1: %2 cards imported %1. %2 cartas importadas - + Save card database Guardar base de dados das cartas - + XML; card database (*.xml) XML; Base de dados de cartas (*.xml) - + The file could not be saved to %1 O ficheiro não pode ser gravado em %1 @@ -414,7 +431,8 @@ - Downloading (0MB) + Downloading (0 MB) + Downloading (0MB) @@ -587,7 +605,7 @@ i18n - + English Português (Portuguese) @@ -595,12 +613,12 @@ main - + Only run in spoiler mode - + Run in no-confirm background mode diff --git a/oracle/translations/oracle_pt_BR.ts b/oracle/translations/oracle_pt_BR.ts index 30c4e5e65..fc0f5e1ed 100644 --- a/oracle/translations/oracle_pt_BR.ts +++ b/oracle/translations/oracle_pt_BR.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Introdução - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Esse recurso irá importar a lista de coleções, cartas e fichas que serão utilizadas pelo Cokatrice. - + Interface language: Idioma da interface: - + Version: Versão: @@ -25,192 +25,206 @@ LoadSetsPage - + Source selection Selecione a origem - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Por favor especifique uma fonte compatível de expansões e cartas. Você pode especificar um endereço URL que será baixado ou usar um arquivo existente no seu computador. - + Download URL: Endereço para download: - + Local file: Arquivo local: - + Restore default URL Restaurar URL padrão - + Choose file... Escolha o arquivo... - + Load sets file Carregar arquivos de expansão - + Sets file (%1) Sets JSON file (%1) Arquivo de expansões (%1) - - - - - - - + + + + + Error Erro - + The provided URL is not valid. A URL escolhida não é valida - - Downloading (0MB) - Baixando (0MB) - - - + Please choose a file. Por favor, escolha um arquivo; - + Cannot open file '%1'. Não pode abrir arquivo '%1' - - Downloading (%1MB) - Baixando (%1MB) - - - + Network error: %1. Erro na conexão com a internet: %1. - + Parsing file Analisando arquivo - + Xz extraction failed. Extração de arquivo .xz falhou - + Sorry, this version of Oracle does not support xz compressed files. Desculpe, esta versão do Oracle não suporta arquivos comprimidos no formato .xz - + Failed to open Zip archive: %1. Falhou em abrir o arquivo Zip: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. A extração do arquivo ZIP falhou: o arquivo ZIP não contém exatamente um arquivo. - + Zip extraction failed: %1. Extração do arquivo Zip falhou: %1. - + Sorry, this version of Oracle does not support zipped files. Desculpe, esta versão do Oracle não suporta arquivos compactados. - + Failed to interpret downloaded data. Falha ao interpretar dados baixados - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? Gostaria de baixar os arquivos descomprimidos, em vez disso? - + + The file was retrieved successfully, but it does not contain any sets data. O arquivo foi recebido com sucesso, mas não contém qualquer informação de expansão. + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database Salvar base de dados de spoiler - + XML; spoiler database (*.xml) XML; base de dados de spoilers (*.xml) - + spoiler - + spoiler - + Spoilers import Importar spoilers - + Please specify a compatible source for spoiler data. Por favor, selecione uma fonte compatível para os dados de spoilers. - + Download URL: Endereço para download: - + Local file: - + Arquivo local: - + Restore default URL Restaurar URL padrão - + Choose file... - + Escolha o arquivo... - + The spoiler database will be saved at the following location: A base de dados de spoilers será salva no seguinte diretório: - + Save to a custom path (not recommended) Salvar em diretório customizado (não recomendado) @@ -218,57 +232,57 @@ LoadTokensPage - + Save token database Salvar base de dados de tokens - + XML; token database (*.xml) XML; base de dados de token (*.xml) - + tokens - + fichas - + Tokens import Importar tokens - + Please specify a compatible source for token data. Por favor especifique uma fonte compatível para os dados de tokens. - + Download URL: Endereço para download: - + Local file: - + Arquivo local: - + Restore default URL Restaurar URL padrão - + Choose file... - + Escolha o arquivo... - + The token database will be saved at the following location: A base de dados de tokens será salva no seguinte diretório: - + Save to a custom path (not recommended) Salvar em diretório customizado (não recomendado) @@ -276,7 +290,7 @@ OracleImporter - + Dummy set containing tokens Esta expansão contém fichas. @@ -284,7 +298,7 @@ OracleWizard - + Oracle Importer Importador Oracle @@ -292,22 +306,22 @@ OutroPage - + Finished Finalizado - + The wizard has finished. O recurso terminou. - + You can now start using Cockatrice with the newly updated cards. Agora você pode começar a usar o Cockatrice com as novas cartas atualizadas. - + If the card databases don't reload automatically, restart the Cockatrice client. Se a base de dados das cartas não for atualizada automaticamente, reinicie o cliente do Cockatrice. @@ -315,73 +329,76 @@ SaveSetsPage - - + + Error Erro - + + No set has been imported. O set não foi importado. - + Sets imported Expansões importadas - - A cockatrice database file of %1 MB has been downloaded. - Um arquivo de %1 MB de banco de dados do cockatrice foi baixado. + + A Cockatrice card database file of %1 MB has been downloaded. + - + The following sets have been found: As seguintes expansões foram encontradas: - + Press "Save" to store the imported cards in the Cockatrice database. Aperte "Salvar" para guardar as cartas importadas na base de dados do Cockatrice. - + The card database will be saved at the following location: A base de dados de cartas será salva no seguinte diretório: - + Save to a custom path (not recommended) Salvar em diretório customizado (não recomendado) - + &Save &Salvar - + + Import finished: %1 cards. Importação finalizada: %1 cartas. - + + %1: %2 cards imported %1: %2 cartas importadas - + Save card database Carta salva no banco de dados - + XML; card database (*.xml) XML; banco de dados de cartas (*.xml) - + The file could not be saved to %1 O arquivo não pode ser salvo para %1 @@ -391,12 +408,12 @@ Load %1 file - + Carregar arquivo de %1 %1 file (%1) - + Arquivo de %1 (%1) @@ -410,22 +427,23 @@ The provided URL is not valid: - + O endereço web fornecido não é válido: - Downloading (0MB) - Baixando (0MB) + Downloading (0 MB) + Downloading (0MB) + Please choose a file. - + Por favor, escolha um arquivo. Cannot open file '%1'. - + Não pôde abrir o arquivo '%1'. @@ -587,7 +605,7 @@ i18n - + English Português do Brasil (Brazilian Portuguese) @@ -595,12 +613,12 @@ main - + Only run in spoiler mode Rodar apenas em modo de 'spoiler' - + Run in no-confirm background mode diff --git a/oracle/translations/oracle_ru.ts b/oracle/translations/oracle_ru.ts index 4e1bcd3c6..475fdde74 100644 --- a/oracle/translations/oracle_ru.ts +++ b/oracle/translations/oracle_ru.ts @@ -2,215 +2,229 @@ IntroPage - + Introduction Введение - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Эта программа импортирует перечень выпусков, карт и фишек, которые будут использоваться в Cockatrice. - + Interface language: Язык интерфейса: - + Version: - Версия + Версия: LoadSetsPage - + Source selection Выбор источника - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Пожалуйста, укажите источник для перечня сетов и карт. Вы можете использовать ссылку или уже существующий локальный файл. - + Download URL: Ссылка на скачивание: - + Local file: Локальный файл: - + Restore default URL Восстановить ссылку по умолчанию - + Choose file... Выбрать файл.. - + Load sets file Загрузить файл сетов - + Sets file (%1) Sets JSON file (%1) Файл (%1) - - - - - - - + + + + + Error Ошибка - + The provided URL is not valid. Предоставленная ссылка некорректна. - - Downloading (0MB) - Загрузка (0MB) - - - + Please choose a file. Пожалуйста, выберите файл. - + Cannot open file '%1'. Не удалось открыть файл '%1'. - - Downloading (%1MB) - Загрузка (%1MB) - - - + Network error: %1. Ошибка сети: %1. - + Parsing file Ожидание файла - + Xz extraction failed. Ошибка распаковки архива - + Sorry, this version of Oracle does not support xz compressed files. Данная версия Oracle не поддерживает архивы xz - + Failed to open Zip archive: %1. Не удалось загрузить zip-архив: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Ошибка распаковки: zip-архив содержит больше одного файла. - + Zip extraction failed: %1. Ошибка распаковки zip-архива: %1. - + Sorry, this version of Oracle does not support zipped files. Данная версия Oracle не поддерживает zip-архивы. - + Failed to interpret downloaded data. Ошибка в обработке загруженных данных - + + Downloading (0 MB) + Загрузка (0 MB) + + + + Downloading (%1 MB) + Загрузка (%1 MB) + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? Хотите вместо этого загрузить новую несжатую копию? - + + The file was retrieved successfully, but it does not contain any sets data. Файл успешно получен, но в нем не содержится данных о сетах. + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database Сохранить базу карт - + XML; spoiler database (*.xml) XML; база карт (*.xml) - + spoiler - + Spoilers import Импорт карт - + Please specify a compatible source for spoiler data. Укажите совместимый ресурс для импорта базы карт - + Download URL: Ссылка на скачивание: - + Local file: - + Restore default URL Восстановить ссылку по умолчанию - + Choose file... - + The spoiler database will be saved at the following location: База карт будет сохранена в следующей директории: - + Save to a custom path (not recommended) @@ -218,57 +232,57 @@ LoadTokensPage - + Save token database - + XML; token database (*.xml) - + tokens - + фишки - + Tokens import - + Please specify a compatible source for token data. - + Download URL: Ссылка на скачивание: - + Local file: - + Restore default URL Восстановить ссылку по умолчанию - + Choose file... - + The token database will be saved at the following location: - + Save to a custom path (not recommended) @@ -276,7 +290,7 @@ OracleImporter - + Dummy set containing tokens Пример сета с фишками @@ -284,7 +298,7 @@ OracleWizard - + Oracle Importer Импортер Oracle @@ -292,22 +306,22 @@ OutroPage - + Finished - + The wizard has finished. - + You can now start using Cockatrice with the newly updated cards. - + If the card databases don't reload automatically, restart the Cockatrice client. @@ -315,73 +329,76 @@ SaveSetsPage - - + + Error Ошибка - + + No set has been imported. Не было импортировано ни одного сета. - + Sets imported Импортировано сетов - - A cockatrice database file of %1 MB has been downloaded. + + A Cockatrice card database file of %1 MB has been downloaded. - + The following sets have been found: - + Press "Save" to store the imported cards in the Cockatrice database. - + The card database will be saved at the following location: - + Save to a custom path (not recommended) - + &Save Сохранить - + + Import finished: %1 cards. Импорт завершен: %1 карт. - + + %1: %2 cards imported %1: %2 карт импортировано - + Save card database Сохранить базу карт - + XML; card database (*.xml) База карт (*.xml) - + The file could not be saved to %1 Не удалось сохранить файл в %1 @@ -405,7 +422,7 @@ Error - + Ошибка @@ -414,18 +431,19 @@ - Downloading (0MB) + Downloading (0 MB) + Downloading (0MB) Please choose a file. - + Выберите файл. Cannot open file '%1'. - + Не удалось открыть файл '%1'. @@ -587,7 +605,7 @@ i18n - + English Русский (Russian) @@ -595,12 +613,12 @@ main - + Only run in spoiler mode Запускать только в режиме сетов - + Run in no-confirm background mode diff --git a/oracle/translations/oracle_sr.ts b/oracle/translations/oracle_sr.ts index 9532030b3..7db2c733b 100644 --- a/oracle/translations/oracle_sr.ts +++ b/oracle/translations/oracle_sr.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Uvod - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. - + Interface language: - + Version: @@ -25,192 +25,206 @@ LoadSetsPage - + Source selection Odabir izvora - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. - + Download URL: URL za preuzimanje: - + Local file: Lokalni fajl: - + Restore default URL Povrati uobičajeni URL - + Choose file... Izaberite fajl... - + Load sets file - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + Error Greška - + The provided URL is not valid. Dati URL nije važeći. - - Downloading (0MB) - Preuzimanje (0MB) - - - + Please choose a file. Molimo Vas izaberite fajl. - + Cannot open file '%1'. Nemoguće otvori fajl '%1'. - - Downloading (%1MB) - Preuzimanje (%1MB) - - - + Network error: %1. Mrežna greška: %1. - + Parsing file Parsiranje fajla - + Xz extraction failed. - + Sorry, this version of Oracle does not support xz compressed files. - + Failed to open Zip archive: %1. Neuspeh u otvaranju Zip arhive: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. - + Zip extraction failed: %1. - + Sorry, this version of Oracle does not support zipped files. Izvinite, ova verzija Oracle-a ne podržava zip fajlove. - + Failed to interpret downloaded data. - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? - + + The file was retrieved successfully, but it does not contain any sets data. + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database - + XML; spoiler database (*.xml) - + spoiler - + Spoilers import - + Please specify a compatible source for spoiler data. - + Download URL: - + Local file: - + Restore default URL - + Choose file... - + The spoiler database will be saved at the following location: - + Save to a custom path (not recommended) @@ -218,57 +232,57 @@ LoadTokensPage - + Save token database - + XML; token database (*.xml) - + tokens - + Tokens import - + Please specify a compatible source for token data. - + Download URL: URL za preuzimanje: - + Local file: - + Restore default URL - + Choose file... - + The token database will be saved at the following location: - + Save to a custom path (not recommended) @@ -276,7 +290,7 @@ OracleImporter - + Dummy set containing tokens @@ -284,7 +298,7 @@ OracleWizard - + Oracle Importer @@ -292,22 +306,22 @@ OutroPage - + Finished - + The wizard has finished. - + You can now start using Cockatrice with the newly updated cards. - + If the card databases don't reload automatically, restart the Cockatrice client. @@ -315,73 +329,76 @@ SaveSetsPage - - + + Error Greška - + + No set has been imported. - + Sets imported - - A cockatrice database file of %1 MB has been downloaded. + + A Cockatrice card database file of %1 MB has been downloaded. - + The following sets have been found: - + Press "Save" to store the imported cards in the Cockatrice database. - + The card database will be saved at the following location: - + Save to a custom path (not recommended) - + &Save - + + Import finished: %1 cards. - + + %1: %2 cards imported - + Save card database - + XML; card database (*.xml) - + The file could not be saved to %1 @@ -414,7 +431,8 @@ - Downloading (0MB) + Downloading (0 MB) + Downloading (0MB) @@ -587,7 +605,7 @@ i18n - + English Srpski (Serbian) @@ -595,12 +613,12 @@ main - + Only run in spoiler mode - + Run in no-confirm background mode diff --git a/oracle/translations/oracle_tr.ts b/oracle/translations/oracle_tr.ts index e65124c40..da48b106b 100644 --- a/oracle/translations/oracle_tr.ts +++ b/oracle/translations/oracle_tr.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction Açıklama - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. Bu sihirbaz Cockatrice tarafından kullanılacak setlerin, kartların ve tokenlerin listesini içe aktaracaktır. - + Interface language: Arayüz dili: - + Version: Sürüm: @@ -25,192 +25,206 @@ LoadSetsPage - + Source selection Kaynak seçimi - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. Lütfen set ve kart listesi için uyumlu bir kaynak belirtin. İndirilecek bir URL adresi belirtebilir veya bilgisayarınızdaki mevcut bir dosyayı kullanabilirsiniz. - + Download URL: İndirme Bağlantısı: - + Local file: Yerel dosya: - + Restore default URL Varsayılan URL'yi geri yükle - + Choose file... Dosya seç... - + Load sets file Set dosyasını yükle - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + Error Hata - + The provided URL is not valid. Paylaşılan bağlantı geçerli değil. - - Downloading (0MB) - İndiriliyor (0MB) - - - + Please choose a file. Lütfen dosya seçin. - + Cannot open file '%1'. Dosya açılamıyor '1%'. - - Downloading (%1MB) - İndiriliyor (%1MB) - - - + Network error: %1. Bağlantı hatası: %1 - + Parsing file Dosya ayrıştırılıyor - + Xz extraction failed. Xz çıkarma işlemi başarısız oldu. - + Sorry, this version of Oracle does not support xz compressed files. Maalesef, Oracle'ın bu sürümü xz sıkıştırılmış dosyaları desteklemiyor. - + Failed to open Zip archive: %1. Zip dosyası açılamadı: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. Zip çıkarma işlemi başarısız oldu: Zip arşivi tam olarak bir dosya içermiyor. - + Zip extraction failed: %1. Zip çıkarma işlemi başarısız oldu: %1. - + Sorry, this version of Oracle does not support zipped files. Maalesef, Oracle'ın bu sürümü zip dosyalarını desteklemiyor. - + Failed to interpret downloaded data. İndirilen veriler işlenemedi. - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? Bunun yerine sıkıştırılmamış dosyayı indirmek ister misiniz? - + + The file was retrieved successfully, but it does not contain any sets data. Dosya başarıyla alındı, ancak herhangi bir set verisi içermiyor. + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database Spoiler veritabanını kaydet - + XML; spoiler database (*.xml) XML; spoiler veritabanı (*.xml) - + spoiler - + Spoilers import Spoilers içe aktar - + Please specify a compatible source for spoiler data. Lütfen spoiler verileri için uyumlu bir kaynak belirtin. - + Download URL: İndirme Bağlantısı: - + Local file: - + Restore default URL Varsayılan bağlantıyı geri yükle - + Choose file... - + The spoiler database will be saved at the following location: - + Save to a custom path (not recommended) @@ -218,57 +232,57 @@ LoadTokensPage - + Save token database - + XML; token database (*.xml) - + tokens - + Tokens import - + Please specify a compatible source for token data. - + Download URL: - + Local file: - + Restore default URL - + Choose file... - + The token database will be saved at the following location: - + Save to a custom path (not recommended) @@ -276,7 +290,7 @@ OracleImporter - + Dummy set containing tokens @@ -284,7 +298,7 @@ OracleWizard - + Oracle Importer @@ -292,22 +306,22 @@ OutroPage - + Finished - + The wizard has finished. - + You can now start using Cockatrice with the newly updated cards. - + If the card databases don't reload automatically, restart the Cockatrice client. @@ -315,73 +329,76 @@ SaveSetsPage - - + + Error - + + No set has been imported. - + Sets imported - - A cockatrice database file of %1 MB has been downloaded. + + A Cockatrice card database file of %1 MB has been downloaded. - + The following sets have been found: - + Press "Save" to store the imported cards in the Cockatrice database. - + The card database will be saved at the following location: - + Save to a custom path (not recommended) - + &Save - + + Import finished: %1 cards. - + + %1: %2 cards imported - + Save card database - + XML; card database (*.xml) - + The file could not be saved to %1 @@ -414,7 +431,8 @@ - Downloading (0MB) + Downloading (0 MB) + Downloading (0MB) @@ -587,7 +605,7 @@ i18n - + English Türkçe (Turkish) @@ -595,12 +613,12 @@ main - + Only run in spoiler mode - + Run in no-confirm background mode diff --git a/oracle/translations/oracle_yue.ts b/oracle/translations/oracle_yue.ts index d13848c34..1be3677fe 100644 --- a/oracle/translations/oracle_yue.ts +++ b/oracle/translations/oracle_yue.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction 介绍 - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. 此嚮導將會導入Cockatrice中用到的系列,卡牌和衍生物列表。 - + Interface language: 介面語言: - + Version: 版本: @@ -25,192 +25,206 @@ LoadSetsPage - + Source selection 選擇來源 - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. 請選擇合適的牌組和卡片清單來源。您可以輸入下載URL或使用電腦中已有的檔案。 - + Download URL: 下载URL: - + Local file: 本地檔案: - + Restore default URL 恢復常用URL - + Choose file... 選擇檔案... - + Load sets file 载入牌组檔案 - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + Error 出現錯誤 - + The provided URL is not valid. 所提供的url無效. - - Downloading (0MB) - 下載中 (0MB) - - - + Please choose a file. 請選擇檔案 - + Cannot open file '%1'. 不能開啟檔案 '%1 - - Downloading (%1MB) - 下載中 (%1MB) - - - + Network error: %1. 網路出錯: %1. - + Parsing file 分析檔案中 - + Xz extraction failed. xz解壓縮失敗. - + Sorry, this version of Oracle does not support xz compressed files. 很抱歉,現前Oracle版本不支援xc壓縮檔案 - + Failed to open Zip archive: %1. 未能開啟壓縮文件: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. 解壓縮失敗:這壓縮文件擁有超過一個檔案. - + Zip extraction failed: %1. 解壓縮失敗: %1. - + Sorry, this version of Oracle does not support zipped files. 很抱歉,現前Oracle版本不支援壓縮檔案. - + Failed to interpret downloaded data. 不能分析下載資料. - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? 你是否想另再下載未經壓縮的檔案? - + + The file was retrieved successfully, but it does not contain any sets data. 雖然檔案成功取回, 但是檔案並未含有任何牌組資料. + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database 儲存預覽資料庫 - + XML; spoiler database (*.xml) XML ; 預覽資料庫(*.XML) - + spoiler - + Spoilers import 導入預覽卡牌 - + Please specify a compatible source for spoiler data. 請指定預覽資料合用來源. - + Download URL: 下载URL: - + Local file: - + Restore default URL 恢復常用URL - + Choose file... - + The spoiler database will be saved at the following location: 預覽資料庫將會儲藏於以下位置 - + Save to a custom path (not recommended) 儲存到自訂路徑(不建議) @@ -218,57 +232,57 @@ LoadTokensPage - + Save token database 儲存衍生物資料庫 - + XML; token database (*.xml) XML; 衍生物資料庫 (*.xml) - + tokens - + Tokens import 導入衍生物 - + Please specify a compatible source for token data. 請指定衍生物資料合用來源. - + Download URL: 下载URL: - + Local file: - + Restore default URL 恢復常用URL - + Choose file... - + The token database will be saved at the following location: 衍生物資料庫將會儲藏於以下位置: - + Save to a custom path (not recommended) 儲存到自訂路徑(不建議) @@ -276,7 +290,7 @@ OracleImporter - + Dummy set containing tokens 包含衍生物的虚拟牌組 @@ -284,7 +298,7 @@ OracleWizard - + Oracle Importer Oracle導入器 @@ -292,22 +306,22 @@ OutroPage - + Finished 完成 - + The wizard has finished. 嚮導程序工作完成 - + You can now start using Cockatrice with the newly updated cards. Cockatrice已載入更新了卡牌, 現在已經可以使用. - + If the card databases don't reload automatically, restart the Cockatrice client. 如果卡牌資料庫尚未能自動更新,請把cockatrice用戶端重新開啟. @@ -315,73 +329,76 @@ SaveSetsPage - - + + Error 出現錯誤 - + + No set has been imported. 沒有牌組被導入。 - + Sets imported 已導入牌組 - - A cockatrice database file of %1 MB has been downloaded. - 有%1MB大小的Cockatrice資料庫檔案已被下載 + + A Cockatrice card database file of %1 MB has been downloaded. + - + The following sets have been found: 已找到以下牌组: - + Press "Save" to store the imported cards in the Cockatrice database. 要將導入卡牌保存於Cockatrice資料庫內請按"儲藏". - + The card database will be saved at the following location: 卡牌資料庫將被儲藏於以下位置 - + Save to a custom path (not recommended) 儲存到自訂路徑(不建議) - + &Save &儲存 - + + Import finished: %1 cards. 導入過程完成:成功導入%1張卡牌. - + + %1: %2 cards imported %1:成功導入%2張卡牌. - + Save card database 儲存卡牌資料庫 - + XML; card database (*.xml) XML; 卡牌資料庫 (*.xml) - + The file could not be saved to %1 檔案未能儲藏於%1。 @@ -414,8 +431,9 @@ - Downloading (0MB) - 下載中(0MB) + Downloading (0 MB) + Downloading (0MB) + @@ -587,20 +605,20 @@ i18n - + English - + 粵語 (Cantonese) main - + Only run in spoiler mode 限於在預覽模式運行 - + Run in no-confirm background mode diff --git a/oracle/translations/oracle_zh-Hans.ts b/oracle/translations/oracle_zh-Hans.ts index 75ebdc138..29f603373 100644 --- a/oracle/translations/oracle_zh-Hans.ts +++ b/oracle/translations/oracle_zh-Hans.ts @@ -2,22 +2,22 @@ IntroPage - + Introduction 介绍 - + This wizard will import the list of sets, cards, and tokens that will be used by Cockatrice. 此向导将会导入Cockatrice中用到的系列,卡牌和衍生物列表. - + Interface language: 界面语言: - + Version: 版本: @@ -25,192 +25,206 @@ LoadSetsPage - + Source selection 选择来源 - + Please specify a compatible source for the list of sets and cards. You can specify a URL address that will be downloaded or use an existing file from your computer. 请选择合适的牌组和卡片清单来源。您可以输入下载URL或使用电脑中已有的档案。 - + Download URL: 下载URL: - + Local file: 本地文件: - + Restore default URL 恢复常用URL - + Choose file... 选择档案... - + Load sets file 载入牌组档案 - + Sets file (%1) Sets JSON file (%1) - - - - - - - + + + + + Error 出现错误 - + The provided URL is not valid. 所提供的url无效. - - Downloading (0MB) - 下载中(0MB) - - - + Please choose a file. 请选择档案. - + Cannot open file '%1'. 不能开启档案 '%1 - - Downloading (%1MB) - 下载中(%1MB) - - - + Network error: %1. 网路出错: %1. - + Parsing file 分析档案中 - + Xz extraction failed. xz解压缩失败。 - + Sorry, this version of Oracle does not support xz compressed files. 很抱歉,现前Oracle版本不支援xc压缩档案 - + Failed to open Zip archive: %1. 未能开启压缩文件: %1. - + Zip extraction failed: the Zip archive doesn't contain exactly one file. 解压缩失败:这压缩文件拥有超过一个档案. - + Zip extraction failed: %1. 解压缩失败:%1。 - + Sorry, this version of Oracle does not support zipped files. 很抱歉,现前Oracle版本不支援压缩档案. - + Failed to interpret downloaded data. 不能分析下载资料. - + + Downloading (0 MB) + + + + + Downloading (%1 MB) + + + + + Parsing file (%1%) + + + + + Extracting file... + + + + Do you want to download the uncompressed file instead? 你是否想另再下载未经压缩的档案? - + + The file was retrieved successfully, but it does not contain any sets data. 虽然档案成功取回, 但是档案并未含有任何牌组资料. + + + Parsing file (100%) + + LoadSpoilersPage - + Save spoiler database 储存预览资料库 - + XML; spoiler database (*.xml) XML ; 预览资料库(*.XML) - + spoiler - + 预览 - + Spoilers import 导入预览卡牌 - + Please specify a compatible source for spoiler data. 请指定预览资料合用来源. - + Download URL: 下载URL: - + Local file: - + 本地文件: - + Restore default URL 恢复常用URL - + Choose file... - + 选择文件... - + The spoiler database will be saved at the following location: 预览资料库将会储藏于以下位置: - + Save to a custom path (not recommended) 保存到自定义路径(不推荐) @@ -218,57 +232,57 @@ LoadTokensPage - + Save token database 储存衍生物资料库 - + XML; token database (*.xml) XML; 衍生物资料库 (*.xml) - + tokens - + 衍生物 - + Tokens import 导入衍生物 - + Please specify a compatible source for token data. 请指定衍生物资料合用来源. - + Download URL: 下载URL: - + Local file: - + 本地文件: - + Restore default URL 恢复常用URL - + Choose file... - + 选择文件... - + The token database will be saved at the following location: 衍生物资料库将会储藏于以下位置: - + Save to a custom path (not recommended) 保存到自定义路径(不推荐) @@ -276,7 +290,7 @@ OracleImporter - + Dummy set containing tokens 包含衍生物的虚拟牌组 @@ -284,7 +298,7 @@ OracleWizard - + Oracle Importer Oracle导入器 @@ -292,22 +306,22 @@ OutroPage - + Finished 完成 - + The wizard has finished. 向导程式工作完成. - + You can now start using Cockatrice with the newly updated cards. Cockatrice已载入更新了卡牌, 现在已经可以使用. - + If the card databases don't reload automatically, restart the Cockatrice client. 如果卡牌资料库尚未能自动更新,请把cockatrice用户端重新开启. @@ -315,73 +329,76 @@ SaveSetsPage - - + + Error 出现错误 - + + No set has been imported. 没有牌组被导入。 - + Sets imported 已导入牌组 - - A cockatrice database file of %1 MB has been downloaded. - 有%1MB大小的Cockatrice资料库档案已被下载. + + A Cockatrice card database file of %1 MB has been downloaded. + - + The following sets have been found: 已找到以下牌组: - + Press "Save" to store the imported cards in the Cockatrice database. 要将导入卡牌保存于Cockatrice资料库内请按"储藏". - + The card database will be saved at the following location: 卡牌资料库将被储藏于以下位置 - + Save to a custom path (not recommended) 保存到自定义路径(不推荐) - + &Save &储存 - + + Import finished: %1 cards. 导入过程完成:成功导入%1张卡牌. - + + %1: %2 cards imported %1:成功导入%2张卡牌. - + Save card database 储存卡牌资料库 - + XML; card database (*.xml) XML; 卡牌资料库 (*.xml) - + The file could not be saved to %1 档案未能储藏于%1。 @@ -391,12 +408,12 @@ Load %1 file - + 载入 %1 文件 %1 file (%1) - + %1 文件 (%1) @@ -410,22 +427,23 @@ The provided URL is not valid: - + 所提供的url无效: - Downloading (0MB) - 下载中(0MB) + Downloading (0 MB) + Downloading (0MB) + Please choose a file. - + 请选择文件。 Cannot open file '%1'. - + 无法打开文件“%1”。 @@ -587,7 +605,7 @@ i18n - + English 简体中文 (Chinese Simplified) @@ -595,14 +613,14 @@ main - + Only run in spoiler mode 限于在预览模式运行 - + Run in no-confirm background mode - + 在“无确认背景”模式下运行 \ No newline at end of file diff --git a/publish_beta.sh b/publish_beta.sh new file mode 100755 index 000000000..b0c3960b3 --- /dev/null +++ b/publish_beta.sh @@ -0,0 +1,186 @@ +#!/bin/bash +# Publish a new Development beta tag. +# Wraps the manual flow: +# git checkout master && git remote update -p && git pull +# git tag -Development--beta[.N] +# git push upstream +# +# Tag convention (see cmake/getversion.cmake): +# first beta for a version: YYYY-MM-DD-Development-X.Y.Z-beta +# subsequent betas: YYYY-MM-DD-Development-X.Y.Z-beta.2, .3, ... +# Version defaults to the PROJECT VERSION in CMakeLists.txt. +# +# Usage: +# ./publish_beta.sh [--dry-run] [--yes] [--version X.Y.Z] [--remote upstream] [--branch master] + +set -euo pipefail + +REMOTE="upstream" +BRANCH="master" +VERSION="" +DRY_RUN=0 +ASSUME_YES=0 + +usage() { + cat <&2 + usage >&2 + exit 1 + ;; + esac +done + +cd "$(dirname "${BASH_SOURCE[0]}")" || exit 2 + +if ! git rev-parse --git-dir >/dev/null 2>&1; then + echo "Error: not inside a git repository." >&2 + exit 1 +fi + +if [[ -z "$VERSION" ]]; then + VERSION="$(grep -m1 -oP 'project\(\s*"Cockatrice"\s+VERSION\s+\K[0-9]+\.[0-9]+\.[0-9]+' CMakeLists.txt || true)" +fi +if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Error: could not determine version (got '$VERSION'). Pass --version X.Y.Z." >&2 + exit 1 +fi + +if [[ -n "$(git status --porcelain)" ]]; then + echo "Warning: working tree has uncommitted changes. They will not be included in the tag." >&2 + git status --short >&2 +fi + +echo "==> git checkout $BRANCH" +if [[ "$DRY_RUN" -eq 0 ]]; then + git checkout "$BRANCH" +else + echo "(dry-run) skipped" +fi + +echo "==> git remote update -p" +if [[ "$DRY_RUN" -eq 0 ]]; then + git remote update -p +else + echo "(dry-run) skipped" +fi + +echo "==> git pull --ff-only" +if [[ "$DRY_RUN" -eq 0 ]]; then + git pull --ff-only + git fetch --tags "$REMOTE" +else + echo "(dry-run) skipped" +fi + +DATE="$(date +%F)" +PATTERN="*-Development-${VERSION}-beta*" + +# Highest beta number used so far for this version. +# A bare "-beta" (no suffix) counts as 1; "-beta.N" counts as N. +MAX_BETA=0 +while IFS= read -r tag; do + [[ -n "$tag" ]] || continue + if [[ "$tag" =~ -beta\.([0-9]+)$ ]]; then + NUM="${BASH_REMATCH[1]}" + elif [[ "$tag" =~ -beta$ ]]; then + NUM=1 + else + continue + fi + if [[ "$NUM" -gt "$MAX_BETA" ]]; then + MAX_BETA="$NUM" + fi +done < <(git tag --list "$PATTERN") + +NEXT_BETA=$((MAX_BETA + 1)) +if [[ "$NEXT_BETA" -le 1 ]]; then + NEW_TAG="${DATE}-Development-${VERSION}-beta" +else + NEW_TAG="${DATE}-Development-${VERSION}-beta.${NEXT_BETA}" +fi + +echo "" +echo "Version: $VERSION" +echo "Latest beta: $MAX_BETA (for $VERSION)" +echo "New tag: $NEW_TAG" +echo "Head: $(git rev-parse --short HEAD) $(git log -1 --oneline | head -c 120)" +echo "" + +if git rev-parse -q --verify "refs/tags/${NEW_TAG}" >/dev/null; then + echo "Error: tag $NEW_TAG already exists locally." >&2 + exit 1 +fi +if git ls-remote --tags "$REMOTE" | grep -q "refs/tags/${NEW_TAG}$"; then + echo "Error: tag $NEW_TAG already exists on $REMOTE." >&2 + exit 1 +fi + +if [[ "$DRY_RUN" -eq 1 ]]; then + echo "(dry-run) would run: git tag $NEW_TAG" + echo "(dry-run) would run: git push $REMOTE $NEW_TAG" + exit 0 +fi + +if [[ "$ASSUME_YES" -eq 0 ]]; then + read -r -p "Create tag $NEW_TAG and push to $REMOTE? [y/N] " answer + if [[ ! "$answer" =~ ^[Yy]$ ]]; then + echo "Aborted." + exit 1 + fi +fi + +git tag "$NEW_TAG" +git push "$REMOTE" "$NEW_TAG" + +echo "Done: $NEW_TAG" diff --git a/servatrice/CMakeLists.txt b/servatrice/CMakeLists.txt index f6d28d6c1..9d8dd46d2 100644 --- a/servatrice/CMakeLists.txt +++ b/servatrice/CMakeLists.txt @@ -7,6 +7,7 @@ project(Servatrice VERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${ set(servatrice_SOURCES src/email_parser.cpp src/event_loop_watchdog.cpp + src/forwarded_address.cpp src/main.cpp src/metrics_registry.cpp src/ratelimiter.cpp @@ -18,6 +19,7 @@ set(servatrice_SOURCES src/settingscache.cpp src/isl_interface.cpp src/signalhandler.cpp + src/trusted_sources.cpp ${VERSION_STRING_CPP} src/smtpclient.cpp src/smtp/qxthmac.cpp @@ -54,7 +56,7 @@ if(APPLE) set(servatrice_SOURCES ${servatrice_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/resources/appicon.icns) endif(APPLE) -qt6_add_resources(servatrice_RESOURCES_RCC ${servatrice_RESOURCES}) +qt_add_resources(servatrice_RESOURCES_RCC ${servatrice_RESOURCES}) set(QT_DONT_USE_QTGUI TRUE) diff --git a/servatrice/migrations/servatrice_0037_to_0038.sql b/servatrice/migrations/servatrice_0037_to_0038.sql new file mode 100644 index 000000000..ad6477c46 --- /dev/null +++ b/servatrice/migrations/servatrice_0037_to_0038.sql @@ -0,0 +1,29 @@ +-- Servatrice db migration from version 37 to version 38 + +-- Room chat channels. +-- +-- A room now offers a fixed catalog of named message streams in addition to the +-- implicit Main channel. Public channels are broadcast to every room member; +-- restricted channels are only delivered to (and sendable by) members whose +-- privilege level meets the channel's access level. + +-- 1. Catalog table, mirroring the config-driven channels of no-DB servers. +CREATE TABLE IF NOT EXISTS `cockatrice_rooms_channels` ( + `id_room` int(7) unsigned NOT NULL, + `id_server` tinyint(3) NOT NULL DEFAULT 1, + `channel_id` varchar(50) NOT NULL, + `display_name` varchar(50) NOT NULL, + `access_level` ENUM('public','registered','judge','moderator','admin','developer') NOT NULL DEFAULT 'public', + PRIMARY KEY (`id_room`, `id_server`, `channel_id`), + FOREIGN KEY(`id_room`) REFERENCES `cockatrice_rooms`(`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; + +-- 2. Audit log: room_channel target type plus a channel column so moderators +-- can tell which stream a logged message belongs to. +ALTER TABLE `cockatrice_log` + MODIFY `target_type` ENUM('room', 'game', 'chat', 'room_channel'); + +ALTER TABLE `cockatrice_log` + ADD COLUMN `channel` varchar(50) NULL AFTER `target_name`; + +UPDATE cockatrice_schema_version SET version=38 WHERE version=37; \ No newline at end of file diff --git a/servatrice/migrations/servatrice_0038_to_0039.sql b/servatrice/migrations/servatrice_0038_to_0039.sql new file mode 100644 index 000000000..0cad5f494 --- /dev/null +++ b/servatrice/migrations/servatrice_0038_to_0039.sql @@ -0,0 +1,19 @@ +-- Servatrice db migration from version 38 to version 39 + +-- Per-channel read/write split and banner messages. +-- +-- write_access_level gates sending: NULL (the default) means writing requires +-- the same level as reading (access_level), preserving the previous +-- single-level behavior. Set it higher than access_level for read-mostly +-- channels (e.g. a developer news feed readable by everyone but writable only +-- by developers). +-- +-- banner_message is a static notice shown at the top of the channel, +-- analogous to the room join message on Main. Empty = none. + +ALTER TABLE `cockatrice_rooms_channels` + MODIFY `access_level` ENUM('public','registered','judge','moderator','admin','developer') NOT NULL DEFAULT 'public', + ADD COLUMN `write_access_level` ENUM('public','registered','judge','moderator','admin','developer') NULL DEFAULT NULL, + ADD COLUMN `banner_message` varchar(255) NOT NULL DEFAULT ''; + +UPDATE cockatrice_schema_version SET version=39 WHERE version=38; diff --git a/servatrice/migrations/servatrice_0039_to_0040.sql b/servatrice/migrations/servatrice_0039_to_0040.sql new file mode 100644 index 000000000..8298d03f9 --- /dev/null +++ b/servatrice/migrations/servatrice_0039_to_0040.sql @@ -0,0 +1,17 @@ +-- Servatrice db migration from version 39 to version 40 + +-- The column must hold "$scrypt$$$

$$" (up to ~255 chars) and arbitrary +-- legacy base64 hashes, so it grows beyond the old 120-char size. varchar(255) is used because +-- widening a CHAR requires a table rebuild, which ALGORITHM=INSTANT cannot perform — dropping the +-- clause lets the server pick a suitable algorithm (and any row-format change is avoided anyway). +ALTER TABLE `cockatrice_users` MODIFY `password_sha512` varchar(255) NOT NULL; + +-- Encrypted downgrade backup: lets pre-challenge clients keep logging in after the +-- account migrates to scrypt, without storing the fast-crackable legacy hash in the +-- clear. Sealed with security/legacy_backup_key and purged after +-- security/legacy_backup_ttl_days without use (see password_legacy_backup_used_at). +ALTER TABLE `cockatrice_users` + ADD COLUMN `password_legacy_backup` varchar(255) NOT NULL DEFAULT '', + ADD COLUMN `password_legacy_backup_used_at` datetime DEFAULT NULL; + +UPDATE cockatrice_schema_version SET version=40 WHERE version=39; diff --git a/servatrice/servatrice.ini.example b/servatrice/servatrice.ini.example index f802f0244..ac291fd7f 100644 --- a/servatrice/servatrice.ini.example +++ b/servatrice/servatrice.ini.example @@ -42,9 +42,11 @@ websocket_host=any websocket_port=4748 ; If websockets are served through a reverse proxy (e.g. nginx), the real client address can be -; read from an HTTP header such as X-Forwarded-For. The header is ONLY trusted when the websocket -; connection itself originates from an address listed in security/trusted_sources (default 127.0.0.1,::1), -; so that clients cannot spoof their address to bypass bans or rate limits. +; read from an HTTP header such as X-Real-IP or X-Forwarded-For. A comma-separated X-Forwarded-For +; list resolves to its rightmost entry, which is the one the proxy appended. The header is ONLY +; trusted when the websocket connection itself originates from an address listed in +; security/trusted_sources (default 127.0.0.1,::1), so that clients cannot spoof their address to +; bypass bans or rate limits. web_socket_ip_header= ; When database is enabled, servatrice writes the server status in the "update" database table; this @@ -332,6 +334,40 @@ roomlist\1\game_types\1\name="GameType1" roomlist\1\game_types\2\name="GameType2" roomlist\1\game_types\3\name="GameType3" +; Optional chat channels for room number 1, in addition to the implicit Main +; channel every room has. Each channel needs a stable ASCII id, a display name +; (server data, like the room name) and an access level. Supported levels, from +; least to most restrictive, are: public, registered, judge, moderator, admin +; and developer. Access is evaluated against the member's user flags, not as a +; ladder: a moderator channel admits moderators (admins hold the moderator +; flag alongside the admin flag, so they are admitted too), an admin channel +; admits only admins, and a developer channel admits only developers and +; admins. In particular, developers cannot read moderator or admin channels. +; A judge channel additionally admits moderators. +; Public channels are visible to every room member. +; +; Write access defaults to the read level, so by default a channel is readable +; and writable by the same members. Set writeaccess higher for read-mostly +; channels (e.g. a developer news feed readable by everyone but writable only +; by developers). Members who can read but not write see the channel with a +; disabled message input. +; +; banner is an optional static notice shown at the top of the channel, similar +; to the room joinmessage on Main. Empty or unset = none. +roomlist\1\channels\size=3 +roomlist\1\channels\1\id=looking-for-players +roomlist\1\channels\1\displayname="Looking for players" +roomlist\1\channels\1\access=public +roomlist\1\channels\1\banner="Find opponents here. Be friendly!" +roomlist\1\channels\2\id=moderators +roomlist\1\channels\2\displayname="Moderators" +roomlist\1\channels\2\access=moderator +roomlist\1\channels\3\id=developer-news +roomlist\1\channels\3\displayname="Developer news" +roomlist\1\channels\3\access=public +roomlist\1\channels\3\writeaccess=developer +roomlist\1\channels\3\banner="Official announcements from the developers." + [game] @@ -386,10 +422,55 @@ max_forgot_password_requests_per_ip=3 ; Length in seconds of the forgot-password window. Default is 3600 (1 hour). forgot_password_window_seconds=3600 +; How strictly new authentication features are enforced. Possible values: +; * legacy: accounts that still use the legacy 1000-round SHA-512 hash keep logging in with it +; (they are served the legacy salt, never a challenge-response nonce) and are never +; auto-migrated to scrypt. Already-migrated scrypt rows keep logging in via +; challenge-response. New credentials may use either format; +; * mixed: accept both legacy hashes and challenge-response authentication (default); +; * strict: only accept challenge-response authentication from clients that support it, +; and reject plain password submissions. Legacy accounts are migrated to scrypt +; automatically on their next successful login. Strict only constrains +; registered SQL accounts: guest/unregistered logins and servers with +; authentication/method set to none or password are unaffected, since +; they have no stored verifier to challenge against. +; +; Note: anyone holding only the legacy salt+hash of a not-yet-migrated account +; (e.g. from a wire capture of an old client, or an old database dump) can log in +; with it and then set a brand new password of their choosing. Before +; challenge-response a captured hash yielded a session, but changing the password +; still required the plaintext old password. There is no way around this without +; a plaintext proof at migration time, so not-yet-migrated rows should be treated +; accordingly. +; +; Challenge-response verifiers are scrypt (RFC 7914, N=32768, r=8, p=1) stored as +; "$scrypt$$$

$$". The stored verifier is password-equivalent: +; plaintext passwords never reach the client configuration and never go over the wire, but +; a database dump yields credentials that can answer a login challenge directly. +authentication_strictness=mixed + +; Optional key that seals the legacy password backup (64 hex characters). +; Migrating an account to scrypt normally strands old clients, which can only present +; the legacy hash. With a key configured, the server keeps that hash encrypted next to +; the scrypt verifier, so old clients can still log in during the window below without +; the database holding a fast-crackable hash in the clear. +; Generate with: openssl rand -hex 32 +; If unset there is no downgrade path at all: a migrated account keeps only a scrypt +; verifier, and an administrator's password reset stores a verifier too, so pre-challenge +; clients cannot log in with the temporary password either. Set the key if you still serve +; them. Rotating the key invalidates existing backups (they become unusable immediately; +; expired ones are still purged). +legacy_backup_key= + +; Days a legacy password backup may go unused before it is purged from the database. +; Logging in with an old client refreshes the window; backups that are never touched +; age out. Default 180. +legacy_backup_ttl_days=180 ; You may want to allow an unlimited number of users from a trusted source. This setting can contain a ; comma-separed list of IP addresses which will allow an unlimited number of connections from each of the -; IP addresses listed (ignoring the max_users_per_address). Default is "127.0.0.1,::1"; example: "192.73.233.244,81.4.100.74" +; IP addresses listed (ignoring the max_users_per_address). Entries must be exact IP addresses; hostnames, prefixes +; and subnets are ignored. Default is "127.0.0.1,::1"; example: "192.73.233.244,81.4.100.74" trusted_sources="127.0.0.1,::1" ; Servatrice can avoid users from flooding rooms with large number of messages in an interval of time. diff --git a/servatrice/servatrice.sql b/servatrice/servatrice.sql index 4fbc1d8bd..e942e4e91 100644 --- a/servatrice/servatrice.sql +++ b/servatrice/servatrice.sql @@ -20,7 +20,7 @@ CREATE TABLE IF NOT EXISTS `cockatrice_schema_version` ( PRIMARY KEY (`version`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; -INSERT INTO cockatrice_schema_version VALUES(37); +INSERT INTO cockatrice_schema_version VALUES(40); -- users and user data tables CREATE TABLE IF NOT EXISTS `cockatrice_users` ( @@ -31,7 +31,9 @@ CREATE TABLE IF NOT EXISTS `cockatrice_users` ( `admin` tinyint(1) NOT NULL, `name` varchar(35) NOT NULL, `realname` varchar(255) NOT NULL, - `password_sha512` char(120) NOT NULL, + `password_sha512` varchar(255) NOT NULL, + `password_legacy_backup` varchar(255) NOT NULL DEFAULT '', + `password_legacy_backup_used_at` datetime DEFAULT NULL, `email` varchar(255) NOT NULL, `country` char(2) NOT NULL, `avatar_bmp` mediumblob NOT NULL, @@ -158,6 +160,18 @@ CREATE TABLE IF NOT EXISTS `cockatrice_rooms_gametypes` ( FOREIGN KEY(`id_room`) REFERENCES `cockatrice_rooms`(`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; +CREATE TABLE IF NOT EXISTS `cockatrice_rooms_channels` ( + `id_room` int(7) unsigned NOT NULL, + `id_server` tinyint(3) NOT NULL DEFAULT 1, + `channel_id` varchar(50) NOT NULL, + `display_name` varchar(50) NOT NULL, + `access_level` ENUM('public','registered','judge','moderator','admin','developer') NOT NULL DEFAULT 'public', + `write_access_level` ENUM('public','registered','judge','moderator','admin','developer') NULL DEFAULT NULL, + `banner_message` varchar(255) NOT NULL DEFAULT '', + PRIMARY KEY (`id_room`, `id_server`, `channel_id`), + FOREIGN KEY(`id_room`) REFERENCES `cockatrice_rooms`(`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 DEFAULT COLLATE utf8mb4_unicode_ci; + -- games CREATE TABLE IF NOT EXISTS `cockatrice_games` ( `room_name` varchar(255) NOT NULL, @@ -329,9 +343,10 @@ CREATE TABLE IF NOT EXISTS `cockatrice_log` ( `sender_name` varchar(35) NOT NULL, `sender_ip` varchar(45) NOT NULL, `log_message` text NOT NULL, - `target_type` ENUM('room', 'game', 'chat'), + `target_type` ENUM('room', 'game', 'chat', 'room_channel'), `target_id` int(7) NULL, `target_name` varchar(50) NOT NULL, + `channel` varchar(50) NULL, KEY `sender_name` (`sender_name`), KEY `sender_ip` (`sender_ip`), KEY `target_id` (`target_id`), diff --git a/servatrice/src/forwarded_address.cpp b/servatrice/src/forwarded_address.cpp new file mode 100644 index 000000000..8d5d3f4c1 --- /dev/null +++ b/servatrice/src/forwarded_address.cpp @@ -0,0 +1,21 @@ +#include "forwarded_address.h" + +#include + +QHostAddress parseForwardedAddress(const QByteArray &headerValue) +{ + const QList entries = headerValue.split(','); + for (auto it = entries.crbegin(); it != entries.crend(); ++it) { + const QByteArray entry = it->trimmed(); + if (entry.isEmpty()) { + continue; + } + + const QHostAddress parsed(QString::fromLatin1(entry)); + if (!parsed.isNull()) { + return parsed; + } + } + + return {}; +} diff --git a/servatrice/src/forwarded_address.h b/servatrice/src/forwarded_address.h new file mode 100644 index 000000000..d77e041bd --- /dev/null +++ b/servatrice/src/forwarded_address.h @@ -0,0 +1,18 @@ +#ifndef FORWARDED_ADDRESS_H +#define FORWARDED_ADDRESS_H + +#include +#include + +/** + * @brief Parses the real client address out of a forwarded header value (X-Real-IP, X-Forwarded-For). + * + * A comma-separated list resolves to its rightmost usable entry. That entry was appended by the + * proxy closest to the server and is the only one a direct client cannot choose, since the earlier + * entries are passed through verbatim from whatever the client sent. + * + * @return the parsed address, or a null QHostAddress when the value holds none. + */ +QHostAddress parseForwardedAddress(const QByteArray &headerValue); + +#endif diff --git a/servatrice/src/isl_interface.cpp b/servatrice/src/isl_interface.cpp index a78ee9e72..1898cd5fa 100644 --- a/servatrice/src/isl_interface.cpp +++ b/servatrice/src/isl_interface.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -57,17 +58,16 @@ void IslInterface::sharedCtor(const QSslCertificate &cert, const QSslKey &privat socket->setLocalCertificate(cert); socket->setPrivateKey(privateKey); - connect(socket, SIGNAL(readyRead()), this, SLOT(readClient()), Qt::QueuedConnection); - connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, - SLOT(catchSocketError(QAbstractSocket::SocketError))); - connect(this, SIGNAL(outputBufferChanged()), this, SLOT(flushOutputBuffer()), Qt::QueuedConnection); + connect(socket, &QSslSocket::readyRead, this, &IslInterface::readClient, Qt::QueuedConnection); + connect(socket, &QAbstractSocket::errorOccurred, this, &IslInterface::catchSocketError); + connect(this, &IslInterface::outputBufferChanged, this, &IslInterface::flushOutputBuffer, Qt::QueuedConnection); } IslInterface::IslInterface(int _socketDescriptor, const QSslCertificate &cert, const QSslKey &privateKey, Servatrice *_server) - : QObject(), socketDescriptor(_socketDescriptor), server(_server), messageInProgress(false) + : QObject(), serverId(-1), socketDescriptor(_socketDescriptor), server(_server), messageInProgress(false) { sharedCtor(cert, privateKey); } @@ -263,13 +263,17 @@ void IslInterface::readClient() server->incRxBytes(data.size()); inputBuffer.append(data); - do { + // Parse messages straight out of inputBuffer at readOffset. Removing each + // consumed message from the front would memmove the remaining bytes once + // per message; the consumed prefix is dropped once parsing stops. + qsizetype readOffset = 0; + + while (true) { if (!messageInProgress) { - if (inputBuffer.size() >= 4) { - messageLength = (((quint32)(unsigned char)inputBuffer[0]) << 24) + - (((quint32)(unsigned char)inputBuffer[1]) << 16) + - (((quint32)(unsigned char)inputBuffer[2]) << 8) + - ((quint32)(unsigned char)inputBuffer[3]); + if (inputBuffer.size() - readOffset >= 4) { + const auto *header = reinterpret_cast(inputBuffer.constData() + readOffset); + messageLength = (static_cast(header[0]) << 24) + (static_cast(header[1]) << 16) + + (static_cast(header[2]) << 8) + static_cast(header[3]); // Reject implausible lengths instead of buffering until they arrive. // This also handles lengths that overflow into negative int values. if (messageLength < 0 || messageLength > MAX_ISL_MESSAGE_SIZE) { @@ -277,19 +281,19 @@ void IslInterface::readClient() socket->disconnectFromHost(); return; } - inputBuffer.remove(0, 4); + readOffset += 4; messageInProgress = true; } else { - return; + break; } } - if (inputBuffer.size() < messageLength) { - return; + if (inputBuffer.size() - readOffset < messageLength) { + break; } IslMessage newMessage; - bool ok = newMessage.ParseFromArray(inputBuffer.data(), messageLength); - inputBuffer.remove(0, messageLength); + bool ok = newMessage.ParseFromArray(inputBuffer.constData() + readOffset, messageLength); + readOffset += messageLength; messageInProgress = false; if (ok) { @@ -297,7 +301,12 @@ void IslInterface::readClient() } else { qCWarning(IslInterfaceLog) << "parsing error!"; } - } while (!inputBuffer.isEmpty()); + } + + // Drop the consumed prefix once, instead of shifting the buffer per message. + if (readOffset > 0) { + inputBuffer.remove(0, readOffset); + } } void IslInterface::catchSocketError(QAbstractSocket::SocketError socketError) @@ -383,7 +392,14 @@ void IslInterface::roomEvent_UserLeft(int roomId, const Event_LeaveRoom &event) void IslInterface::roomEvent_Say(int roomId, const Event_RoomSay &event) { - emit externalRoomSay(roomId, QString::fromStdString(event.name()), QString::fromStdString(event.message())); + emit externalRoomSay(roomId, QString::fromStdString(event.name()), QString::fromStdString(event.message()), + QString()); +} + +void IslInterface::roomEvent_ChannelSay(int roomId, const Event_RoomChannelSay &event) +{ + emit externalRoomSay(roomId, QString::fromStdString(event.name()), QString::fromStdString(event.message()), + QString::fromStdString(event.channel_id())); } void IslInterface::roomEvent_ListGames(int roomId, const Event_ListGames &event) @@ -459,6 +475,9 @@ void IslInterface::processRoomEvent(const RoomEvent &event) case RoomEvent::ROOM_SAY: roomEvent_Say(event.room_id(), event.GetExtension(Event_RoomSay::ext)); break; + case RoomEvent::ROOM_CHANNEL_SAY: + roomEvent_ChannelSay(event.room_id(), event.GetExtension(Event_RoomChannelSay::ext)); + break; case RoomEvent::LIST_GAMES: roomEvent_ListGames(event.room_id(), event.GetExtension(Event_ListGames::ext)); break; @@ -484,7 +503,9 @@ void IslInterface::processRoomCommand(const CommandContainer &cont, qint64 sessi void IslInterface::processMessage(const IslMessage &item) { - qCDebug(IslInterfaceLog) << getSafeDebugString(item); + if (IslInterfaceLog().isDebugEnabled()) { + qCDebug(IslInterfaceLog) << getSafeDebugString(item); + } switch (item.message_type()) { case IslMessage::ROOM_COMMAND_CONTAINER: { diff --git a/servatrice/src/isl_interface.h b/servatrice/src/isl_interface.h index f7680c0a2..c1d9334a5 100644 --- a/servatrice/src/isl_interface.h +++ b/servatrice/src/isl_interface.h @@ -26,6 +26,7 @@ class Event_UserLeft; class Event_JoinRoom; class Event_LeaveRoom; class Event_RoomSay; +class Event_RoomChannelSay; class Event_ListGames; class Event_RemoveMessages; class Command_JoinGame; @@ -49,7 +50,7 @@ signals: void externalUserLeft(QString userName); void externalRoomUserJoined(int roomId, ServerInfo_User userInfo); void externalRoomUserLeft(int roomId, QString userName); - void externalRoomSay(int roomId, QString userName, QString message); + void externalRoomSay(int roomId, QString userName, QString message, QString channelId); void externalRoomGameListChanged(int roomId, ServerInfo_Game gameInfo); void externalRoomRemoveMessages(int roomId, QString userName, int amount); void joinGameCommandReceived(const Command_JoinGame &cmd, int cmdId, int roomId, int serverId, qint64 sessionId); @@ -79,6 +80,7 @@ private: void roomEvent_UserJoined(int roomId, const Event_JoinRoom &event); void roomEvent_UserLeft(int roomId, const Event_LeaveRoom &event); void roomEvent_Say(int roomId, const Event_RoomSay &event); + void roomEvent_ChannelSay(int roomId, const Event_RoomChannelSay &event); void roomEvent_ListGames(int roomId, const Event_ListGames &event); void roomEvent_RemoveMessages(int roomId, const Event_RemoveMessages &event); diff --git a/servatrice/src/main.cpp b/servatrice/src/main.cpp index eb019a2b7..4d513d026 100644 --- a/servatrice/src/main.cpp +++ b/servatrice/src/main.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -162,14 +163,15 @@ int main(int argc, char *argv[]) configPath = SettingsCache::guessConfigurationPath(); } else if (!QFile::exists(configPath)) { qCritical() << "Could not find configuration file at" << configPath; - return 1; + return EXIT_FAILURE; } qWarning() << "Using configuration file: " << configPath; settingsCache = new SettingsCache(configPath); loggerThread = new QThread; loggerThread->setObjectName("logger"); - logger = new ServerLogger(logToConsole); + logger = new ServerLogger(logToConsole, settingsCache->value("server/writelog", 1).toBool(), + settingsCache->value("server/logfilters").toString()); logger->moveToThread(loggerThread); loggerThread->start(); @@ -214,8 +216,16 @@ int main(int argc, char *argv[]) std::cerr << "Server quit." << std::endl; std::cerr << "-------------------------" << std::endl; + } else { + // initServer() logged the reason; a failed startup must not look like a clean exit. + std::cerr << "Server initialization failed, exiting." << std::endl; + retval = EXIT_FAILURE; } + // The handler reads the logger and settingsCache globals torn down below; leaving it + // installed lets Qt's own shutdown warnings dereference freed memory. + qInstallMessageHandler(nullptr); + delete smtpClient; delete rng; delete signalhandler; diff --git a/servatrice/src/servatrice.cpp b/servatrice/src/servatrice.cpp index a832c1652..afe028f32 100644 --- a/servatrice/src/servatrice.cpp +++ b/servatrice/src/servatrice.cpp @@ -33,6 +33,7 @@ #include "serversocketinterface.h" #include "settingscache.h" #include "smtpclient.h" +#include "trusted_sources.h" #include #include @@ -45,6 +46,8 @@ #include #include #include +#include +#include #include #include #include @@ -65,7 +68,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -73,6 +78,10 @@ inline Q_LOGGING_CATEGORY(ServatriceLog, "servatrice"); +// Expired legacy password backups are also purged on first use, but idle accounts +// need this sweep so credentials that are never touched still age out of the database. +static constexpr qint64 kLegacyBackupSweepIntervalMs = 6 * 60 * 60 * 1000; + Servatrice_GameServer::Servatrice_GameServer(Servatrice *_server, int _numberPools, const QSqlDatabase &_sqlDatabase, @@ -91,8 +100,10 @@ Servatrice_GameServer::Servatrice_GameServer(Servatrice *_server, newThread->start(); server->watchWorkerThread(newThread); - QMetaObject::invokeMethod(newDatabaseInterface, "initDatabase", Qt::BlockingQueuedConnection, - Q_ARG(QSqlDatabase, _sqlDatabase)); + QMetaObject::invokeMethod( + newDatabaseInterface, + [newDatabaseInterface, _sqlDatabase] { newDatabaseInterface->initDatabase(_sqlDatabase); }, + Qt::BlockingQueuedConnection); connectionPools.append(newPool); } @@ -116,9 +127,10 @@ void Servatrice_GameServer::incomingConnection(qintptr socketDescriptor) auto ssi = new TcpServerSocketInterface(server, pool->getDatabaseInterface()); ssi->moveToThread(pool->thread()); pool->addClient(); - connect(ssi, SIGNAL(destroyed()), pool, SLOT(removeClient())); + connect(ssi, &QObject::destroyed, pool, &Servatrice_ConnectionPool::removeClient); - QMetaObject::invokeMethod(ssi, "initConnection", Qt::QueuedConnection, Q_ARG(int, socketDescriptor)); + QMetaObject::invokeMethod( + ssi, [ssi, socketDescriptor] { ssi->initConnection(socketDescriptor); }, Qt::QueuedConnection); } Servatrice_ConnectionPool *Servatrice_GameServer::findLeastUsedConnectionPool() @@ -159,13 +171,15 @@ Servatrice_WebsocketGameServer::Servatrice_WebsocketGameServer(Servatrice *_serv newThread->start(); server->watchWorkerThread(newThread); - QMetaObject::invokeMethod(newDatabaseInterface, "initDatabase", Qt::BlockingQueuedConnection, - Q_ARG(QSqlDatabase, _sqlDatabase)); + QMetaObject::invokeMethod( + newDatabaseInterface, + [newDatabaseInterface, _sqlDatabase] { newDatabaseInterface->initDatabase(_sqlDatabase); }, + Qt::BlockingQueuedConnection); connectionPools.append(newPool); - - connect(this, SIGNAL(newConnection()), this, SLOT(onNewConnection())); } + + connect(this, &QWebSocketServer::newConnection, this, &Servatrice_WebsocketGameServer::onNewConnection); } Servatrice_WebsocketGameServer::~Servatrice_WebsocketGameServer() @@ -190,9 +204,10 @@ void Servatrice_WebsocketGameServer::onNewConnection() */ // ssi->moveToThread(pool->thread()); pool->addClient(); - connect(ssi, SIGNAL(destroyed()), pool, SLOT(removeClient())); + connect(ssi, &QObject::destroyed, pool, &Servatrice_ConnectionPool::removeClient); - QMetaObject::invokeMethod(ssi, "initConnection", Qt::QueuedConnection, Q_ARG(void *, nextPendingConnection())); + void *socket = nextPendingConnection(); + QMetaObject::invokeMethod(ssi, [ssi, socket] { ssi->initConnection(socket); }, Qt::QueuedConnection); } Servatrice_ConnectionPool *Servatrice_WebsocketGameServer::findLeastUsedConnectionPool() @@ -215,14 +230,14 @@ Servatrice_ConnectionPool *Servatrice_WebsocketGameServer::findLeastUsedConnecti void Servatrice_IslServer::incomingConnection(qintptr socketDescriptor) { auto thread = new QThread; - connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); auto interface = new IslInterface(static_cast(socketDescriptor), cert, privateKey, server); interface->moveToThread(thread); - connect(interface, SIGNAL(destroyed()), thread, SLOT(quit())); + connect(interface, &QObject::destroyed, thread, &QThread::quit); thread->start(); - QMetaObject::invokeMethod(interface, "initServer", Qt::QueuedConnection); + QMetaObject::invokeMethod(interface, [interface] { interface->initServer(); }, Qt::QueuedConnection); } Servatrice::Servatrice(QObject *parent) @@ -249,6 +264,37 @@ Servatrice::~Servatrice() prepareDestroy(); } +/** + * @brief Maps a configured channel access level (database column or ini value) + * onto the ServerInfo_RoomChannel privilege ladder. + * + * Returns nullopt for unknown keywords so callers can fail closed instead of + * silently downgrading a restricted channel to Public. + */ +static std::optional roomChannelAccessFromKey(const QString &key) +{ + const QString level = key.trimmed().toLower(); + if (level == "developer") { + return ServerInfo_RoomChannel::Developer; + } + if (level == "admin") { + return ServerInfo_RoomChannel::Admin; + } + if (level == "moderator") { + return ServerInfo_RoomChannel::Moderator; + } + if (level == "judge") { + return ServerInfo_RoomChannel::Judge; + } + if (level == "registered") { + return ServerInfo_RoomChannel::Registered; + } + if (level == "public") { + return ServerInfo_RoomChannel::Public; + } + return std::nullopt; +} + bool Servatrice::initServer() { @@ -268,7 +314,8 @@ bool Servatrice::initServer() authenticationMethod = AuthenticationPassword; } else { if (getRegOnlyServerEnabled()) { - qDebug() << "Registration only server enabled but no authentication method defined: Error."; + qCCritical(ServatriceLog) << "Registration only server enabled but no authentication method defined; " + "refusing to start."; return false; } qDebug() << "Authenticating method: none"; @@ -285,6 +332,14 @@ bool Servatrice::initServer() qDebug() << "Maximum websocket user limit:" << getMaxWebSocketUserLimit(); } + // trusted_sources entries are matched as exact addresses; older configs may hold hostnames, + // prefixes or subnets that substring matching used to accept and that now never match. + for (const QString &entry : getTrustedSources().split(',', Qt::SkipEmptyParts)) { + if (QHostAddress(entry.trimmed()).isNull()) { + qWarning() << "security/trusted_sources entry is not an IP address and will be ignored:" << entry.trimmed(); + } + } + qDebug() << "Accept registered users only:" << getRegOnlyServerEnabled(); qDebug() << "Registration enabled:" << getRegistrationEnabled(); if (getRegistrationEnabled()) { @@ -331,7 +386,7 @@ bool Servatrice::initServer() bool dbOpened = servatriceDatabaseInterface->initDatabase( "QMYSQL", getDBHostNameString(), getDBDatabaseNameString(), getDBUserNameString(), getDBPasswordString()); if (!dbOpened) { - qDebug() << "Failed to open database"; + qCCritical(ServatriceLog) << "Failed to open the database; refusing to start."; return false; } updateServerList(); @@ -360,10 +415,65 @@ bool Servatrice::initServer() while (query2->next()) { gameTypes.append(query2->value(0).toString()); } + + QSqlQuery *query3 = servatriceDatabaseInterface->prepareQuery( + "select channel_id, display_name, access_level, write_access_level, banner_message from " + "{prefix}_rooms_channels where id_room = :id_room " + "AND id_server = :id_server order by channel_id asc"); + query3->bindValue(":id_server", serverId); + query3->bindValue(":id_room", query->value(0).toInt()); + servatriceDatabaseInterface->execSqlQuery(query3); + QList channels; + QSet seenChannelIds; + while (query3->next()) { + const QString channelId = query3->value(0).toString(); + if (channelId.isEmpty()) { + qWarning() << "Skipping room channel with empty id"; + continue; + } + if (Server_Room::isMainChannel(channelId)) { + qWarning() << "Skipping room channel with reserved id" << channelId; + continue; + } + if (seenChannelIds.contains(channelId)) { + qWarning() << "Skipping duplicate room channel id" << channelId; + continue; + } + seenChannelIds.insert(channelId); + const QString accessKey = query3->value(2).toString(); + auto accessLevel = + accessKey.trimmed().isEmpty() + ? std::optional(ServerInfo_RoomChannel::Public) + : roomChannelAccessFromKey(accessKey); + if (!accessLevel.has_value()) { + qWarning() << "Skipping room channel with unknown access level" << accessKey << "for id" + << channelId; + continue; + } + ServerInfo_RoomChannel channel; + channel.set_id(channelId.toStdString()); + channel.set_display_name(query3->value(1).toString().toStdString()); + channel.set_access_level(accessLevel.value()); + if (!query3->isNull(3)) { + const QString writeKey = query3->value(3).toString(); + if (!writeKey.trimmed().isEmpty()) { + auto writeLevel = roomChannelAccessFromKey(writeKey); + if (!writeLevel.has_value()) { + qWarning() << "Skipping room channel with unknown write access level" << writeKey + << "for id" << channelId; + continue; + } + channel.set_write_access_level(writeLevel.value()); + } + } + channel.set_banner_message(query3->value(4).toString().toStdString()); + channels.append(channel); + } + addRoom(new Server_Room(query->value(0).toInt(), query->value(7).toInt(), query->value(1).toString(), query->value(2).toString(), query->value(3).toString().toLower(), query->value(4).toString().toLower(), static_cast(query->value(5).toInt()), - query->value(6).toString(), gameTypes, this)); + query->value(6).toString(), gameTypes, this, channels)); } } else { int size = settingsCache->beginReadArray("rooms/roomlist"); @@ -376,12 +486,61 @@ bool Servatrice::initServer() gameTypes.append(settingsCache->value("name").toString()); } settingsCache->endArray(); + QList channels; + QSet seenChannelIds; + int size3 = settingsCache->beginReadArray("channels"); + for (int k = 0; k < size3; ++k) { + settingsCache->setArrayIndex(k); + const QString channelId = settingsCache->value("id").toString(); + if (channelId.isEmpty()) { + qWarning() << "Skipping room channel with empty id"; + continue; + } + if (Server_Room::isMainChannel(channelId)) { + qWarning() << "Skipping room channel with reserved id" << channelId; + continue; + } + if (seenChannelIds.contains(channelId)) { + qWarning() << "Skipping duplicate room channel id" << channelId; + continue; + } + seenChannelIds.insert(channelId); + const QString accessKey = settingsCache->value("access").toString(); + auto accessLevel = + accessKey.trimmed().isEmpty() + ? std::optional(ServerInfo_RoomChannel::Public) + : roomChannelAccessFromKey(accessKey); + if (!accessLevel.has_value()) { + qWarning() << "Skipping room channel with unknown access level" << accessKey << "for id" + << channelId; + continue; + } + ServerInfo_RoomChannel channel; + channel.set_id(channelId.toStdString()); + channel.set_display_name(settingsCache->value("displayname").toString().toStdString()); + channel.set_access_level(accessLevel.value()); + if (settingsCache->contains("writeaccess")) { + const QString writeKey = settingsCache->value("writeaccess").toString(); + if (!writeKey.trimmed().isEmpty()) { + auto writeLevel = roomChannelAccessFromKey(writeKey); + if (!writeLevel.has_value()) { + qWarning() << "Skipping room channel with unknown write access level" << writeKey + << "for id" << channelId; + continue; + } + channel.set_write_access_level(writeLevel.value()); + } + } + channel.set_banner_message(settingsCache->value("banner").toString().toStdString()); + channels.append(channel); + } + settingsCache->endArray(); Server_Room *newRoom = new Server_Room( i, settingsCache->value("chathistorysize").toInt(), settingsCache->value("name").toString(), settingsCache->value("description").toString(), settingsCache->value("permissionlevel").toString().toLower(), settingsCache->value("privilegelevel").toString().toLower(), settingsCache->value("autojoin").toBool(), - settingsCache->value("joinmessage").toString(), gameTypes, this); + settingsCache->value("joinmessage").toString(), gameTypes, this, channels); addRoom(newRoom); } @@ -432,15 +591,16 @@ bool Servatrice::initServer() auto *thread = new QThread; thread->setObjectName("isl_" + QString::number(prop.id)); - connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + connect(thread, &QThread::finished, thread, &QObject::deleteLater); IslInterface *interface = new IslInterface(prop.id, prop.hostname, prop.address.toString(), prop.controlPort, prop.cert, cert, key, this); interface->moveToThread(thread); - connect(interface, SIGNAL(destroyed()), thread, SLOT(quit())); + connect(interface, &QObject::destroyed, thread, &QThread::quit); thread->start(); - QMetaObject::invokeMethod(interface, "initClient", Qt::BlockingQueuedConnection); + QMetaObject::invokeMethod( + interface, [interface] { interface->initClient(); }, Qt::BlockingQueuedConnection); } qDebug() << "Starting ISL server on port" << getISLNetworkPort(); @@ -448,33 +608,37 @@ bool Servatrice::initServer() if (islServer->listen(QHostAddress::Any, static_cast(getISLNetworkPort()))) { qDebug() << "ISL server listening."; } else { - throw QString("islServer->listen()"); + throw islServer->errorString(); } } } catch (QString &error) { - qDebug() << "ERROR --" << error; + qCCritical(ServatriceLog) << "Failed to start the ISL server:" << error; return false; } pingClock = new QTimer(this); - connect(pingClock, SIGNAL(timeout()), this, SIGNAL(pingClockTimeout())); + connect(pingClock, &QTimer::timeout, this, &Servatrice::pingClockTimeout); pingClock->start(getClientKeepAlive() * 1000); statusUpdateClock = new QTimer(this); - connect(statusUpdateClock, SIGNAL(timeout()), this, SLOT(statusUpdate())); + connect(statusUpdateClock, &QTimer::timeout, this, &Servatrice::statusUpdate); if (getServerStatusUpdateTime() != 0) { qDebug() << "Starting status update clock, interval" << getServerStatusUpdateTime() << "ms"; statusUpdateClock->start(getServerStatusUpdateTime()); } deckShareCleanupClock = new QTimer(this); - connect(deckShareCleanupClock, SIGNAL(timeout()), this, SLOT(cleanupExpiredDeckShares())); + connect(deckShareCleanupClock, &QTimer::timeout, this, &Servatrice::cleanupExpiredDeckShares); const int deckShareCleanupInterval = getDeckShareCleanupInterval(); if (deckShareCleanupInterval > 0) { qDebug() << "Starting deck share cleanup clock, interval" << deckShareCleanupInterval << "ms"; deckShareCleanupClock->start(deckShareCleanupInterval); } + legacyBackupSweepClock = new QTimer(this); + connect(legacyBackupSweepClock, &QTimer::timeout, this, &Servatrice::sweepLegacyBackups); + legacyBackupSweepClock->start(kLegacyBackupSweepIntervalMs); + // SOCKET SERVER if (getNumberOfTCPPools() > 0) { gameServer = @@ -485,7 +649,7 @@ bool Servatrice::initServer() if (gameServer->listen(tcpHost, static_cast(getServerTCPPort()))) { qDebug() << "Server listening."; } else { - qDebug() << "gameServer->listen(): Error:" << gameServer->errorString(); + qCCritical(ServatriceLog) << "Failed to listen for TCP clients:" << gameServer->errorString(); return false; } } @@ -501,7 +665,8 @@ bool Servatrice::initServer() if (websocketGameServer->listen(webSocketHost, static_cast(getServerWebSocketPort()))) { qDebug() << "Websocket server listening."; } else { - qDebug() << "websocketGameServer->listen(): Error:" << websocketGameServer->errorString(); + qCCritical(ServatriceLog) << "Failed to listen for websocket clients:" + << websocketGameServer->errorString(); return false; } } @@ -864,6 +1029,14 @@ SessionEvent *Servatrice::getLoginSessionEvent() const return nullptr; } +void Servatrice::sweepLegacyBackups() +{ + if (!servatriceDatabaseInterface->checkSql()) { + return; + } + servatriceDatabaseInterface->purgeExpiredLegacyBackups(); +} + void Servatrice::scheduleShutdown(const QString &reason, int minutes) { shutdownStateMutex.lock(); @@ -872,7 +1045,7 @@ void Servatrice::scheduleShutdown(const QString &reason, int minutes) nextShutdownMessageMinutes = shutdownMinutes; if (minutes > 0) { shutdownTimer = new QTimer; - connect(shutdownTimer, SIGNAL(timeout()), this, SLOT(shutdownTimeout())); + connect(shutdownTimer, &QTimer::timeout, this, &Servatrice::shutdownTimeout); shutdownTimer->start(60000); } shutdownStateMutex.unlock(); @@ -935,25 +1108,19 @@ void Servatrice::addIslInterface(int _serverId, IslInterface *interface) { // Only call with islLock locked for writing islInterfaces.insert(_serverId, interface); - connect(interface, SIGNAL(externalUserJoined(ServerInfo_User)), this, SLOT(externalUserJoined(ServerInfo_User))); - connect(interface, SIGNAL(externalUserLeft(QString)), this, SLOT(externalUserLeft(QString))); - connect(interface, SIGNAL(externalRoomUserJoined(int, ServerInfo_User)), this, - SLOT(externalRoomUserJoined(int, ServerInfo_User))); - connect(interface, SIGNAL(externalRoomUserLeft(int, QString)), this, SLOT(externalRoomUserLeft(int, QString))); - connect(interface, SIGNAL(externalRoomSay(int, QString, QString)), this, - SLOT(externalRoomSay(int, QString, QString))); - connect(interface, SIGNAL(externalRoomRemoveMessages(int, QString, int)), this, - SLOT(externalRoomRemoveMessages(int, QString, int))); - connect(interface, SIGNAL(externalRoomGameListChanged(int, ServerInfo_Game)), this, - SLOT(externalRoomGameListChanged(int, ServerInfo_Game))); - connect(interface, SIGNAL(joinGameCommandReceived(Command_JoinGame, int, int, int, qint64)), this, - SLOT(externalJoinGameCommandReceived(Command_JoinGame, int, int, int, qint64))); - connect(interface, SIGNAL(gameCommandContainerReceived(CommandContainer, int, int, qint64)), this, - SLOT(externalGameCommandContainerReceived(CommandContainer, int, int, qint64))); - connect(interface, SIGNAL(responseReceived(Response, qint64)), this, - SLOT(externalResponseReceived(Response, qint64))); - connect(interface, SIGNAL(gameEventContainerReceived(GameEventContainer, qint64)), this, - SLOT(externalGameEventContainerReceived(GameEventContainer, qint64))); + connect(interface, &IslInterface::externalUserJoined, this, &Servatrice::externalUserJoined); + connect(interface, &IslInterface::externalUserLeft, this, &Servatrice::externalUserLeft); + connect(interface, &IslInterface::externalRoomUserJoined, this, &Servatrice::externalRoomUserJoined); + connect(interface, &IslInterface::externalRoomUserLeft, this, &Servatrice::externalRoomUserLeft); + connect(interface, &IslInterface::externalRoomSay, this, &Servatrice::externalRoomSay); + connect(interface, &IslInterface::externalRoomRemoveMessages, this, &Servatrice::externalRoomRemoveMessages); + connect(interface, &IslInterface::externalRoomGameListChanged, this, &Servatrice::externalRoomGameListChanged); + connect(interface, &IslInterface::joinGameCommandReceived, this, &Servatrice::externalJoinGameCommandReceived); + connect(interface, &IslInterface::gameCommandContainerReceived, this, + &Servatrice::externalGameCommandContainerReceived); + connect(interface, &IslInterface::responseReceived, this, &Servatrice::externalResponseReceived); + connect(interface, &IslInterface::gameEventContainerReceived, this, + &Servatrice::externalGameEventContainerReceived); } void Servatrice::removeIslInterface(int _serverId) @@ -1056,6 +1223,42 @@ QString Servatrice::getRequiredFeatures() const return settingsCache->value("server/requiredfeatures", "").toString(); } +Servatrice::AuthenticationStrictness Servatrice::getAuthenticationStrictness() const +{ + const QString strictness = settingsCache->value("security/authentication_strictness", "mixed").toString(); + if (strictness == "strict") { + return AuthenticationStrict; + } + if (strictness == "legacy") { + return AuthenticationLegacy; + } + return AuthenticationMixed; +} + +QByteArray Servatrice::getLegacyBackupKey() const +{ + const QString keyHex = settingsCache->value("security/legacy_backup_key").toString().trimmed(); + if (keyHex.isEmpty()) { + return {}; + } + static const QRegularExpression validHexKey(QStringLiteral("^[0-9a-fA-F]{64}$")); + if (!validHexKey.match(keyHex).hasMatch()) { + qDebug() << "security/legacy_backup_key is not 64 hex characters;" << "legacy password backups are disabled"; + return {}; + } + return QByteArray::fromHex(keyHex.toUtf8()); +} + +int Servatrice::getLegacyBackupTtlDays() const +{ + bool ok = false; + const int days = settingsCache->value("security/legacy_backup_ttl_days", 180).toInt(&ok); + if (!ok || days < 1 || days > 3650) { + return 180; + } + return days; +} + QString Servatrice::getDBTypeString() const { if (QProcessEnvironment::systemEnvironment().contains("DATABASE_URL")) { @@ -1133,6 +1336,11 @@ int Servatrice::getMaxUsersPerAddress() const return settingsCache->value("security/max_users_per_address", 4).toInt(); } +QString Servatrice::getTrustedSources() const +{ + return settingsCache->value("security/trusted_sources", "127.0.0.1,::1").toString(); +} + int Servatrice::getMessageCountingInterval() const { return settingsCache->value("security/message_counting_interval", 10).toInt(); @@ -1346,9 +1554,11 @@ bool Servatrice::isRateLimitExempt(const QString &ipAddress) const { // Mirrors the max_users_per_address bypass in initSession: trusted sources // (e.g. 127.0.0.1 for Webatrice behind local nginx, where every websocket - // client shares one address) are not subject to per-address limits. - const QString trustedSources = settingsCache->value("security/trusted_sources", "127.0.0.1,::1").toString(); - return trustedSources.contains(ipAddress, Qt::CaseInsensitive); + // client shares one address) are not subject to per-address limits. Loopback + // is always exempt, so a same-host proxy whose forwarded address could not be + // resolved cannot lock every user out together when trusted_sources is + // customized. + return QHostAddress(ipAddress).isLoopback() || isTrustedSource(getTrustedSources(), ipAddress); } void Servatrice::clearFailedLogins(const QString &ipAddress, const QString &userName) diff --git a/servatrice/src/servatrice.h b/servatrice/src/servatrice.h index d5414f810..3dba23234 100644 --- a/servatrice/src/servatrice.h +++ b/servatrice/src/servatrice.h @@ -155,8 +155,15 @@ public: AuthenticationSql, AuthenticationPassword }; + enum AuthenticationStrictness + { + AuthenticationLegacy, + AuthenticationMixed, + AuthenticationStrict + }; private slots: void statusUpdate(); + void sweepLegacyBackups(); void shutdownTimeout(); void cleanupExpiredDeckShares(); @@ -173,6 +180,7 @@ private: DatabaseType databaseType; QTimer *pingClock, *statusUpdateClock; QTimer *deckShareCleanupClock; + QTimer *legacyBackupSweepClock; Servatrice_GameServer *gameServer; Servatrice_WebsocketGameServer *websocketGameServer; Servatrice_IslServer *islServer; @@ -251,6 +259,14 @@ public: { return serverRequiredFeatureList; } + bool requiresChallengeResponseAuth() const override + { + return getAuthenticationStrictness() == AuthenticationStrict; + } + /** @brief Key sealing legacy password backups, or empty when they are disabled. */ + QByteArray getLegacyBackupKey() const; + /** @brief Days a legacy password backup may go unused before it is purged. */ + int getLegacyBackupTtlDays() const; QString getServerName() const; QString getLoginMessage() const override { @@ -272,6 +288,7 @@ public: { return authenticationMethod; } + AuthenticationStrictness getAuthenticationStrictness() const; bool permitUnregisteredUsers() const override { return authenticationMethod != AuthenticationNone; @@ -304,6 +321,8 @@ public: int getDeckShareMaxDecksPerShare() const; int getDeckShareMaxSharesPerDay() const; int getMaxUsersPerAddress() const; + /// Raw security/trusted_sources list; match addresses against it with isTrustedSource(). + QString getTrustedSources() const; int getMessageCountingInterval() const override; int getMaxMessageCountPerInterval() const override; int getMaxMessageSizePerInterval() const override; diff --git a/servatrice/src/servatrice_database_interface.cpp b/servatrice/src/servatrice_database_interface.cpp index ac3a19c8e..a4f184e38 100644 --- a/servatrice/src/servatrice_database_interface.cpp +++ b/servatrice/src/servatrice_database_interface.cpp @@ -33,10 +33,12 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include @@ -44,6 +46,17 @@ inline Q_LOGGING_CATEGORY(DatabaseInterfaceLog, "database_interface"); +// Oldest password_legacy_backup_used_at that is still inside the TTL. The value is +// compared against (and stored as) a UTC "yyyy-MM-dd HH:mm:ss" string, which sorts +// lexicographically, so no timezone-aware parsing is needed on either side. +static QString legacyBackupExpiryCutoff(int ttlDays) +{ + return QDateTime::currentDateTimeUtc().addDays(-ttlDays).toString(QStringLiteral("yyyy-MM-dd HH:mm:ss")); +} + +// Width of the password_legacy_backup column; a sealed backup must fit inside it. +static constexpr int MAX_LEGACY_BACKUP_LENGTH = 255; + Servatrice_DatabaseInterface::Servatrice_DatabaseInterface(int _instanceId, Servatrice *_server) : instanceId(_instanceId), sqlDatabase(QSqlDatabase()), server(_server) { @@ -429,13 +442,108 @@ AuthenticationResult Servatrice_DatabaseInterface::checkUserPassword(Server_Prot qCWarning(DatabaseInterfaceLog) << "Login denied: user not active"; return UserIsInactive; } + + // Fail closed on an absent stored credential: an empty key would + // otherwise authenticate anyone who can compute HMAC("", nonce). + if (correctPasswordSha512.isEmpty()) { + qCWarning(DatabaseInterfaceLog) << "Login denied: empty stored credential"; + return NotLoggedIn; + } + + if (password.startsWith("$challenge$")) { + // Challenge-response login: verify HMAC(stored_key, nonce) without + // ever transmitting the stored credential or password hash. + const QStringList parts = password.split("$"); + if (parts.size() != 4) { + return NotLoggedIn; + } + const QByteArray nonce = QByteArray::fromBase64(parts.at(2).toUtf8()); + const QByteArray response = QByteArray::fromBase64(parts.at(3).toUtf8()); + if (nonce.isEmpty() || response.isEmpty() || !handler->isAuthNonceValid(nonce, user)) { + return NotLoggedIn; + } + + QByteArray key; + if (PasswordHasher::isLegacyFormat(correctPasswordSha512)) { + key = correctPasswordSha512.toUtf8(); + } else { + // Design note: the stored scrypt verifier IS the challenge-response key, so a + // database dump yields credentials that can answer a login challenge directly. + // We deliberately accept this trade: it removes plaintext passwords from the + // client config and from the wire, but does not protect against DB compromise. + // A password-equivalent proof scheme (e.g. SRP-6a/OPAQUE) would be the proper + // escalation and is out of scope here. + const PasswordVerifier verifier = PasswordHasher::parsePasswordVerifier(correctPasswordSha512); + if (!verifier.isValid) { + return NotLoggedIn; + } + key = verifier.verifier; + } + + const QByteArray expected = PasswordHasher::computeResponse(key, nonce); + handler->clearAuthNonce(); + if (PasswordHasher::constantTimeEquals(expected, response)) { + if (forceChange) { + qCDebug(DatabaseInterfaceLog) << "Login accepted but password change required"; + return PasswordChangeRequired; + } + qCDebug(DatabaseInterfaceLog) << "Login accepted: challenge-response password right"; + return PasswordRight; + } + qCDebug(DatabaseInterfaceLog) << "Login denied: challenge-response password wrong"; + return NotLoggedIn; + } + + // A migrated (scrypt) row distinguishes how the credential arrived: + // a plaintext password (passwordNeedsHash) is verified directly against + // the scrypt verifier, so email changes (cmdAccountEdit) and old clients + // that still send `password` keep working without a legacy backup key. + // A pre-hashed legacy value is accepted against the encrypted legacy + // backup while that backup is fresh. Without one there is no downgrade + // path. The backup window is refreshed only when the backup is what + // actually matched, so a plaintext proof never keeps the + // fast-crackable hash alive. + if (!PasswordHasher::isLegacyFormat(correctPasswordSha512)) { + if (passwordNeedsHash) { + // Cost is bounded by costParamsAreSane inside parsePasswordVerifier. + if (PasswordHasher::verifyPassword(password, correctPasswordSha512)) { + if (forceChange) { + qCDebug(DatabaseInterfaceLog) << "Login accepted but password change required"; + return PasswordChangeRequired; + } + qCDebug(DatabaseInterfaceLog) << "Login accepted: password right"; + return PasswordRight; + } + qCDebug(DatabaseInterfaceLog) << "Login denied: password wrong"; + return NotLoggedIn; + } + const QString backupCredential = getUsableLegacyBackup(user); + if (backupCredential.isEmpty()) { + qCDebug(DatabaseInterfaceLog) + << "Login denied: migrated account without a usable legacy backup"; + return NotLoggedIn; + } + if (PasswordHasher::constantTimeEquals(backupCredential.toUtf8(), password.toUtf8())) { + touchLegacyBackup(user); + if (forceChange) { + qCDebug(DatabaseInterfaceLog) << "Login accepted but password change required"; + return PasswordChangeRequired; + } + qCDebug(DatabaseInterfaceLog) << "Login accepted: password right"; + return PasswordRight; + } + qCDebug(DatabaseInterfaceLog) << "Login denied: password wrong"; + return NotLoggedIn; + } + + QString storedCredential = correctPasswordSha512; QString hashedPassword; if (passwordNeedsHash) { - hashedPassword = PasswordHasher::computeHash(password, correctPasswordSha512.left(16)); + hashedPassword = PasswordHasher::computeHash(password, storedCredential.left(16)); } else { hashedPassword = password; } - if (correctPasswordSha512 == hashedPassword) { + if (PasswordHasher::constantTimeEquals(storedCredential.toUtf8(), hashedPassword.toUtf8())) { if (forceChange) { qCDebug(DatabaseInterfaceLog) << "Login accepted but password change required"; return PasswordChangeRequired; @@ -631,6 +739,194 @@ QString Servatrice_DatabaseInterface::getUserSalt(const QString &user) return {}; } +QString Servatrice_DatabaseInterface::getUserPasswordData(const QString &user) +{ + if (server->getAuthenticationMethod() != Servatrice::AuthenticationSql) { + return {}; + } + + checkSql(); + + QSqlQuery *query = prepareQuery("SELECT password_sha512 FROM {prefix}_users WHERE name = :name"); + query->bindValue(":name", user); + if (!execSqlQuery(query)) { + return {}; + } + + if (!query->next()) { + return {}; + } + + return query->value(0).toString(); +} + +QString Servatrice_DatabaseInterface::sealLegacyBackup(const QString &storedName, const QString &credential) +{ + const QByteArray key = server->getLegacyBackupKey(); + if (key.isEmpty()) { + return {}; + } + const QByteArray aad = storedName.simplified().toUtf8(); + const QString sealed = CryptoUtil::encryptSecret(credential.toUtf8(), key, aad); + if (sealed.isEmpty() || sealed.length() > MAX_LEGACY_BACKUP_LENGTH) { + return {}; + } + return sealed; +} + +bool Servatrice_DatabaseInterface::submitPasswordVerifier(const QString &user, const QString &passwordVerifier) +{ + if (server->getAuthenticationMethod() != Servatrice::AuthenticationSql) { + return false; + } + + checkSql(); + + // Read the credential that is about to be replaced: while it is still in the + // legacy format it becomes the encrypted downgrade backup, sealed before the + // row is overwritten with the scrypt verifier. + QSqlQuery *currentQuery = prepareQuery("SELECT name, password_sha512 FROM {prefix}_users WHERE name = :name"); + currentQuery->bindValue(":name", user); + if (!execSqlQuery(currentQuery) || !currentQuery->next()) { + return false; + } + // Use the stored row name (not the caller-supplied one) as the AAD: name is + // utf8mb4_unicode_ci, so `alice` logs in as `Alice` and the SELECT still finds + // the row while a client-typed name would fail the GCM tag check on open. + const QString storedName = currentQuery->value(0).toString(); + const QString currentCredential = currentQuery->value(1).toString(); + + QString sealedBackup; + QDateTime backupUsedAt; + if (PasswordHasher::isLegacyFormat(currentCredential) && !server->getLegacyBackupKey().isEmpty()) { + sealedBackup = sealLegacyBackup(storedName, currentCredential); + if (sealedBackup.isEmpty()) { + qCWarning(DatabaseInterfaceLog) + << "Failed to seal legacy backup for user" << user << "- account migrates without a downgrade path"; + } else { + backupUsedAt = QDateTime::currentDateTimeUtc(); + } + } + + // Only migrate accounts that still use the credential this backup was taken from; + // the guard makes a re-migration (or a raced second submission) a no-op. + QSqlQuery *query = prepareQuery("update {prefix}_users set password_sha512 = :verifier, " + "password_legacy_backup = :backup, password_legacy_backup_used_at = :used_at " + "where name = :user and password_sha512 = :expected and password_sha512 not like " + "'$%'"); + query->bindValue(":verifier", passwordVerifier); + query->bindValue(":backup", sealedBackup); + query->bindValue(":used_at", backupUsedAt.isValid() + ? QVariant(backupUsedAt.toString(QStringLiteral("yyyy-MM-dd HH:mm:ss"))) + : QVariant()); + query->bindValue(":user", user); + query->bindValue(":expected", currentCredential); + if (!execSqlQuery(query)) { + qCWarning(DatabaseInterfaceLog) << "Failed to submit password verifier for user" << user << query->lastError(); + return false; + } + // The guard makes a re-migration a no-op; only report success when a row was actually updated. + return query->numRowsAffected() > 0; +} + +QString Servatrice_DatabaseInterface::getUsableLegacyBackup(const QString &user) +{ + if (server->getAuthenticationMethod() != Servatrice::AuthenticationSql) { + return {}; + } + + // Without a configured key the backup cannot be sealed or opened; do not touch + // (and never purge on this path) so a temporarily missing key cannot destroy it. + const QByteArray key = server->getLegacyBackupKey(); + if (key.isEmpty()) { + return {}; + } + + checkSql(); + + QSqlQuery *query = prepareQuery( + "SELECT name, password_legacy_backup, password_legacy_backup_used_at FROM {prefix}_users WHERE name = :name"); + query->bindValue(":name", user); + if (!execSqlQuery(query) || !query->next()) { + return {}; + } + + // Open with the stored row name as the AAD: sealing used the canonical DB name + // while the caller passes the client-typed name, and the two differ whenever + // the login case does not match the stored case (utf8mb4_unicode_ci). + const QString storedName = query->value(0).toString(); + const QString sealed = query->value(1).toString(); + const QString usedAt = query->value(2).toString(); + if (sealed.isEmpty()) { + return {}; + } + + // Age out: a backup unused for the whole TTL is purged on sight, so a database + // dump taken after the window closes never carries a crackable legacy hash. + if (usedAt.isEmpty() || usedAt < legacyBackupExpiryCutoff(server->getLegacyBackupTtlDays())) { + qCDebug(DatabaseInterfaceLog) << "Purging expired legacy backup for user" << user; + clearLegacyBackup(user); + return {}; + } + + const QByteArray legacyCredential = CryptoUtil::decryptSecret(sealed, key, storedName.simplified().toUtf8()); + if (legacyCredential.isEmpty()) { + qCWarning(DatabaseInterfaceLog) << "Legacy backup for user" << user << "failed to decrypt" + << "- check security/legacy_backup_key"; + return {}; + } + return QString::fromUtf8(legacyCredential); +} + +void Servatrice_DatabaseInterface::touchLegacyBackup(const QString &user) +{ + if (!checkSql()) { + return; + } + + QSqlQuery *query = prepareQuery("UPDATE {prefix}_users SET password_legacy_backup_used_at = UTC_TIMESTAMP() " + "WHERE name = :name AND password_legacy_backup <> ''"); + query->bindValue(":name", user); + execSqlQuery(query); +} + +void Servatrice_DatabaseInterface::clearLegacyBackup(const QString &user) +{ + if (!checkSql()) { + return; + } + + QSqlQuery *query = prepareQuery("UPDATE {prefix}_users SET password_legacy_backup = '', " + "password_legacy_backup_used_at = NULL WHERE name = :name"); + query->bindValue(":name", user); + execSqlQuery(query); +} + +void Servatrice_DatabaseInterface::purgeExpiredLegacyBackups() +{ + if (server->getAuthenticationMethod() != Servatrice::AuthenticationSql) { + return; + } + + if (!checkSql()) { + return; + } + + QSqlQuery *query = prepareQuery("UPDATE {prefix}_users SET password_legacy_backup = '', " + "password_legacy_backup_used_at = NULL " + "WHERE password_legacy_backup <> '' AND " + "(password_legacy_backup_used_at IS NULL OR password_legacy_backup_used_at < " + ":cutoff)"); + query->bindValue(":cutoff", legacyBackupExpiryCutoff(server->getLegacyBackupTtlDays())); + if (!execSqlQuery(query)) { + qCWarning(DatabaseInterfaceLog) << "Legacy backup sweep failed:" << query->lastError(); + return; + } + if (query->numRowsAffected() > 0) { + qCDebug(DatabaseInterfaceLog) << "Purged" << query->numRowsAffected() << "expired legacy password backups"; + } +} + int Servatrice_DatabaseInterface::getUserIdInDB(const QString &name) { if (server->getAuthenticationMethod() == Servatrice::AuthenticationSql) { @@ -1286,7 +1582,8 @@ void Servatrice_DatabaseInterface::logMessage(const int senderId, const QString &logMessage, LogMessage_TargetType targetType, const int targetId, - const QString &targetName) + const QString &targetName, + const QString &channel) { QString targetTypeString; switch (targetType) { @@ -1296,6 +1593,12 @@ void Servatrice_DatabaseInterface::logMessage(const int senderId, } targetTypeString = "room"; break; + case MessageTargetRoomChannel: + if (!settingsCache->value("logging/log_user_msg_room", 0).toBool()) { + return; + } + targetTypeString = "room_channel"; + break; case MessageTargetGame: if (!settingsCache->value("logging/log_user_msg_game", 0).toBool()) { return; @@ -1319,8 +1622,9 @@ void Servatrice_DatabaseInterface::logMessage(const int senderId, } QSqlQuery *query = prepareQuery("insert into {prefix}_log (log_time, sender_id, sender_name, sender_ip, " - "log_message, target_type, target_id, target_name) values (now(), :sender_id, " - ":sender_name, :sender_ip, :log_message, :target_type, :target_id, :target_name)"); + "log_message, target_type, target_id, target_name, channel) values (now(), " + ":sender_id, :sender_name, :sender_ip, :log_message, :target_type, :target_id, " + ":target_name, :channel)"); query->bindValue(":sender_id", senderId < 1 ? QVariant() : senderId); query->bindValue(":sender_name", senderName); query->bindValue(":sender_ip", senderIp); @@ -1328,6 +1632,7 @@ void Servatrice_DatabaseInterface::logMessage(const int senderId, query->bindValue(":target_type", targetTypeString); query->bindValue(":target_id", (targetType == MessageTargetChat && targetId < 1) ? QVariant() : targetId); query->bindValue(":target_name", targetName); + query->bindValue(":channel", channel.isEmpty() ? QVariant() : channel); execSqlQuery(query); } @@ -1340,7 +1645,10 @@ bool Servatrice_DatabaseInterface::changeUserPassword(const QString &user, passwordSha512 = PasswordHasher::computeHash(password, PasswordHasher::generateRandomSalt()); } + // The backup authenticates the credential it was taken from; a new credential + // invalidates it (the old password is dead, and the new one has no legacy form). QSqlQuery *passwordQuery = prepareQuery("update {prefix}_users set password_sha512=:password, " + "password_legacy_backup='', password_legacy_backup_used_at=NULL, " "passwordLastChangedDate = NOW() where name = :name"); passwordQuery->bindValue(":password", passwordSha512); passwordQuery->bindValue(":name", user); @@ -1350,6 +1658,54 @@ bool Servatrice_DatabaseInterface::changeUserPassword(const QString &user, return passwordQuery->numRowsAffected() > 0; } +bool Servatrice_DatabaseInterface::changeUserPasswordWithLegacyBackup(const QString &user, + const QString &passwordVerifier, + const QString &legacyCredential) +{ + if (server->getAuthenticationMethod() != Servatrice::AuthenticationSql) { + return false; + } + + if (!checkSql()) { + return false; + } + + // Seal against the stored row name, not the caller-supplied one: the name column is + // case-insensitive, so a differently-cased name would fail the tag check on open. + QSqlQuery *nameQuery = prepareQuery("SELECT name FROM {prefix}_users WHERE name = :name"); + nameQuery->bindValue(":name", user); + if (!execSqlQuery(nameQuery) || !nameQuery->next()) { + return false; + } + const QString storedName = nameQuery->value(0).toString(); + + const QString sealedBackup = sealLegacyBackup(storedName, legacyCredential); + const QDateTime backupUsedAt = sealedBackup.isEmpty() ? QDateTime() : QDateTime::currentDateTimeUtc(); + if (sealedBackup.isEmpty()) { + // Without a backup a pre-challenge client has nothing to authenticate against: the + // row holds a verifier and the legacy hash it would send cannot match it. Say so, + // because the symptom - the temporary password appearing not to work - shows up on + // a different machine from the one that reset it. + qCWarning(DatabaseInterfaceLog) << "Stored a verifier without a legacy backup for user" << user + << "- pre-challenge clients cannot use this password" + << "- check security/legacy_backup_key"; + } + + QSqlQuery *query = prepareQuery("update {prefix}_users set password_sha512=:password, " + "password_legacy_backup=:backup, password_legacy_backup_used_at=:used_at, " + "passwordLastChangedDate = NOW() where name = :name"); + query->bindValue(":password", passwordVerifier); + query->bindValue(":backup", sealedBackup); + query->bindValue(":used_at", backupUsedAt.isValid() + ? QVariant(backupUsedAt.toString(QStringLiteral("yyyy-MM-dd HH:mm:ss"))) + : QVariant()); + query->bindValue(":name", storedName); + if (!execSqlQuery(query)) { + return false; + } + return query->numRowsAffected() > 0; +} + void Servatrice_DatabaseInterface::setForcePasswordChange(const QString &user, bool force) { if (!checkSql()) { @@ -1393,13 +1749,13 @@ bool Servatrice_DatabaseInterface::changeUserPassword(const QString &user, return false; } - const QString correctPasswordSha512 = passwordQuery->value(0).toString(); - QString oldPasswordSha512 = oldPassword; - if (oldPasswordNeedsHash) { - QString salt = correctPasswordSha512.left(16); - oldPasswordSha512 = PasswordHasher::computeHash(oldPassword, salt); - } - if (correctPasswordSha512 != oldPasswordSha512) { + const QString storedPassword = passwordQuery->value(0).toString(); + // oldPasswordNeedsHash means the client sent the old password in plaintext. Verify it + // against whatever is stored: legacy salt+hash rows or already-migrated scrypt rows + // (which must NOT be re-hashed with a salt torn out of the "$scrypt$..." string). + const bool oldPasswordMatches = oldPasswordNeedsHash ? PasswordHasher::verifyPassword(oldPassword, storedPassword) + : (oldPassword == storedPassword); + if (!oldPasswordMatches) { return false; } @@ -1783,6 +2139,108 @@ bool Servatrice_DatabaseInterface::removeUserAvatar(const QString &userName) return query->numRowsAffected() > 0; } +Servatrice_DatabaseInterface::RemoveUserBannerResult +Servatrice_DatabaseInterface::removeUserBanner(const QString &userName) +{ + if (!checkSql()) { + return RemoveUserBannerResult::DatabaseError; + } + + // The UPDATE alone cannot tell a missing user from a user without a banner, + // so read the row first and let the moderator know which case they hit. + QSqlQuery *lookupQuery = prepareQuery("SELECT card_art_params FROM {prefix}_users WHERE name = :user_name"); + lookupQuery->bindValue(":user_name", userName); + if (!execSqlQuery(lookupQuery)) { + return RemoveUserBannerResult::DatabaseError; + } + if (!lookupQuery->next()) { + return RemoveUserBannerResult::NoSuchUser; + } + if (lookupQuery->value(0).toString().isEmpty()) { + return RemoveUserBannerResult::NothingToRemove; + } + + QSqlQuery *query = prepareQuery("UPDATE {prefix}_users SET card_art_params = NULL WHERE name = :user_name"); + query->bindValue(":user_name", userName); + if (!execSqlQuery(query)) { + return RemoveUserBannerResult::DatabaseError; + } + return RemoveUserBannerResult::Removed; +} + +// isCardArtAllowed() compares against the rule table with the database's +// case-insensitive collation, so the sweep has to fold case the same way or it +// leaves behind banners the server already treats as blacklisted. +static QPair bannerKey(const QString &cardName, const QString &cardProviderId) +{ + return {cardName.toCaseFolded(), cardProviderId.toCaseFolded()}; +} + +int Servatrice_DatabaseInterface::clearBlacklistedBanners() +{ + if (!checkSql()) { + return -1; + } + + QSqlQuery *ruleQuery = prepareQuery("SELECT card_name, card_provider_id FROM {prefix}_card_art_name_rules " + "WHERE mode = 'DENY'"); + if (!execSqlQuery(ruleQuery)) { + return -1; + } + + QSet> denied; + while (ruleQuery->next()) { + denied.insert(bannerKey(ruleQuery->value(0).toString(), ruleQuery->value(1).toString())); + } + if (denied.isEmpty()) { + return 0; + } + + QSqlQuery *userQuery = + prepareQuery("SELECT id, card_art_params FROM {prefix}_users WHERE card_art_params IS NOT NULL"); + if (!execSqlQuery(userQuery)) { + return -1; + } + + QList matchingIds; + while (userQuery->next()) { + const QJsonDocument doc = QJsonDocument::fromJson(userQuery->value(1).toString().toUtf8()); + if (!doc.isObject()) { + continue; + } + const QJsonObject obj = doc.object(); + if (denied.contains(bannerKey(obj.value("card_name").toString(), obj.value("card_provider_id").toString()))) { + matchingIds.append(userQuery->value(0)); + } + } + + if (matchingIds.isEmpty()) { + return 0; + } + + // Chunk the UPDATE so very large user tables do not exceed driver placeholder limits. + int cleared = 0; + constexpr int BATCH_SIZE = 500; + for (int i = 0; i < matchingIds.size(); i += BATCH_SIZE) { + const QList batch = matchingIds.mid(i, BATCH_SIZE); + QStringList placeholders; + placeholders.reserve(batch.size()); + for (int j = 0; j < batch.size(); ++j) { + placeholders.append(QString(":id%1").arg(j)); + } + QSqlQuery *clearQuery = prepareQuery( + QString("UPDATE {prefix}_users SET card_art_params = NULL WHERE id IN (%1)").arg(placeholders.join(","))); + for (int j = 0; j < batch.size(); ++j) { + clearQuery->bindValue(placeholders.at(j), batch.at(j)); + } + if (!execSqlQuery(clearQuery)) { + return -1; + } + cleared += clearQuery->numRowsAffected(); + } + return cleared; +} + QList Servatrice_DatabaseInterface::getMessageLogHistory(const QString &user, const QString &ipaddress, const QString &gamename, @@ -1792,8 +2250,14 @@ QList Servatrice_DatabaseInterface::getMessageLogHistory bool &game, bool &room, int &range, - int &maxresults) + int &maxresults, + int offset, + const QString &beforeTime, + bool *hasMore) { + if (hasMore != nullptr) { + *hasMore = false; + } QList results; ServerInfo_ChatMessage chatMessage; @@ -1802,8 +2266,31 @@ QList Servatrice_DatabaseInterface::getMessageLogHistory return results; } - if (user.isEmpty() && ipaddress.isEmpty() && gameid.isEmpty() && gamename.isEmpty()) { - // To ensure quick results and minimal lag, require an indexed field + // Page size guard: always bound the scan so a filter-less browse of room + // history cannot become a 50M-row fetch. Zero means "client did not set a + // size", so fall back to one smooth-scroll page. + const int pageSize = maxresults <= 0 ? 200 : qBound(1, maxresults, 1000); + maxresults = pageSize; + const int safeOffset = qMax(0, offset); + + const bool hasIndexedFilter = !user.isEmpty() || !ipaddress.isEmpty() || !gameid.isEmpty() || !gamename.isEmpty(); + const bool hasLocationFilter = chat || game || room; + if (!hasLocationFilter) { + // No target_type restriction would mix private chat into a room/game + // browse, so refuse rather than leak. + return results; + } + // A browse narrowed only by a message substring has no index to serve it, and `range` + // has no upper bound on the wire: the client offers 20 days, but a hand-built command + // can ask for any value and every LIKE candidate in it is a row the server examines. + // Clamping here keeps the guard independent of what the client happens to send. + constexpr int maxFilterlessRangeHours = 20 * 24; + if (!hasIndexedFilter && range > maxFilterlessRangeHours) { + range = maxFilterlessRangeHours; + } + if (!hasIndexedFilter && range <= 0) { + // Filter-less browse must stay time-bounded (idx_log_time) so the + // query stays an index range scan instead of a full table scan. return results; } @@ -1846,9 +2333,9 @@ QList Servatrice_DatabaseInterface::getMessageLogHistory if (room) { if (game || chat) { - queryString.append(" OR `target_type` = 'room'"); + queryString.append(" OR `target_type` = 'room' OR `target_type` = 'room_channel'"); } else { - queryString.append("`target_type` = 'room'"); + queryString.append("`target_type` = 'room' OR `target_type` = 'room_channel'"); } } queryString.append(")"); @@ -1858,10 +2345,23 @@ QList Servatrice_DatabaseInterface::getMessageLogHistory queryString.append(" AND log_time >= DATE_SUB(now(), INTERVAL :range_time HOUR)"); } - if (maxresults) { - queryString.append(" LIMIT :limit_size"); + if (!beforeTime.isEmpty()) { + // Pages after the first run against the snapshot fixed by the first one, so rows + // logged while the moderator scrolls can no longer push the OFFSET window down and + // repeat a row already on screen. + queryString.append(" AND log_time <= :before_time"); } + // Newest-first keeps "going back from today" stable across pages and lets + // the client append older rows at the bottom for smooth infinite scroll. + queryString.append(" ORDER BY log_time DESC"); + + // Fetch one extra row when the caller wants a has_more cursor so a full + // page does not need a second empty round-trip to prove exhaustion. + const bool detectMore = (hasMore != nullptr); + const int fetchSize = detectMore ? pageSize + 1 : pageSize; + queryString.append(" LIMIT :limit_size OFFSET :offset_size"); + QSqlQuery *query = prepareQuery(queryString); if (!user.isEmpty()) { query->bindValue(":user_name", user); @@ -1881,9 +2381,11 @@ QList Servatrice_DatabaseInterface::getMessageLogHistory if (range) { query->bindValue(":range_time", range); } - if (maxresults) { - query->bindValue(":limit_size", maxresults); + if (!beforeTime.isEmpty()) { + query->bindValue(":before_time", beforeTime); } + query->bindValue(":limit_size", fetchSize); + query->bindValue(":offset_size", safeOffset); if (!execSqlQuery(query)) { qCWarning(DatabaseInterfaceLog) << "Failed to collect log history information: SQL Error"; @@ -1899,9 +2401,15 @@ QList Servatrice_DatabaseInterface::getMessageLogHistory chatMessage.set_target_type(QString(query->value(5).toString()).toStdString()); chatMessage.set_target_id(QString(query->value(6).toString()).toStdString()); chatMessage.set_target_name(QString(query->value(7).toString()).toStdString()); + chatMessage.set_channel(QString(query->value(8).toString()).toStdString()); results << chatMessage; } + if (detectMore && results.size() > pageSize) { + results.removeLast(); + *hasMore = true; + } + return results; } @@ -2068,3 +2576,25 @@ void Servatrice_DatabaseInterface::addAuditRecord(const QString &user, query->bindValue(":details", details); execSqlQuery(query); } + +bool Servatrice_DatabaseInterface::isCardArtAllowed(const QString &cardName, const QString &cardProviderId) +{ + if (cardName.isEmpty() || !checkSql()) { + return true; + } + QSqlQuery *q = prepareQuery( + "SELECT mode FROM {prefix}_card_art_name_rules WHERE card_name = :name AND card_provider_id = :provider"); + q->bindValue(":name", cardName); + q->bindValue(":provider", cardProviderId); + + if (!execSqlQuery(q)) { + qWarning() << "Card art rule lookup failed; failing open for" << cardName; + return true; + } + + if (!q->next()) { + return true; // default allow + } + + return q->value(0).toString() != "DENY"; +} diff --git a/servatrice/src/servatrice_database_interface.h b/servatrice/src/servatrice_database_interface.h index 0509d1a03..eb74063ac 100644 --- a/servatrice/src/servatrice_database_interface.h +++ b/servatrice/src/servatrice_database_interface.h @@ -21,7 +21,7 @@ class QSqlQuery; -#define DATABASE_SCHEMA_VERSION 37 +#define DATABASE_SCHEMA_VERSION 40 class Servatrice; @@ -62,6 +62,9 @@ private: bool checkUserIsIpBanned(const QString &ipAddress, QString &banReason, int &banSecondsRemaining); /** Must be called after checkSql and server is known to be in auth mode. */ bool checkUserIsNameBanned(QString const &userName, QString &banReason, int &banSecondsRemaining); + /** Seal a legacy credential for the account's downgrade backup; empty when the + * backup key is unset or the sealed value does not fit the column. */ + QString sealLegacyBackup(const QString &storedName, const QString &credential); bool isStrictModeEnabled(bool &ok) const; protected: @@ -96,6 +99,18 @@ public: bool activeUserExists(const QString &user) override; bool userExists(const QString &user) override; QString getUserSalt(const QString &user) override; + QString getUserPasswordData(const QString &user) override; + bool submitPasswordVerifier(const QString &user, const QString &passwordVerifier) override; + /** @brief Decrypt the account's legacy password backup when it exists, is decryptable + * with the configured key and has not aged out; purges it when it has. Returns the + * legacy credential in the stored "salt + hash" format, or an empty string. */ + QString getUsableLegacyBackup(const QString &user); + /** @brief Refresh the age window of the account's legacy password backup. */ + void touchLegacyBackup(const QString &user); + /** @brief Drop the account's legacy password backup. */ + void clearLegacyBackup(const QString &user); + /** @brief Drop every legacy password backup that has not been used within the TTL. */ + void purgeExpiredLegacyBackups(); int getUserIdInDB(const QString &name); QMap getBuddyList(const QString &name) override; QMap getIgnoreList(const QString &name) override; @@ -177,8 +192,14 @@ public: const QString &logMessage, LogMessage_TargetType targetType, const int targetId, - const QString &targetName) override; + const QString &targetName, + const QString &channel = QString()) override; bool changeUserPassword(const QString &user, const QString &password, bool passwordNeedsHash) override; + /** Replace a credential with an already-derived verifier, keeping a sealed legacy hash of the + * same password as the downgrade backup for pre-challenge clients. */ + bool changeUserPasswordWithLegacyBackup(const QString &user, + const QString &passwordVerifier, + const QString &legacyCredential); bool changeUserPassword(const QString &user, const QString &oldPassword, bool oldPasswordNeedsHash, @@ -198,7 +219,10 @@ public: bool &game, bool &room, int &range, - int &maxresults); + int &maxresults, + int offset = 0, + const QString &beforeTime = QString(), + bool *hasMore = nullptr); QList getUserSessions(const QString &userName, int limit); QList getUserAlts(const QString &userName); QList getModeratorLastLogins(); @@ -218,6 +242,19 @@ public: }; UptimeSnapshot getLatestUptimeSnapshot(int serverId); bool removeUserAvatar(const QString &userName); + + // Outcome of removeUserBanner(), so the caller can tell a missing user apart + // from a user who simply has no banner card set. + enum class RemoveUserBannerResult + { + Removed, + NothingToRemove, + NoSuchUser, + DatabaseError, + }; + RemoveUserBannerResult removeUserBanner(const QString &userName); + int clearBlacklistedBanners(); + bool isCardArtAllowed(const QString &cardName, const QString &cardProviderId) override; bool addForgotPassword(const QString &user); bool removeForgotPassword(const QString &user) override; bool doesForgotPasswordExist(const QString &user); diff --git a/servatrice/src/server_logger.cpp b/servatrice/src/server_logger.cpp index 1972444f1..09453b7ee 100644 --- a/servatrice/src/server_logger.cpp +++ b/servatrice/src/server_logger.cpp @@ -1,42 +1,45 @@ #include "server_logger.h" -#include "settingscache.h" - #include #include #include #include #include #include +#include #include #include -#include #include #include #include #include #include -ServerLogger::ServerLogger(bool _logToConsole, QObject *parent) +ServerLogger::ServerLogger(bool _logToConsole, bool _writeLog, const QString &_logFilters, QObject *parent) : QObject(parent), logToConsole(_logToConsole), flushRunning(false) { + reloadConfig(_writeLog, _logFilters); + connect(this, SIGNAL(sigFlushBuffer()), this, SLOT(flushBuffer()), Qt::QueuedConnection); } ServerLogger::~ServerLogger() { flushBuffer(); + // logFile is a child of this object and is closed and deleted by ~QObject. // This does not work with the destroyed() signal as this destructor is called after the main event loop is done. thread()->quit(); } void ServerLogger::startLog(const QString &logFileName) { + delete logFile; + logFile = nullptr; + if (!logFileName.isEmpty()) { QFileInfo fi(logFileName); QDir fileDir(fi.path()); if (!fileDir.exists() && !fileDir.mkpath(fileDir.absolutePath())) { std::cerr << "ERROR: logfile folder doesn't exist and i can't create it." << std::endl; - logFile = 0; return; } @@ -44,55 +47,81 @@ void ServerLogger::startLog(const QString &logFileName) if (!logFile->open(QIODevice::Append)) { std::cerr << "ERROR: can't open() logfile." << std::endl; delete logFile; - logFile = 0; + logFile = nullptr; return; } - } else { - logFile = 0; } +} - connect(this, SIGNAL(sigFlushBuffer()), this, SLOT(flushBuffer()), Qt::QueuedConnection); +void ServerLogger::reloadConfig(bool _writeLog, const QString &_logFilters) +{ + const bool filterActive = !_logFilters.trimmed().isEmpty(); + const QStringList filters = _logFilters.split(QStringLiteral(","), Qt::SkipEmptyParts); + + configMutex.lock(); + writeLog = _writeLog; + messageFilterActive = filterActive; + logFilters = filters; + configMutex.unlock(); +} + +bool ServerLogger::isLoggingEnabled() const +{ + // Mirror logMessage(): the console is an independent sink and the logfile needs writeLog. + QMutexLocker locker(&configMutex); + return logToConsole || (logFile != nullptr && writeLog); } void ServerLogger::logMessage(const QString &message, void *caller) { - if (!logFile) { - return; - } - QString callerString; if (caller) { - callerString = QString::number((qulonglong)caller, 16) + " "; + callerString = QString::number(reinterpret_cast(caller), 16) + " "; } - // filter out all log entries based on values in configuration file - bool shouldWeWriteLog = settingsCache->value("server/writelog", 1).toBool(); - QString logFilters = settingsCache->value("server/logfilters").toString(); - QStringList listlogFilters = logFilters.split(",", Qt::SkipEmptyParts); - bool shouldWeSkipLine = false; + const QString line = QDateTime::currentDateTime().toString() + " " + callerString + message; + bool queued = false; - if (!shouldWeWriteLog) { - return; + // The console is an independent sink: writelog and logfilters only describe the logfile + // (see servatrice.ini.example), so a disabled or unopenable logfile must not swallow + // startup errors such as a refused database connection. Lines are buffered for the logger + // thread instead of written here, so a stalled stdout reader cannot stall game threads. + if (logToConsole) { + bufferMutex.lock(); + consoleBuffer.append(line); + bufferMutex.unlock(); + queued = true; } - if (!logFilters.trimmed().isEmpty()) { - shouldWeSkipLine = true; - for (const QString &logFilter : listlogFilters) { - if (message.contains(logFilter, Qt::CaseInsensitive)) { - shouldWeSkipLine = false; - break; + configMutex.lock(); + const bool shouldWriteFile = writeLog; + const bool filterMessages = messageFilterActive; + const QStringList filters = logFilters; + configMutex.unlock(); + + if (logFile && shouldWriteFile) { + // filter out all log entries based on values in configuration file + bool shouldWriteLine = !filterMessages; + if (filterMessages) { + for (const QString &logFilter : filters) { + if (message.contains(logFilter, Qt::CaseInsensitive)) { + shouldWriteLine = true; + break; + } } } + + if (shouldWriteLine) { + bufferMutex.lock(); + buffer.append(line); + bufferMutex.unlock(); + queued = true; + } } - if (shouldWeSkipLine) { - return; + if (queued) { + emit sigFlushBuffer(); } - - bufferMutex.lock(); - buffer.append(QDateTime::currentDateTime().toString() + " " + callerString + message); - bufferMutex.unlock(); - emit sigFlushBuffer(); } void ServerLogger::flushBuffer() @@ -102,25 +131,42 @@ void ServerLogger::flushBuffer() } flushRunning = true; - QTextStream stream(logFile); - forever - { - bufferMutex.lock(); - if (buffer.isEmpty()) { - bufferMutex.unlock(); - flushRunning = false; - return; - } - QString message = buffer.takeFirst(); - bufferMutex.unlock(); - stream << message << "\n"; - stream.flush(); + bufferMutex.lock(); + QStringList consoleLines; + consoleLines.swap(consoleBuffer); + bufferMutex.unlock(); - if (logToConsole) { - std::cout << message.toStdString() << std::endl; - } + for (const QString &line : consoleLines) { + std::cout << line.toStdString() << '\n'; } + if (!consoleLines.isEmpty()) { + std::cout.flush(); + } + + if (logFile) { + QTextStream stream(logFile); + forever + { + bufferMutex.lock(); + if (buffer.isEmpty()) { + bufferMutex.unlock(); + break; + } + QString message = buffer.takeFirst(); + bufferMutex.unlock(); + + stream << message << "\n"; + stream.flush(); + } + } else { + // No logfile to write to; do not let the buffer grow without bound. + bufferMutex.lock(); + buffer.clear(); + bufferMutex.unlock(); + } + + flushRunning = false; } void ServerLogger::rotateLogs() @@ -136,5 +182,3 @@ void ServerLogger::rotateLogs() std::cerr << "ERROR: Failed to open log file for writing!" << std::endl; } } - -QFile *ServerLogger::logFile; diff --git a/servatrice/src/server_logger.h b/servatrice/src/server_logger.h index f2875a6a3..40df9fd62 100644 --- a/servatrice/src/server_logger.h +++ b/servatrice/src/server_logger.h @@ -14,10 +14,20 @@ class ServerLogger : public QObject { Q_OBJECT public: - ServerLogger(bool _logToConsole, QObject *parent = 0); + ServerLogger(bool _logToConsole, bool _writeLog, const QString &_logFilters, QObject *parent = nullptr); ~ServerLogger(); + + /** + * @brief Whether messages passed to logMessage() would be written anywhere. + * + * Callers use this to avoid formatting expensive debug payloads that + * logMessage() would discard. + */ + [[nodiscard]] bool isLoggingEnabled() const; + public slots: void startLog(const QString &logFileName); + void reloadConfig(bool _writeLog, const QString &_logFilters); void logMessage(const QString &message, void *caller = 0); void rotateLogs(); private slots: @@ -27,9 +37,17 @@ signals: private: bool logToConsole; - static QFile *logFile; + bool writeLog = true; + bool messageFilterActive = false; + QStringList logFilters; + // Guards writeLog/messageFilterActive/logFilters: SIGHUP rewrites them while other threads log. + mutable QMutex configMutex; + QFile *logFile = nullptr; bool flushRunning; QStringList buffer; + // Console lines wait here for the logger thread, so game threads never block on stdout. + QStringList consoleBuffer; + // Guards buffer and consoleBuffer. QMutex bufferMutex; }; diff --git a/servatrice/src/serversocketinterface.cpp b/servatrice/src/serversocketinterface.cpp index bf8d02fc7..c38d790b1 100644 --- a/servatrice/src/serversocketinterface.cpp +++ b/servatrice/src/serversocketinterface.cpp @@ -22,6 +22,7 @@ #include "deck_tag_serialization.h" #include "email_parser.h" +#include "forwarded_address.h" #include "libcockatrice/protocol/pb/admin_commands.pb.h" #include "libcockatrice/protocol/pb/developer_commands.pb.h" #include "libcockatrice/protocol/pb/moderator_commands.pb.h" @@ -42,6 +43,7 @@ #include "server_logger.h" #include "server_protocolhandler.h" #include "settingscache.h" +#include "trusted_sources.h" #include "version_string.h" #include @@ -124,6 +126,7 @@ #include #include #include +#include #include #include #include @@ -138,6 +141,7 @@ #include #include #include +#include #include #include #include @@ -161,6 +165,7 @@ #include #include #include +#include #include #include #include @@ -194,7 +199,8 @@ AbstractServerSocketInterface::AbstractServerSocketInterface(Servatrice *_server // Never call flushOutputQueue directly from outputQueueChanged. In case of a socket error, // it could lead to this object being destroyed while another function is still on the call stack. -> mutex // deadlocks etc. - connect(this, SIGNAL(outputQueueChanged()), this, SLOT(flushOutputQueue()), Qt::QueuedConnection); + connect(this, &AbstractServerSocketInterface::outputQueueChanged, this, + &AbstractServerSocketInterface::flushOutputQueue, Qt::QueuedConnection); } bool AbstractServerSocketInterface::initSession() @@ -204,15 +210,22 @@ bool AbstractServerSocketInterface::initSession() identEvent.set_server_version(VERSION_STRING); identEvent.set_protocol_version(protocolVersion); if (servatrice->getAuthenticationMethod() == Servatrice::AuthenticationSql) { + // Challenge-response is advertised in every strictness mode: legacy accounts keep + // logging in with the legacy hash, but already-migrated scrypt rows are always + // served challenge-response (authentication_strictness only governs NEW credentials). + // server_options stays exactly SupportsPasswordHash on the wire: already-released + // clients parse an unknown proto2 enum value as NoOptions and would fall back to + // plaintext passwords. Challenge support is advertised in the separate + // supports_challenge_response field, which old clients simply ignore. identEvent.set_server_options(Event_ServerIdentification::SupportsPasswordHash); + identEvent.set_supports_challenge_response(true); } SessionEvent *identSe = prepareSessionEvent(identEvent); sendProtocolItem(*identSe); delete identSe; // allow unlimited number of connections from the trusted sources - QString trustedSources = settingsCache->value("security/trusted_sources", "127.0.0.1,::1").toString(); - if (trustedSources.contains(getAddress(), Qt::CaseInsensitive)) { + if (isTrustedSource(servatrice->getTrustedSources(), getPeerAddress())) { return true; } @@ -255,6 +268,11 @@ void AbstractServerSocketInterface::logDebugMessage(const QString &message) logger->logMessage(message, this); } +bool AbstractServerSocketInterface::logsDebugMessages() const +{ + return logger != nullptr && logger->isLoggingEnabled(); +} + void AbstractServerSocketInterface::processCommandContainer(const CommandContainer &cont) { QElapsedTimer timer; @@ -409,6 +427,8 @@ Response::ResponseCode AbstractServerSocketInterface::processExtendedSessionComm return cmdReportAddComment(cmd.GetExtension(Command_ReportAddComment::ext), rc); case SessionCommand::REPORT_DETAILS: return cmdReportDetails(cmd.GetExtension(Command_ReportDetails::ext), rc); + case SessionCommand::SUBMIT_PASSWORD_VERIFIER: + return cmdSubmitPasswordVerifier(cmd.GetExtension(Command_SubmitPasswordVerifier::ext), rc); default: return Response::RespFunctionNotAllowed; } @@ -467,6 +487,10 @@ Response::ResponseCode AbstractServerSocketInterface::processExtendedModeratorCo return cmdGetModeratorLastLogins(cmd.GetExtension(Command_GetModeratorLastLogins::ext), rc); case ModeratorCommand::REMOVE_USER_AVATAR: return cmdRemoveUserAvatar(cmd.GetExtension(Command_RemoveUserAvatar::ext), rc); + case ModeratorCommand::REMOVE_USER_BANNER: + return cmdRemoveUserBanner(cmd.GetExtension(Command_RemoveUserBanner::ext), rc); + case ModeratorCommand::CLEAR_BLACKLISTED_BANNERS: + return cmdClearBlacklistedBanners(cmd.GetExtension(Command_ClearBlacklistedBanners::ext), rc); default: return Response::RespFunctionNotAllowed; } @@ -1719,12 +1743,17 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetLogHistory(const Com int dateRange = cmd.date_range(); int maximumResults = cmd.maximum_results(); + const int offset = cmd.has_offset() ? static_cast(cmd.offset()) : 0; + const QString beforeTime = cmd.has_before_time() ? QString::fromStdString(cmd.before_time()) : QString(); Response_ViewLogHistory *re = new Response_ViewLogHistory; if (servatrice->getEnableLogQuery()) { - QListIterator messageIterator(sqlInterface->getMessageLogHistory( - userName, ipAddress, gameName, gameID, message, chatType, gameType, roomType, dateRange, maximumResults)); + bool hasMore = false; + QList history = + sqlInterface->getMessageLogHistory(userName, ipAddress, gameName, gameID, message, chatType, gameType, + roomType, dateRange, maximumResults, offset, beforeTime, &hasMore); + QListIterator messageIterator(history); while (messageIterator.hasNext()) { ServerInfo_ChatMessage chatMessage = messageIterator.next(); if (!allowPrivateChat) { @@ -1732,6 +1761,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdGetLogHistory(const Com } re->add_log_message()->CopyFrom(chatMessage); } + re->set_has_more(hasMore); } else { ServerInfo_ChatMessage chatMessage; @@ -1897,9 +1927,8 @@ Response::ResponseCode AbstractServerSocketInterface::cmdBanFromServer(const Com if (amountRemove != 0) { removeSaidMessages(userName, amountRemove); } - QString trustedSources = settingsCache->value("server/trusted_sources", "127.0.0.1,::1").toString(); int minutes = cmd.minutes(); - if (trustedSources.contains(address, Qt::CaseInsensitive)) { + if (isTrustedSource(servatrice->getTrustedSources(), address)) { address = ""; } @@ -1960,7 +1989,8 @@ Response::ResponseCode AbstractServerSocketInterface::cmdBanFromServer(const Com SessionEvent *se = userList[i]->prepareSessionEvent(event); userList[i]->sendProtocolItem(*se); delete se; - QMetaObject::invokeMethod(userList[i], "prepareDestroy", Qt::QueuedConnection); + QMetaObject::invokeMethod( + userList[i], [user = userList[i]] { user->prepareDestroy(); }, Qt::QueuedConnection); } } @@ -2619,7 +2649,15 @@ Response::ResponseCode AbstractServerSocketInterface::cmdResetUserPassword(const } const QString tempPassword = PasswordHasher::generateRandomSalt(); - if (!sqlInterface->changeUserPassword(userName, tempPassword, true)) { + // Store a scrypt verifier rather than a legacy salt+hash: a legacy row makes the next + // login look like an authentication downgrade, which modern clients refuse when they + // still hold a verifier for the account. Where security/legacy_backup_key is configured, + // keep a sealed legacy hash of the same temporary password as the downgrade backup so + // pre-challenge clients keep the path the reset used to provide; without the key they + // have none, which changeUserPasswordWithLegacyBackup warns about. + const QString verifier = PasswordHasher::generatePasswordVerifier(tempPassword); + const QString legacyCredential = PasswordHasher::computeHash(tempPassword, PasswordHasher::generateRandomSalt()); + if (!sqlInterface->changeUserPasswordWithLegacyBackup(userName, verifier, legacyCredential)) { return Response::RespInternalError; } sqlInterface->setForcePasswordChange(userName, true); @@ -2675,6 +2713,67 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRemoveUserAvatar(const return Response::RespOk; } +Response::ResponseCode AbstractServerSocketInterface::cmdRemoveUserBanner(const Command_RemoveUserBanner &cmd, + ResponseContainer &rc) +{ + if (!sqlInterface->checkSql()) { + return Response::RespInternalError; + } + + const QString userName = nameFromStdString(cmd.user_name()).simplified(); + if (userName.isEmpty()) { + return Response::RespContextError; + } + + bool hadBanner = true; + switch (sqlInterface->removeUserBanner(userName)) { + case Servatrice_DatabaseInterface::RemoveUserBannerResult::Removed: + break; + case Servatrice_DatabaseInterface::RemoveUserBannerResult::NothingToRemove: + hadBanner = false; + break; + case Servatrice_DatabaseInterface::RemoveUserBannerResult::NoSuchUser: + return Response::RespNameNotFound; + case Servatrice_DatabaseInterface::RemoveUserBannerResult::DatabaseError: + default: + return Response::RespInternalError; + } + + if (hadBanner) { + sqlInterface->addAuditRecord(userName, this->getAddress(), QString::fromStdString(userInfo->clientid()), + "REMOVE_USER_BANNER", "Moderator removed user banner card", true); + } + + Response_RemoveUserBanner *re = new Response_RemoveUserBanner; + re->set_user_name(userName.toStdString()); + re->set_had_banner(hadBanner); + rc.setResponseExtension(re); + return Response::RespOk; +} + +Response::ResponseCode +AbstractServerSocketInterface::cmdClearBlacklistedBanners(const Command_ClearBlacklistedBanners & /* cmd */, + ResponseContainer &rc) +{ + if (!sqlInterface->checkSql()) { + return Response::RespInternalError; + } + + const int cleared = sqlInterface->clearBlacklistedBanners(); + if (cleared < 0) { + return Response::RespInternalError; + } + + sqlInterface->addAuditRecord(QString::fromStdString(userInfo->name()), this->getAddress(), + QString::fromStdString(userInfo->clientid()), "CLEAR_BLACKLISTED_BANNERS", + QString("Moderator cleared %1 blacklisted banner(s)").arg(cleared), true); + + Response_ClearBlacklistedBanners *re = new Response_ClearBlacklistedBanners; + re->set_cleared_count(static_cast(cleared)); + rc.setResponseExtension(re); + return Response::RespOk; +} + void AbstractServerSocketInterface::sendPendingReportNotifications(ResponseContainer &rc) { if (!sqlInterface->checkSql()) { @@ -3298,6 +3397,11 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRegisterAccount(const C password = QString::fromStdString(cmd.hashed_password()); } + // Reject credential formats the configured authentication strictness does not accept. + if (!acceptsCredentialFormat(passwordNeedsHash, password)) { + return Response::RespClientUpdateRequired; + } + bool requireEmailActivation = settingsCache->value("registration/requireemailactivation", true).toBool(); bool regSucceeded = sqlInterface->registerUser(userName, realName, password, passwordNeedsHash, parsedEmailAddress, country, !requireEmailActivation); @@ -3342,6 +3446,30 @@ bool AbstractServerSocketInterface::tooManyRegistrationAttempts(const QString &i return servatrice->recordRegistrationAttempt(ipAddress); } +bool AbstractServerSocketInterface::acceptsCredentialFormat(bool passwordNeedsHash, const QString &password) const +{ + // An empty credential must never reach the database: it would be accepted + // as a legacy format and stored as '' (fail-open on login, see the empty + // stored-credential guard in Servatrice_DatabaseInterface). + if (password.isEmpty()) { + return false; + } + // "scryptFormat" means the client sent a derived verifier rather than a + // password to hash ourselves. parsePasswordVerifier enforces the sane-cost + // clamp, so nothing starting with '$' reaches the database unparsed. + const bool scryptFormat = !passwordNeedsHash && PasswordHasher::parsePasswordVerifier(password).isValid; + + // The strictness mode governs how existing legacy accounts are served, not which new-credential + // formats are tolerated: a legacy-mode server must still accept scrypt verifiers, because clients + // derive them whenever challenge-response is advertised (and it must be, so already-migrated + // scrypt rows keep logging in). strict is the only mode that rejects legacy formats. + if (servatrice->getAuthenticationStrictness() == Servatrice::AuthenticationStrict) { + return scryptFormat; + } + // legacy and mixed accept a valid scrypt verifier or a genuine legacy salt+hash. + return scryptFormat || PasswordHasher::isLegacyFormat(password); +} + Response::ResponseCode AbstractServerSocketInterface::cmdActivateAccount(const Command_Activate &cmd, ResponseContainer & /*rc*/) { @@ -3481,26 +3609,6 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountImage(const Comm return Response::RespOk; } -bool AbstractServerSocketInterface::isCardNameAllowed(const QString &cardName, const QString &cardProviderId) -{ - QSqlQuery *q = sqlInterface->prepareQuery( - "SELECT mode FROM {prefix}_card_art_name_rules WHERE card_name = :name AND card_provider_id = :provider"); - - q->bindValue(":name", cardName); - q->bindValue(":provider", cardProviderId); - - if (!sqlInterface->execSqlQuery(q)) { - qWarning() << "Card art rule lookup failed; failing open for" << cardName; - return true; - } - - if (!q->next()) { - return true; // default allow - } - - return q->value(0).toString() != "DENY"; -} - Response::ResponseCode AbstractServerSocketInterface::cmdSetCardArtParams(const Command_SetCardArtParams &cmd, ResponseContainer & /* rc */) { @@ -3527,7 +3635,7 @@ Response::ResponseCode AbstractServerSocketInterface::cmdSetCardArtParams(const return Response::RespOk; } - if (!isCardNameAllowed(cardName, cardProviderId)) { + if (!sqlInterface->isCardArtAllowed(cardName, cardProviderId)) { return Response::RespFunctionNotAllowed; } @@ -3676,12 +3784,20 @@ Response::ResponseCode AbstractServerSocketInterface::cmdAccountPassword(const C newPassword = QString::fromStdString(cmd.hashed_new_password()); } + // Reject new credential formats the configured authentication strictness does not accept. + if (!acceptsCredentialFormat(newPasswordNeedsHash, newPassword)) { + return Response::RespClientUpdateRequired; + } + QString userName = QString::fromStdString(userInfo->name()); if (!databaseInterface->changeUserPassword(userName, oldPassword, true, newPassword, newPasswordNeedsHash)) { return Response::RespWrongPassword; } databaseInterface->setForcePasswordChange(userName, false); + // The forced change is done, but this session never completed login (see + // Server_ProtocolHandler::cmdLogin). It stays restricted to ping and another password + // change; the client reconnects with the new password to actually enter the server. return Response::RespOk; } @@ -3822,6 +3938,11 @@ Response::ResponseCode AbstractServerSocketInterface::cmdForgotPasswordReset(con password = QString::fromStdString(cmd.hashed_new_password()); } + // Reject new credential formats the configured authentication strictness does not accept. + if (!acceptsCredentialFormat(passwordNeedsHash, password)) { + return Response::RespClientUpdateRequired; + } + if (sqlInterface->changeUserPassword(nameFromStdString(cmd.user_name()), password, passwordNeedsHash)) { if (servatrice->getEnableForgotPasswordAudit()) { sqlInterface->addAuditRecord(userName.simplified(), this->getAddress(), clientId.simplified(), @@ -3889,9 +4010,11 @@ AbstractServerSocketInterface::cmdForgotPasswordChallenge(const Command_ForgotPa Response::ResponseCode AbstractServerSocketInterface::cmdRequestPasswordSalt(const Command_RequestPasswordSalt &cmd, ResponseContainer &rc) { - const QString userName = nameFromStdString(cmd.user_name()); - QString passwordSalt = sqlInterface->getUserSalt(userName); - if (passwordSalt.isEmpty()) { + // Simplified exactly like cmdLogin, so the nonce bound to userName here validates + // against the same normalized name during the login's challenge-response check. + const QString userName = nameFromStdString(cmd.user_name()).simplified(); + const QString storedPasswordData = sqlInterface->getUserPasswordData(userName); + if (storedPasswordData.isEmpty()) { if (server->getRegOnlyServerEnabled()) { return Response::RespRegistrationRequired; } else { @@ -3899,8 +4022,46 @@ Response::ResponseCode AbstractServerSocketInterface::cmdRequestPasswordSalt(con return Response::RespOk; } } + auto *re = new Response_PasswordSalt; - re->set_password_salt(passwordSalt.toStdString()); + if (PasswordHasher::isLegacyFormat(storedPasswordData)) { + re->set_password_salt(storedPasswordData.left(16).toStdString()); + re->set_needs_migration(true); + // Legacy rows get a challenge-response nonce only outside legacy mode (there the client + // logs in with the legacy hash and the account is migrated). In legacy mode the row is + // served the legacy salt, since legacy mode only governs what NEW credentials are accepted. + if (servatrice->getAuthenticationStrictness() != Servatrice::AuthenticationLegacy) { + const QByteArray nonce = CryptoUtil::randomBytes(32); + setAuthNonce(nonce, userName); + re->set_nonce(nonce.constData(), nonce.size()); + } + } else { + const PasswordVerifier verifier = PasswordHasher::parsePasswordVerifier(storedPasswordData); + if (!verifier.isValid) { + delete re; + return Response::RespContextError; + } + const QString scryptSalt = QString(verifier.salt.toBase64()); + re->set_password_salt(scryptSalt.toStdString()); + re->set_scrypt_salt(scryptSalt.toStdString()); + re->set_n(verifier.n); + re->set_r(verifier.r); + re->set_p(verifier.p); + re->set_needs_migration(false); + // Pre-challenge clients derive the legacy hash from password_salt. While a fresh + // encrypted legacy backup exists, serve its salt there so they can still log in; + // challenge-response clients must use scrypt_salt, which always carries the + // verifier salt. + const QString legacyBackup = sqlInterface->getUsableLegacyBackup(userName); + if (!legacyBackup.isEmpty()) { + re->set_password_salt(legacyBackup.left(16).toStdString()); + } + // scrypt rows are served challenge-response in every mode so migrated accounts never lock out. + const QByteArray nonce = CryptoUtil::randomBytes(32); + setAuthNonce(nonce, userName); + re->set_nonce(nonce.constData(), nonce.size()); + } + rc.setResponseExtension(re); return Response::RespOk; } @@ -3998,6 +4159,39 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReport(const Command_Re return Response::RespOk; } +Response::ResponseCode +AbstractServerSocketInterface::cmdSubmitPasswordVerifier(const Command_SubmitPasswordVerifier &cmd, + ResponseContainer & /*rc*/) +{ + if (authState != PasswordRight) { + return Response::RespLoginNeeded; + } + + // Limit to the size of the database column (password_sha512 varchar(255)). + constexpr int MAX_PASSWORD_VERIFIER_LENGTH = 255; + const QString passwordVerifier = QString::fromStdString(cmd.password_verifier()); + if (passwordVerifier.isEmpty() || passwordVerifier.length() > MAX_PASSWORD_VERIFIER_LENGTH || + PasswordHasher::isLegacyFormat(passwordVerifier)) { + return Response::RespContextError; + } + + // Reject unparseable or hostile cost parameters before they reach the database. + const PasswordVerifier parsedVerifier = PasswordHasher::parsePasswordVerifier(passwordVerifier); + if (!parsedVerifier.isValid) { + qCWarning(AbstractServerSocketInterfaceLog) + << "Rejecting password verifier submission with invalid or insane cost parameters"; + return Response::RespContextError; + } + + if (!sqlInterface->submitPasswordVerifier(QString::fromStdString(userInfo->name()), passwordVerifier)) { + return Response::RespContextError; + } + + qCDebug(AbstractServerSocketInterfaceLog) + << "Password verifier migrated for user" << QString::fromStdString(userInfo->name()); + return Response::RespOk; +} + // ADMIN FUNCTIONS. // Permission is checked by the calling function. @@ -4005,15 +4199,15 @@ Response::ResponseCode AbstractServerSocketInterface::cmdUpdateServerMessage(const Command_UpdateServerMessage & /*cmd*/, ResponseContainer & /*rc*/) { - QMetaObject::invokeMethod(server, "updateLoginMessage"); + QMetaObject::invokeMethod(servatrice, [srv = servatrice] { srv->updateLoginMessage(); }); return Response::RespOk; } Response::ResponseCode AbstractServerSocketInterface::cmdShutdownServer(const Command_ShutdownServer &cmd, ResponseContainer & /*rc*/) { - QMetaObject::invokeMethod(server, "scheduleShutdown", Q_ARG(QString, textFromStdString(cmd.reason())), - Q_ARG(int, cmd.minutes())); + QMetaObject::invokeMethod(servatrice, [srv = servatrice, reason = textFromStdString(cmd.reason()), + minutes = cmd.minutes()] { srv->scheduleShutdown(reason, minutes); }); return Response::RespOk; } @@ -4022,7 +4216,9 @@ Response::ResponseCode AbstractServerSocketInterface::cmdReloadConfig(const Comm { logDebugMessage("Received admin command: reloading configuration"); settingsCache->sync(); - QMetaObject::invokeMethod(server, "setRequiredFeatures", Q_ARG(QString, server->getRequiredFeatures())); + QMetaObject::invokeMethod(servatrice, [srv = servatrice, features = servatrice->getRequiredFeatures()] { + srv->setRequiredFeatures(features); + }); return Response::RespOk; } @@ -4074,7 +4270,7 @@ bool AbstractServerSocketInterface::removeAdminFlagFromUser(const QString &userN delete se; } - QMetaObject::invokeMethod(user, "prepareDestroy", Qt::QueuedConnection); + QMetaObject::invokeMethod(user, [user] { user->prepareDestroy(); }, Qt::QueuedConnection); return true; } @@ -4249,10 +4445,10 @@ TcpServerSocketInterface::TcpServerSocketInterface(Servatrice *_server, { socket = new QTcpSocket(this); socket->setSocketOption(QAbstractSocket::LowDelayOption, 1); - connect(socket, SIGNAL(readyRead()), this, SLOT(readClient())); - connect(socket, SIGNAL(disconnected()), this, SLOT(catchSocketDisconnected())); - connect(socket, SIGNAL(errorOccurred(QAbstractSocket::SocketError)), this, - SLOT(catchSocketError(QAbstractSocket::SocketError))); + connect(socket, &QTcpSocket::readyRead, this, [this] { readClient(); }); + connect(socket, &QTcpSocket::disconnected, this, [this] { catchSocketDisconnected(); }); + connect(socket, &QTcpSocket::errorOccurred, this, + [this](QAbstractSocket::SocketError error) { catchSocketError(error); }); } TcpServerSocketInterface::~TcpServerSocketInterface() @@ -4328,13 +4524,17 @@ void TcpServerSocketInterface::readClient() servatrice->incRxBytes(data.size()); inputBuffer.append(data); - do { + // Parse messages straight out of inputBuffer at readOffset. Removing each + // consumed message from the front would memmove the remaining bytes once + // per message; the consumed prefix is dropped once parsing stops. + qsizetype readOffset = 0; + + while (true) { if (!messageInProgress) { - if (inputBuffer.size() >= 4) { - messageLength = (((quint32)(unsigned char)inputBuffer[0]) << 24) + - (((quint32)(unsigned char)inputBuffer[1]) << 16) + - (((quint32)(unsigned char)inputBuffer[2]) << 8) + - ((quint32)(unsigned char)inputBuffer[3]); + if (inputBuffer.size() - readOffset >= 4) { + const auto *header = reinterpret_cast(inputBuffer.constData() + readOffset); + messageLength = (static_cast(header[0]) << 24) + (static_cast(header[1]) << 16) + + (static_cast(header[2]) << 8) + static_cast(header[3]); // Reject implausible lengths instead of buffering until they arrive. // This also handles lengths that overflow into negative int values. if (messageLength < 0 || messageLength > MAX_TCP_MESSAGE_SIZE) { @@ -4343,32 +4543,33 @@ void TcpServerSocketInterface::readClient() prepareDestroy(); return; } - inputBuffer.remove(0, 4); + readOffset += 4; messageInProgress = true; } else { - return; + break; } } - if (inputBuffer.size() < messageLength) { - return; + if (inputBuffer.size() - readOffset < messageLength) { + break; } CommandContainer newCommandContainer; - bool ok; + bool ok = false; try { - ok = newCommandContainer.ParseFromArray(inputBuffer.data(), messageLength); + ok = newCommandContainer.ParseFromArray(inputBuffer.constData() + readOffset, messageLength); } catch (std::exception &e) { - qCWarning(TcpServerSocketInterfaceLog) << "Caught std::exception in" << __FILE__ << __LINE__ << + qCWarning(TcpServerSocketInterfaceLog) + << "Caught std::exception in" << __FILE__ << __LINE__ << #ifdef _MSC_VER // Visual Studio __FUNCTION__ #else __PRETTY_FUNCTION__ #endif - << Qt::endl - << "Exception:" << e.what() << Qt::endl - << "Message coming from:" << getAddress() << Qt::endl - << "Message length:" << messageLength << Qt::endl - << "Message content:" << inputBuffer.toHex(); + << Qt::endl + << "Exception:" << e.what() << Qt::endl + << "Message coming from:" << getAddress() << Qt::endl + << "Message length:" << messageLength << Qt::endl + << "Message content:" << inputBuffer.mid(readOffset, messageLength).toHex(); } catch (...) { qCWarning(TcpServerSocketInterfaceLog) << "Unhandled exception in" << __FILE__ << __LINE__ << #ifdef _MSC_VER // Visual Studio @@ -4379,7 +4580,7 @@ void TcpServerSocketInterface::readClient() << Qt::endl << "Message coming from:" << getAddress(); } - inputBuffer.remove(0, messageLength); + readOffset += messageLength; messageInProgress = false; if (ok) { @@ -4396,8 +4597,12 @@ void TcpServerSocketInterface::readClient() } else { qCWarning(TcpServerSocketInterfaceLog) << "parsing error!"; } + } - } while (!inputBuffer.isEmpty()); + // Drop the consumed prefix once, instead of shifting the buffer per message. + if (readOffset > 0) { + inputBuffer.remove(0, readOffset); + } } bool TcpServerSocketInterface::initTcpSession() @@ -4457,21 +4662,25 @@ void WebsocketServerSocketInterface::initConnection(void *_socket) if (websocketIPHeader.length() > 0 && socket->request().hasRawHeader(websocketIPHeader)) { // Only trust the forwarded header if the connection itself comes from a trusted proxy, // otherwise a client could spoof its address to bypass bans and rate limits. - QString trustedSources = settingsCache->value("security/trusted_sources", "127.0.0.1,::1").toString(); - if (trustedSources.contains(socket->peerAddress().toString(), Qt::CaseInsensitive)) { - QString header(socket->request().rawHeader(websocketIPHeader)); - QHostAddress parsed(header); + if (isTrustedSource(servatrice->getTrustedSources(), socket->peerAddress())) { + const QHostAddress parsed = parseForwardedAddress(socket->request().rawHeader(websocketIPHeader)); if (!parsed.isNull()) { address = parsed; } } } - connect(socket, SIGNAL(binaryMessageReceived(const QByteArray &)), this, - SLOT(binaryMessageReceived(const QByteArray &))); + connect(socket, &QWebSocket::binaryMessageReceived, this, &WebsocketServerSocketInterface::binaryMessageReceived); + // QWebSocket::errorOccurred was added in Qt 6.5 (error() is deprecated from 6.5 on), + // so a typed connect cannot compile against the 6.4 floor. +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + connect(socket, &QWebSocket::errorOccurred, this, + [this](QAbstractSocket::SocketError error) { catchSocketError(error); }); +#else connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(catchSocketError(QAbstractSocket::SocketError))); - connect(socket, SIGNAL(disconnected()), this, SLOT(catchSocketDisconnected())); +#endif + connect(socket, &QWebSocket::disconnected, this, [this] { catchSocketDisconnected(); }); // Add this object to the server's list of connections before it can receive socket events. // Otherwise, in case of a socket error, it could be removed from the list before it is added. diff --git a/servatrice/src/serversocketinterface.h b/servatrice/src/serversocketinterface.h index 7de742c66..cd37286c6 100644 --- a/servatrice/src/serversocketinterface.h +++ b/servatrice/src/serversocketinterface.h @@ -73,6 +73,7 @@ class Command_AccountPassword; class Command_Activate; class Command_AddCardArtRule; class Command_AdjustMod; +class Command_ClearBlacklistedBanners; class Command_ForceActivateUser; class Command_ForgotPasswordChallenge; class Command_ForgotPasswordRequest; @@ -90,6 +91,7 @@ class Command_ListCardArtRules; class Command_Register; class Command_RemoveCardArtRule; class Command_RemoveUserAvatar; +class Command_RemoveUserBanner; class Command_Report; class Command_ReportAddComment; class Command_ReportAssign; @@ -103,6 +105,7 @@ class Command_ReportUserInfo; class Command_RequestPasswordSalt; class Command_ResetUserPassword; class Command_SetCardArtParams; +class Command_SubmitPasswordVerifier; class Command_UpdateAdminNotes; class Command_ViewLogHistory; class Command_WarnUser; @@ -122,8 +125,10 @@ signals: protected: void logDebugMessage(const QString &message) override; + [[nodiscard]] bool logsDebugMessages() const override; bool tooManyRegistrationAttempts(const QString &ipAddress); void processCommandContainer(const CommandContainer &cont) override; + bool acceptsCredentialFormat(bool passwordNeedsHash, const QString &password) const; virtual void writeToSocket(QByteArray &data) = 0; virtual void flushSocket() = 0; @@ -205,6 +210,7 @@ private: Response::ResponseCode cmdReportDetails(const Command_ReportDetails &cmd, ResponseContainer &rc); Response::ResponseCode cmdReportAddComment(const Command_ReportAddComment &cmd, ResponseContainer &rc); Response::ResponseCode cmdReplayDownloadByGameId(const Command_ReplayDownloadByGameId &cmd, ResponseContainer &rc); + Response::ResponseCode cmdSubmitPasswordVerifier(const Command_SubmitPasswordVerifier &cmd, ResponseContainer &rc); Response::ResponseCode processExtendedSessionCommand(int cmdType, const SessionCommand &cmd, ResponseContainer &rc) override; Response::ResponseCode @@ -216,7 +222,6 @@ private: Response::ResponseCode cmdAccountEdit(const Command_AccountEdit &cmd, ResponseContainer &rc); Response::ResponseCode cmdAccountImage(const Command_AccountImage &cmd, ResponseContainer &rc); - bool isCardNameAllowed(const QString &cardName, const QString &cardProviderId); Response::ResponseCode cmdSetCardArtParams(const Command_SetCardArtParams &cmd, ResponseContainer &); Response::ResponseCode cmdAddCardArtRule(const Command_AddCardArtRule &cmd, ResponseContainer &); Response::ResponseCode cmdRemoveCardArtRule(const Command_RemoveCardArtRule &cmd, ResponseContainer &); @@ -233,6 +238,9 @@ private: Response::ResponseCode cmdGetModeratorLastLogins(const Command_GetModeratorLastLogins &cmd, ResponseContainer &rc); Response::ResponseCode cmdResetUserPassword(const Command_ResetUserPassword &cmd, ResponseContainer &rc); Response::ResponseCode cmdRemoveUserAvatar(const Command_RemoveUserAvatar &cmd, ResponseContainer &rc); + Response::ResponseCode cmdRemoveUserBanner(const Command_RemoveUserBanner &cmd, ResponseContainer &rc); + Response::ResponseCode cmdClearBlacklistedBanners(const Command_ClearBlacklistedBanners &cmd, + ResponseContainer &rc); Response::ResponseCode cmdGetServerStats(const Command_GetServerStats &cmd, ResponseContainer &rc); diff --git a/servatrice/src/signalhandler.cpp b/servatrice/src/signalhandler.cpp index 32cd5b206..b8a7e3538 100644 --- a/servatrice/src/signalhandler.cpp +++ b/servatrice/src/signalhandler.cpp @@ -73,6 +73,8 @@ void SignalHandler::internalSigHupHandler() logger->rotateLogs(); settingsCache->sync(); + logger->reloadConfig(settingsCache->value("server/writelog", 1).toBool(), + settingsCache->value("server/logfilters").toString()); snHup->setEnabled(true); } diff --git a/servatrice/src/smtpclient.cpp b/servatrice/src/smtpclient.cpp index 82824e496..bfdfae905 100644 --- a/servatrice/src/smtpclient.cpp +++ b/servatrice/src/smtpclient.cpp @@ -25,7 +25,7 @@ static const qint64 IDLE_TIMEOUT_MS = 60000; // 60 seconds // MAX_RETRY_DELAY cap is applied, preventing unbounded intermediate values. static const int MAX_BACKOFF_SHIFT = 10; -inline Q_LOGGING_CATEGORY(SmtpClientLog, "smtp_client") +inline Q_LOGGING_CATEGORY(SmtpClientLog, "smtp_client"); static QString pendingKey(const QString &userName, EmailType type, const QString &token) { @@ -55,24 +55,23 @@ SmtpClient::SmtpClient(QObject *parent) : QObject(parent) idleTimer = new QTimer(this); idleTimer->setSingleShot(true); - connect(idleTimer, SIGNAL(timeout()), this, SLOT(idleTimeout())); + connect(idleTimer, &QTimer::timeout, this, &SmtpClient::idleTimeout); - connect(smtp, SIGNAL(authenticated()), this, SLOT(authenticated())); - connect(smtp, SIGNAL(authenticationFailed(const QByteArray &)), this, - SLOT(authenticationFailed(const QByteArray &))); - connect(smtp, SIGNAL(connected()), this, SLOT(connected())); - connect(smtp, SIGNAL(connectionFailed(const QByteArray &)), this, SLOT(connectionFailed(const QByteArray &))); - connect(smtp, SIGNAL(disconnected()), this, SLOT(disconnected())); - connect(smtp, SIGNAL(encrypted()), this, SLOT(encrypted())); - connect(smtp, SIGNAL(encryptionFailed(const QByteArray &)), this, SLOT(encryptionFailed(const QByteArray &))); - connect(smtp, SIGNAL(finished()), this, SLOT(finished())); - connect(smtp, SIGNAL(mailFailed(int, int, const QByteArray &)), this, - SLOT(mailFailed(int, int, const QByteArray &))); - connect(smtp, SIGNAL(mailSent(int)), this, SLOT(mailSent(int))); - connect(smtp, SIGNAL(recipientRejected(int, const QString &, const QByteArray &)), this, - SLOT(recipientRejected(int, const QString &, const QByteArray &))); - connect(smtp, SIGNAL(senderRejected(int, const QString &, const QByteArray &)), this, - SLOT(senderRejected(int, const QString &, const QByteArray &))); + connect(smtp, &QxtSmtp::authenticated, this, &SmtpClient::authenticated); + connect(smtp, qOverload(&QxtSmtp::authenticationFailed), this, + &SmtpClient::authenticationFailed); + connect(smtp, &QxtSmtp::connected, this, &SmtpClient::connected); + connect(smtp, qOverload(&QxtSmtp::connectionFailed), this, &SmtpClient::connectionFailed); + connect(smtp, &QxtSmtp::disconnected, this, &SmtpClient::disconnected); + connect(smtp, &QxtSmtp::encrypted, this, &SmtpClient::encrypted); + connect(smtp, qOverload(&QxtSmtp::encryptionFailed), this, &SmtpClient::encryptionFailed); + connect(smtp, &QxtSmtp::finished, this, &SmtpClient::finished); + connect(smtp, qOverload(&QxtSmtp::mailFailed), this, &SmtpClient::mailFailed); + connect(smtp, &QxtSmtp::mailSent, this, &SmtpClient::mailSent); + connect(smtp, qOverload(&QxtSmtp::recipientRejected), this, + &SmtpClient::recipientRejected); + connect(smtp, qOverload(&QxtSmtp::senderRejected), this, + &SmtpClient::senderRejected); } SmtpClient::~SmtpClient() diff --git a/servatrice/src/trusted_sources.cpp b/servatrice/src/trusted_sources.cpp new file mode 100644 index 000000000..327491936 --- /dev/null +++ b/servatrice/src/trusted_sources.cpp @@ -0,0 +1,23 @@ +#include "trusted_sources.h" + +#include + +bool isTrustedSource(const QString &trustedSources, const QHostAddress &address) +{ + if (address.isNull()) { + return false; + } + const QStringList entries = trustedSources.split(',', Qt::SkipEmptyParts); + for (const QString &entry : entries) { + const QHostAddress trusted(entry.trimmed()); + if (!trusted.isNull() && trusted.isEqual(address, QHostAddress::ConvertV4MappedToIPv4)) { + return true; + } + } + return false; +} + +bool isTrustedSource(const QString &trustedSources, const QString &address) +{ + return isTrustedSource(trustedSources, QHostAddress(address.trimmed())); +} diff --git a/servatrice/src/trusted_sources.h b/servatrice/src/trusted_sources.h new file mode 100644 index 000000000..3aea55e41 --- /dev/null +++ b/servatrice/src/trusted_sources.h @@ -0,0 +1,20 @@ +#ifndef TRUSTED_SOURCES_H +#define TRUSTED_SOURCES_H + +#include +#include + +/** + * @brief True if @p address is listed in the comma-separated @p trustedSources (security/trusted_sources). + * + * Entries are parsed as addresses and compared exactly, never as substrings, so + * 27.0.0.1 does not match 127.0.0.1. IPv4-mapped IPv6 addresses compare equal to + * their IPv4 form: a dual-stack listener reports a local proxy as ::ffff:127.0.0.1, + * which matches a 127.0.0.1 entry. Malformed entries and null addresses never match. + */ +bool isTrustedSource(const QString &trustedSources, const QHostAddress &address); + +/** @brief Overload for an address in string form; an unparsable string never matches. */ +bool isTrustedSource(const QString &trustedSources, const QString &address); + +#endif diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 095116494..5d956c2c3 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -174,9 +174,31 @@ endfunction() cockatrice_add_test(NAME expression_test SOURCES expression_test.cpp LIBS libcockatrice_utility) cockatrice_add_test(NAME clamped_arithmetic_test SOURCES clamped_arithmetic_test.cpp LIBS libcockatrice_utility) cockatrice_add_test(NAME test_age_formatting SOURCES test_age_formatting.cpp LIBS libcockatrice_utility) +cockatrice_add_test(NAME user_data_backup_test SOURCES user_data_backup_test.cpp LIBS libcockatrice_utility) cockatrice_add_test(NAME password_hash_test SOURCES password_hash_test.cpp LIBS libcockatrice_utility) cockatrice_add_test(NAME report_categories_test SOURCES report_categories_test.cpp LIBS libcockatrice_utility) cockatrice_add_test(NAME warning_categories_test SOURCES warning_categories_test.cpp LIBS libcockatrice_utility) +# The installer's holder probe is Windows-only, but the wording it produces and the +# question which programs are the payload's own are plain C++ and are what a stuck user +# has to act on, so they are tested everywhere. +cockatrice_add_test( + NAME + holder_line_test + SOURCES + ${CMAKE_SOURCE_DIR}/tools/installer-holder-probe/holder_line.cpp + holder_line_test.cpp + INCLUDE_DIRS + ${CMAKE_SOURCE_DIR}/tools/installer-holder-probe +) +cockatrice_add_test( + NAME + payload_paths_test + SOURCES + ${CMAKE_SOURCE_DIR}/tools/installer-holder-probe/payload_paths.cpp + payload_paths_test.cpp + INCLUDE_DIRS + ${CMAKE_SOURCE_DIR}/tools/installer-holder-probe +) cockatrice_add_test( NAME playmat_resolver_test @@ -207,6 +229,39 @@ cockatrice_add_test( LABELS server ) +cockatrice_add_test( + NAME + forwarded_address_test + SOURCES + ${CMAKE_SOURCE_DIR}/servatrice/src/forwarded_address.cpp + forwarded_address_test.cpp + INCLUDE_DIRS + ${CMAKE_SOURCE_DIR}/servatrice/src + LABELS + server +) +cockatrice_add_test( + NAME + trusted_sources_test + SOURCES + ${CMAKE_SOURCE_DIR}/servatrice/src/trusted_sources.cpp + trusted_sources_test.cpp + INCLUDE_DIRS + ${CMAKE_SOURCE_DIR}/servatrice/src + LABELS + server +) +cockatrice_add_test( + NAME + server_logger_test + SOURCES + ${CMAKE_SOURCE_DIR}/servatrice/src/server_logger.cpp + server_logger_test.cpp + INCLUDE_DIRS + ${CMAKE_SOURCE_DIR}/servatrice/src + LABELS + server +) cockatrice_add_test( NAME server_card_counter_test @@ -273,6 +328,51 @@ cockatrice_add_test( LABELS server ) +cockatrice_add_test( + NAME + server_forced_password_change_test + SOURCES + server_forced_password_change_test.cpp + LIBS + libcockatrice_network_server_remote + libcockatrice_rng + LABELS + server +) +cockatrice_add_test( + NAME + client_login_status_test + SOURCES + ${VERSION_STRING_CPP} + client_login_status_test.cpp + LIBS + libcockatrice_network_client_remote + LABELS + ci +) +# The local game runs one client and one server in the same process. +# Either of them can be destroyed first; the survivor has to stay usable. +cockatrice_add_test( + NAME + local_client_lifetime_test + SOURCES + local_client_lifetime_test.cpp + LIBS + libcockatrice_network + LABELS + ci +) +cockatrice_add_test( + NAME + server_room_channels_test + SOURCES + server_room_channels_test.cpp + LIBS + libcockatrice_network_server_remote + libcockatrice_rng + LABELS + server +) cockatrice_add_test( NAME latency_tracker_test @@ -333,6 +433,7 @@ cockatrice_add_test( ${VERSION_STRING_CPP} loader_local_matching_test.cpp LIBS + libcockatrice_network libcockatrice_settings INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/cockatrice/src @@ -369,6 +470,7 @@ if(TARGET Qt6::lrelease) libcockatrice_utility DEFINITIONS TRANSLATION_LOADER_TEST_DIR="${CMAKE_CURRENT_BINARY_DIR}" + COCKATRICE_TRANSLATIONS_DIR="${CMAKE_SOURCE_DIR}/cockatrice/translations" ) add_dependencies(translation_loader_test translation_loader_test_qm) @@ -397,6 +499,37 @@ if(WITH_CLIENT) ${CMAKE_SOURCE_DIR}/cockatrice/src ${CMAKE_SOURCE_DIR}/libcockatrice_utility ) + # The installer's probe and the client build the socket name independently, and the + # command they exchange is spelled in both places. Compiling both ends into one test is + # what keeps them from drifting apart unnoticed: a mismatch would leave the installer + # unable to close an instance whose window it cannot reach. + cockatrice_add_test( + NAME + installer_close_server_test + SOURCES + ${CMAKE_SOURCE_DIR}/cockatrice/src/installer_close_server.cpp + ${CMAKE_SOURCE_DIR}/tools/installer-holder-probe/installer_close_name.cpp + installer_close_server_test.cpp + INCLUDE_DIRS + ${CMAKE_SOURCE_DIR}/cockatrice/src + ${CMAKE_SOURCE_DIR}/libcockatrice_utility + ${CMAKE_SOURCE_DIR}/tools/installer-holder-probe + ) + # CrashReporter is client code; DEFINITIONS keeps the test hermetic by compiling the + # no-op path (no sentry-native headers needed) regardless of WITH_CRASH_REPORTING. + cockatrice_add_test( + NAME + crash_reporter_test + SOURCES + ${CMAKE_SOURCE_DIR}/cockatrice/src/client/crash/crash_reporter.cpp + crash_reporter_test.cpp + LIBS + libcockatrice_settings + INCLUDE_DIRS + ${CMAKE_SOURCE_DIR}/cockatrice/src + DEFINITIONS + COCKATRICE_WITH_CRASH_REPORTING=0 + ) endif() add_subdirectory(card_zone_algorithms) @@ -404,10 +537,16 @@ add_subdirectory(carddatabase) add_subdirectory(deck_list_model) add_subdirectory(deck_list_zones) add_subdirectory(loading_from_clipboard) +add_subdirectory(model_contract) add_subdirectory(movecard_tests) add_subdirectory(oracle) add_subdirectory(settings) +# libcockatrice_game only exists when the client is built. +if(WITH_CLIENT) + add_subdirectory(game) +endif() + # theme_migration_test compiles client sources, whose headers are only reachable from a client build. if(WITH_CLIENT) add_subdirectory(theme_migration) diff --git a/tests/card_zone_algorithms/CMakeLists.txt b/tests/card_zone_algorithms/CMakeLists.txt index 945f8a0ed..2418092de 100644 --- a/tests/card_zone_algorithms/CMakeLists.txt +++ b/tests/card_zone_algorithms/CMakeLists.txt @@ -1,4 +1,4 @@ cockatrice_add_test( NAME card_zone_algorithms_test SOURCES card_zone_algorithms_test.cpp INCLUDE_DIRS - ${CMAKE_SOURCE_DIR}/cockatrice/src/game/zones + ${CMAKE_SOURCE_DIR}/libcockatrice_game/libcockatrice/game/zones ) diff --git a/tests/card_zone_algorithms/card_zone_algorithms_test.cpp b/tests/card_zone_algorithms/card_zone_algorithms_test.cpp index 4f5282235..3d4baa430 100644 --- a/tests/card_zone_algorithms/card_zone_algorithms_test.cpp +++ b/tests/card_zone_algorithms/card_zone_algorithms_test.cpp @@ -16,7 +16,6 @@ struct MockCard bool cardRefWasCalled = false; bool resetStateCalled = false; bool resetStateKeepAnnotations = false; - bool visibleSet = false; void setId(int id) { @@ -35,11 +34,6 @@ struct MockCard resetStateCalled = true; resetStateKeepAnnotations = keepAnnotations; } - - void setVisible(bool visible) - { - visibleSet = visible; - } }; class MockCardList @@ -109,7 +103,6 @@ TEST_F(AddCardAlgorithmTest, ContentsKnownPreservesIdentity) EXPECT_FALSE(card.idWasCalled); EXPECT_FALSE(card.cardRefWasCalled); - EXPECT_TRUE(card.visibleSet); } TEST_F(AddCardAlgorithmTest, ContentsUnknownClearsIdentity) diff --git a/tests/carddatabase/CMakeLists.txt b/tests/carddatabase/CMakeLists.txt index 22171a9e8..7d83640a1 100644 --- a/tests/carddatabase/CMakeLists.txt +++ b/tests/carddatabase/CMakeLists.txt @@ -35,6 +35,21 @@ if(WITH_ORACLE OR WITH_CLIENT) LIBS libcockatrice_filters ) + + # ------------------------ + # Visual Display Model Test + # ------------------------ + cockatrice_add_test( + NAME + visual_display_model_test + SOURCES + ${VERSION_STRING_CPP} + visual_display_model_test.cpp + mocks.cpp + LIBS + libcockatrice_card + libcockatrice_models + ) endif() # ------------------------ diff --git a/tests/carddatabase/carddatabase_test.cpp b/tests/carddatabase/carddatabase_test.cpp index 1b48d934b..22cbe9074 100644 --- a/tests/carddatabase/carddatabase_test.cpp +++ b/tests/carddatabase/carddatabase_test.cpp @@ -1,6 +1,7 @@ #include "libcockatrice/card/database/card_database.h" #include "libcockatrice/card/database/card_database_loader.h" #include "libcockatrice/card/database/card_database_querier.h" +#include "libcockatrice/card/printing/exact_card.h" #include "libcockatrice/card/set/card_set.h" #include "libcockatrice/card/set/card_set_list.h" #include "test_card_database_path_provider.h" @@ -16,19 +17,23 @@ #include #include #include +#include #include #include #include #include +#include #include #include #include #include #include +#include #include #include #include #include +#include #include namespace @@ -60,6 +65,94 @@ TEST(CardDatabaseTest, LoadXml) ASSERT_EQ(NotLoaded, db->getLoadStatus()) << "Incorrect status after clear"; } +TEST(CardDatabaseTest, LazyPropertiesHashStreamsBlobAccessors) +{ + QHash props; + props.insert("manacost", "1R"); + props.insert("colors", "R"); + props.insert("format-standard", "legal"); + + LazyPropertiesHash lazy = LazyPropertiesHash::serializedFrom(props); + const QByteArray blob = lazy.serialized(); + + EXPECT_EQ(3, lazy.size()); + EXPECT_FALSE(lazy.isEmpty()); + EXPECT_TRUE(lazy.contains("colors")); + EXPECT_FALSE(lazy.contains("missing")); + EXPECT_EQ("1R", lazy.value("manacost")); + EXPECT_EQ(QString(), lazy.value("missing")); + EXPECT_EQ(blob, lazy.serialized()) << "an untouched blob must be passed through unchanged"; + + QStringList keys = lazy.keys(); + keys.sort(); + QStringList expectedKeys = props.keys(); + expectedKeys.sort(); + EXPECT_EQ(expectedKeys, keys); + + // Mutating materializes the hash, after which every accessor keeps working. + EXPECT_TRUE(lazy.insert("loyalty", "3")); + EXPECT_FALSE(lazy.insert("loyalty", "3")); + EXPECT_EQ(4, lazy.size()); + EXPECT_EQ("3", lazy.value("loyalty")); + EXPECT_TRUE(lazy.contains("loyalty")); + + LazyPropertiesHash roundTrip(lazy.serialized()); + EXPECT_EQ(4, roundTrip.size()); + EXPECT_EQ("3", roundTrip.value("loyalty")); + EXPECT_EQ("legal", roundTrip.value("format-standard")); +} + +TEST(CardDatabaseTest, GuessCardWithProviderIdOnUnknownCardReturnsEmpty) +{ + // An unknown name plus a providerId used to dereference the missing card + // inside findPrintingWithId(); the lookup must come back empty instead. + NoopCardPreferenceProvider prefs; + TestCardDatabasePathProvider pathProvider; + NoopCardSetPriorityController controller; + CardDatabase db(nullptr, &prefs, &pathProvider, &controller); + + const ExactCard guessed = db.query()->guessCard({"Not a Card", "card_Not a Card_fixture"}); + + EXPECT_TRUE(guessed.isEmpty()); + EXPECT_FALSE(guessed); +} + +TEST(CardDatabaseTest, FindPrintingWithIdOnNullCardInfoReturnsEmpty) +{ + NoopCardPreferenceProvider prefs; + TestCardDatabasePathProvider pathProvider; + NoopCardSetPriorityController controller; + CardDatabase db(nullptr, &prefs, &pathProvider, &controller); + + EXPECT_TRUE(db.query()->findPrintingWithId(nullptr, "card_Not a Card_fixture").isEmpty()); +} + +TEST(CardInfoTest, ClearDirectRelationsDropsRawPointers) +{ + auto *forward = new CardRelation("Child"); + auto *reverse = new CardRelation("Parent"); + auto *toMe = new CardRelation("Child"); + + auto card = CardInfo::newInstance("Parent", "", false, QHash(), {forward}, {reverse}, + SetToPrintingsMap(), CardInfo::UiAttributes()); + card->addReverseRelatedCards2Me(toMe); + ASSERT_EQ(1, card->getRelatedCards().size()); + ASSERT_EQ(1, card->getReverseRelatedCards().size()); + ASSERT_EQ(1, card->getReverseRelatedCards2Me().size()); + ASSERT_EQ(2, card->getAllRelatedCards().size()); + + card->clearDirectRelations(); + + EXPECT_TRUE(card->getRelatedCards().isEmpty()); + EXPECT_TRUE(card->getReverseRelatedCards().isEmpty()); + EXPECT_TRUE(card->getReverseRelatedCards2Me().isEmpty()); + EXPECT_TRUE(card->getAllRelatedCards().isEmpty()); + + delete forward; + delete reverse; + delete toMe; +} + TEST(CardDatabaseTest, Xml4LocalizedDataRoundTrip) { NoopCardSetPriorityController controller; @@ -77,6 +170,8 @@ TEST(CardDatabaseTest, Xml4LocalizedDataRoundTrip) CardInfo::newInstance("Lightning Bolt", "Deal 3 damage.", false, {}, {}, {}, setsInfo, attributes); card->setLocalizedName("de", "Blitzschlag"); card->setLocalizedText("de", "Blitzschlag fügt 3 Schadenspunkte zu."); + card->setProperty("format-standard", "legal"); + card->setProperty("colors", "R"); SetNameMap sets; sets.insert("TST", set); @@ -100,6 +195,13 @@ TEST(CardDatabaseTest, Xml4LocalizedDataRoundTrip) ASSERT_EQ(loaded->getLocalizedName("de"), "Blitzschlag"); ASSERT_EQ(loaded->getLocalizedText("de"), "Blitzschlag fügt 3 Schadenspunkte zu."); ASSERT_EQ(loaded->getLocalizedText("fr"), "Deal 3 damage."); + ASSERT_EQ(loaded->getProperty("format-standard"), "legal"); + ASSERT_TRUE(loaded->hasProperty("colors")); + ASSERT_EQ(loaded->getProperties().size(), 2); + + const QList printings = loaded->getSets().value("TST"); + ASSERT_FALSE(printings.isEmpty()); + ASSERT_EQ(printings.first().getProperty("manacost"), "1R"); } /** @@ -508,6 +610,110 @@ TEST_F(CardDatabaseCacheInvalidationTest, MarkAllSetsAsKnownIsBatched) // Ignored sets keep their known state but are enabled. EXPECT_TRUE(controller.isEnabled("XXX")); } + +TEST_F(CardDatabaseCacheInvalidationTest, ReloadReleasesPreviousSnapshot) +{ + NoopCardPreferenceProvider prefs; + SandboxPathProvider pathProvider(root); + CardDatabase db(nullptr, &prefs, &pathProvider, &controller); + + db.loadCardDatabases(); + CardInfoPtr card = db.getCardList().value("Cat"); + ASSERT_FALSE(card.isNull()); + QWeakPointer weakCard(card); + card.clear(); + + db.reloadCardDatabasesAndNotify(); + + // CardSet holds strong CardInfoPtrs and CardInfo holds the CardSetPtr through its printings; if that + // cycle is not broken, the previous snapshot survives the reload. + EXPECT_TRUE(weakCard.isNull()) << "a reload must release the previous card snapshot"; +} + +TEST_F(CardDatabaseCacheInvalidationTest, ClearReleasesCards) +{ + NoopCardPreferenceProvider prefs; + SandboxPathProvider pathProvider(root); + CardDatabase db(nullptr, &prefs, &pathProvider, &controller); + + db.loadCardDatabases(); + CardInfoPtr card = db.getCardList().value("Cat"); + ASSERT_FALSE(card.isNull()); + QWeakPointer weakCard(card); + card.clear(); + + db.clear(); + + EXPECT_TRUE(weakCard.isNull()) << "clear() must release cards held by set membership"; +} + +TEST_F(CardDatabaseCacheInvalidationTest, RemoveCardDetachesItFromItsSet) +{ + // removeCard() is the one teardown path a caller can trigger for a single card (the token editor + // uses it). Without the detach the set's strong reference keeps the removed card alive forever. + NoopCardPreferenceProvider prefs; + SandboxPathProvider pathProvider(root); + CardDatabase db(nullptr, &prefs, &pathProvider, &controller); + + db.loadCardDatabases(); + CardInfoPtr card = db.getCardList().value("Cat"); + ASSERT_FALSE(card.isNull()); + const CardSetPtr catSet = db.getSet("CAT"); + ASSERT_TRUE(catSet->contains(card)); + + db.removeCard(card); + + EXPECT_FALSE(catSet->contains(card)) << "removeCard() must detach the card from its set"; + QWeakPointer weakCard(card); + card.clear(); + EXPECT_TRUE(weakCard.isNull()) << "a removed card must not stay alive through its set"; +} + +TEST_F(CardDatabaseCacheInvalidationTest, RemoveSelfPointerStillNotifiesDatabaseCard) +{ + // cardInfoChanged() used to be emitted through a self pointer stored on the card. Without it the + // card must still notify its observers whenever its properties change. + NoopCardPreferenceProvider prefs; + SandboxPathProvider pathProvider(root); + CardDatabase db(nullptr, &prefs, &pathProvider, &controller); + + db.loadCardDatabases(); + const CardInfoPtr card = db.getCardList().value("Cat"); + ASSERT_FALSE(card.isNull()); + QSignalSpy spy(card.data(), &CardInfo::cardInfoChanged); + + card->setProperty("manacost", "1G"); + + ASSERT_EQ(spy.count(), 1) << "a property change on a database card must still emit cardInfoChanged"; + const QList arguments = spy.takeFirst(); + ASSERT_EQ(arguments.size(), 1); + EXPECT_EQ(arguments.first().value().data(), card.data()) + << "the signal must carry the card that changed"; +} + +TEST_F(CardDatabaseCacheInvalidationTest, RemoveSelfPointerCloneNotifiesAsItself) +{ + // A clone owns its own shared reference, so its changes must be reported as the clone's, never the + // original's: decks and views listening to the original must not be invalidated by a clone edit. + NoopCardPreferenceProvider prefs; + SandboxPathProvider pathProvider(root); + CardDatabase db(nullptr, &prefs, &pathProvider, &controller); + + db.loadCardDatabases(); + const CardInfoPtr original = db.getCardList().value("Cat"); + ASSERT_FALSE(original.isNull()); + const CardInfoPtr clone = original->clone(); + QSignalSpy originalSpy(original.data(), &CardInfo::cardInfoChanged); + QSignalSpy cloneSpy(clone.data(), &CardInfo::cardInfoChanged); + + clone->setProperty("manacost", "1G"); + + ASSERT_EQ(cloneSpy.count(), 1) << "the clone must notify its own observers"; + EXPECT_EQ(cloneSpy.first().first().value().data(), clone.data()) + << "the clone's signal must carry the clone"; + EXPECT_EQ(originalSpy.count(), 0) << "the clone must not notify the original's observers"; +} + } // namespace int main(int argc, char **argv) diff --git a/tests/carddatabase/filter_string_test.cpp b/tests/carddatabase/filter_string_test.cpp index 95225e28c..e561db590 100644 --- a/tests/carddatabase/filter_string_test.cpp +++ b/tests/carddatabase/filter_string_test.cpp @@ -99,6 +99,17 @@ QUERY(SetCodeWrongSet2, doctor, "set:cat", false) QUERY(BracketNextToUnquotedString, cat, "(o:woof OR o:meow)", true) +// Spaces around the colon are optional for all field queries (e.g. "o: flying") +QUERY(OracleSpaceAfterColon, cat, "o: meow", true) +QUERY(OracleSpaceBeforeColon, cat, "o :meow", true) +QUERY(OracleSpacesAroundColon, cat, "o : meow", true) +QUERY(OracleSpaceAfterColonNoMatch, cat, "o: woof", false) +QUERY(TypeSpaceAfterColon, cat, "t: creature", true) +QUERY(FieldSpaceAfterColon, cat, "pt: \"3/3\"", true) +QUERY(ColorSpaceAfterColon, cat, "c: g", true) +QUERY(ColorSpaceBeforeOperator, cat, "c !g", true) +QUERY(SetSpaceAfterColon, cat, "set: cat", true) + CardInfoPtr localizedCat() { CardInfoPtr localized = CardInfo::newInstance("Cat", "Meow!", false, {}, {}, {}, {}, {}); @@ -107,6 +118,22 @@ CardInfoPtr localizedCat() return localized; } +CardInfoPtr greenIdentityCard() +{ + return CardInfo::newInstance("Identity Cat", "", false, {{"colors", "G"}, {"coloridentity", "G"}}, {}, {}, {}, {}); +} + +// Whitespace belongs around the separator, never inside an operator token: "c i: g" must not +// be read as the color-identity operator "ci" (it is a generic "c" AND a field query on "i"). +TEST_F(CardQuery, ColorIdentityOperatorIsNotSplitByWhitespace) +{ + const CardData identityCard = greenIdentityCard(); + ASSERT_TRUE(FilterString("ci:g").check(identityCard)); + ASSERT_TRUE(FilterString("ci: g").check(identityCard)); + ASSERT_TRUE(FilterString("ci : g").check(identityCard)); + ASSERT_FALSE(FilterString("c i: g").check(identityCard)); +} + TEST_F(CardQuery, SearchLanguageEnglishMatchesOnlyEnglish) { const CardData localized = localizedCat(); @@ -136,6 +163,13 @@ TEST_F(CardQuery, SearchLanguageBothMatchesEitherLanguage) ASSERT_TRUE(FilterString("Kater", CardSearchLanguage{"de", SearchLanguageMode::Both}).check(localized)); } +TEST_F(CardQuery, SearchLanguageBothMatchesOracleTextWithSpaceAfterColon) +{ + const CardData localized = localizedCat(); + ASSERT_TRUE(FilterString("o: Meow", CardSearchLanguage{"de", SearchLanguageMode::Both}).check(localized)); + ASSERT_TRUE(FilterString("o: miaut", CardSearchLanguage{"de", SearchLanguageMode::Both}).check(localized)); +} + TEST_F(CardQuery, SearchLanguageIsBoundPerInstance) { const CardData localized = localizedCat(); diff --git a/tests/carddatabase/load_benchmark.cpp b/tests/carddatabase/load_benchmark.cpp index 5492b9531..da077a50f 100644 --- a/tests/carddatabase/load_benchmark.cpp +++ b/tests/carddatabase/load_benchmark.cpp @@ -125,7 +125,7 @@ RunResult runLoad(CardDatabase *db, ChurnCounters &counters) result.churn.modelResets = counters.modelResets - before.modelResets; for (const CardInfoPtr &card : db->getCardList()) { - result.propTotal += card->getPropertiesHash().size(); + result.propTotal += card->getProperties().size(); if (!card->getProperty("colors").isEmpty()) { ++result.propsQueried; } diff --git a/tests/carddatabase/visual_display_model_test.cpp b/tests/carddatabase/visual_display_model_test.cpp new file mode 100644 index 000000000..5fa554bb3 --- /dev/null +++ b/tests/carddatabase/visual_display_model_test.cpp @@ -0,0 +1,188 @@ +/** + * @file visual_display_model_test.cpp + * @brief Tests for the flattened model behind the visual card database grid. + */ + +#include "card_database_display_model.h" +#include "card_database_model.h" +#include "card_database_visual_display_model.h" +#include "libcockatrice/card/card_info.h" +#include "libcockatrice/card/database/card_database.h" +#include "libcockatrice/card/database/card_database_querier.h" +#include "libcockatrice/card/printing/exact_card.h" +#include "libcockatrice/card/printing/printing_info.h" +#include "libcockatrice/card/set/card_set.h" +#include "libcockatrice/interfaces/noop_card_preference_provider.h" +#include "libcockatrice/interfaces/noop_card_set_priority_controller.h" +#include "test_card_database_path_provider.h" + +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +// GTest's main() creates no QCoreApplication, which QTimer needs for its event loop. +const QCoreApplication &application() +{ + static int argc = 1; + static char executable[] = "visual_display_model_test"; + static char *argv[] = {executable, nullptr}; + static const QCoreApplication app(argc, argv); + return app; +} + +/// Lets the rebuild timer fire, as it would in the running client. +void waitForRebuild() +{ + QEventLoop loop; + QTimer::singleShot(100, &loop, &QEventLoop::quit); + loop.exec(); +} + +class CardDatabaseVisualDisplayModelTest : public ::testing::Test +{ +protected: + void SetUp() override + { + (void)application(); + + db = std::make_unique(nullptr, new NoopCardPreferenceProvider(), + new TestCardDatabasePathProvider(), new NoopCardSetPriorityController()); + db->loadCardDatabases(); + + cardModel = std::make_unique(db.get(), false); + displayModel = std::make_unique(); + displayModel->setSourceModel(cardModel.get()); + visualModel = std::make_unique(displayModel.get(), db->query()); + visualModel->refresh(); + } + + /** + * @brief Builds a card with @p printingCount printings in each named set. + * @param printingCountPerSet Set short name to the number of printings in that set. + */ + [[nodiscard]] CardInfoPtr makeCard(const QString &name, const QMap &printingCountPerSet) + { + SetToPrintingsMap setsInfo; + for (auto it = printingCountPerSet.constBegin(); it != printingCountPerSet.constEnd(); ++it) { + CardSetPtr set = CardSet::newInstance(&setController, it.key(), it.key() + " test set", "expansion", + QDate(), CardSet::PriorityPrimary); + for (int printing = 0; printing < it.value(); ++printing) { + QHash properties; + properties["uuid"] = QString("%1-%2-%3").arg(it.key(), name).arg(printing); + setsInfo[it.key()].append(PrintingInfo(set, LazyPropertiesHash(properties))); + } + } + + CardInfo::UiAttributes attributes = {.tableRow = 1}; + return CardInfo::newInstance(name, "", false, {}, {}, {}, setsInfo, attributes); + } + + NoopCardSetPriorityController setController; + std::unique_ptr db; + std::unique_ptr cardModel; + std::unique_ptr displayModel; + std::unique_ptr visualModel; +}; + +TEST_F(CardDatabaseVisualDisplayModelTest, ExposesTheCardInfoBehindAProxyRow) +{ + ASSERT_GT(displayModel->rowCount(), 0); + + const QVariant info = displayModel->data(displayModel->index(0, CardDatabaseModel::NameColumn), + CardDatabaseDisplayModel::CardInfoRole); + ASSERT_TRUE(info.isValid()); + EXPECT_FALSE(info.value().isNull()); +} + +TEST_F(CardDatabaseVisualDisplayModelTest, FlattensOneRowPerProxyCardWithoutSetFilters) +{ + ASSERT_GT(displayModel->rowCount(), 0); + EXPECT_EQ(displayModel->rowCount(), visualModel->rowCount()); + + for (int row = 0; row < visualModel->rowCount(); ++row) { + const ExactCard card = visualModel->cardAt(row); + ASSERT_TRUE(card) << "row " << row; + EXPECT_EQ( + displayModel->data(displayModel->index(row, CardDatabaseModel::NameColumn), Qt::DisplayRole).toString(), + card.getName()) + << "row " << row; + } +} + +TEST_F(CardDatabaseVisualDisplayModelTest, ShowsOneRowPerCardWhenNoSetIsFiltered) +{ + const int rowsBefore = visualModel->rowCount(); + + db->addCard(makeCard("Multi", {{"TST", 2}, {"OTH", 1}})); + visualModel->refresh(); + + EXPECT_EQ(rowsBefore + 1, visualModel->rowCount()); + EXPECT_EQ("Multi", visualModel->cardAt(visualModel->rowCount() - 1).getName()); +} + +TEST_F(CardDatabaseVisualDisplayModelTest, ShowsEveryPrintingOfAFilteredSet) +{ + db->addCard(makeCard("Multi", {{"TST", 2}, {"OTH", 1}})); + visualModel->refresh(); + + visualModel->setSetFilterTerms({"TST"}); + + ASSERT_EQ(2, visualModel->rowCount()); + for (int row = 0; row < visualModel->rowCount(); ++row) { + const ExactCard card = visualModel->cardAt(row); + ASSERT_TRUE(card) << "row " << row; + EXPECT_EQ("Multi", card.getName()); + ASSERT_FALSE(card.getPrinting().isEmpty()) << "row " << row; + EXPECT_EQ("TST", card.getPrinting().getSet()->getShortName()) << "row " << row; + } + EXPECT_NE(visualModel->cardAt(0).getPrinting(), visualModel->cardAt(1).getPrinting()); +} + +TEST_F(CardDatabaseVisualDisplayModelTest, HidesCardsWithoutAPrintingInTheFilteredSets) +{ + visualModel->setSetFilterTerms({"NO SUCH SET"}); + + EXPECT_EQ(0, visualModel->rowCount()); +} + +TEST_F(CardDatabaseVisualDisplayModelTest, RestoresOneRowPerCardWhenTheSetTermsClear) +{ + db->addCard(makeCard("Multi", {{"TST", 2}, {"OTH", 1}})); + visualModel->refresh(); + const int rowsBefore = visualModel->rowCount(); + + visualModel->setSetFilterTerms({"TST"}); + ASSERT_EQ(2, visualModel->rowCount()); + + visualModel->setSetFilterTerms({}); + EXPECT_EQ(rowsBefore, visualModel->rowCount()); +} + +TEST_F(CardDatabaseVisualDisplayModelTest, RebuildsByItselfWhenTheProxyGainsARow) +{ + const int rowsBefore = visualModel->rowCount(); + + db->addCard(makeCard("Late", {{"TST", 1}})); + waitForRebuild(); + + EXPECT_EQ(rowsBefore + 1, visualModel->rowCount()); +} + +TEST_F(CardDatabaseVisualDisplayModelTest, ReportsTheCardNameForDisplayAndAccessibleText) +{ + ASSERT_GT(visualModel->rowCount(), 0); + const QString expected = visualModel->cardAt(0).getName(); + + EXPECT_EQ(expected, visualModel->data(visualModel->index(0), Qt::DisplayRole).toString()); + EXPECT_EQ(expected, visualModel->data(visualModel->index(0), Qt::AccessibleTextRole).toString()); + EXPECT_FALSE(visualModel->data(QModelIndex(), Qt::DisplayRole).isValid()); +} +} // namespace diff --git a/tests/client_login_status_test.cpp b/tests/client_login_status_test.cpp new file mode 100644 index 000000000..d8ea63550 --- /dev/null +++ b/tests/client_login_status_test.cpp @@ -0,0 +1,116 @@ +/** @file client_login_status_test.cpp + * @brief The login response state machine must keep the session a forced password change needs. + * @ingroup Tests + */ + +#include "abstract_client.h" +#include "libcockatrice/interfaces/interface_network_settings_provider.h" +#include "libcockatrice/protocol/pb/response.pb.h" +#include "libcockatrice/protocol/pb/response_login.pb.h" +#include "remote_client.h" + +#include +#include +#include +#include + +namespace +{ + +class StubNetworkSettingsProvider : public INetworkSettingsProvider +{ +public: + QString getClientID() override + { + return {}; + } + [[nodiscard]] int getTimeOut() const override + { + return 1000; + } + [[nodiscard]] int getKeepAlive() const override + { + return 60; + } + [[nodiscard]] bool getNotifyAboutUpdates() const override + { + return false; + } + void setKnownMissingFeatures(const QString &) override + { + } + QString getKnownMissingFeatures() override + { + return {}; + } +}; + +/** + * @brief Feeds a login response to RemoteClient without a socket. + * + * `loginResponse` is the slot the socket thread runs for the login command's answer, so + * invoking it directly exercises the status transition that decides whether `readData()` + * tears the connection down right after it. + */ +class ClientLoginStatusTest : public ::testing::Test +{ +protected: + void SetUp() override + { + client = new RemoteClient(nullptr, &provider); + } + + void TearDown() override + { + delete client; + client = nullptr; + } + + void deliverLoginResponse(Response::ResponseCode code) + { + Response response; + response.set_response_code(code); + response.MutableExtension(Response_Login::ext); + ASSERT_TRUE( + QMetaObject::invokeMethod(client, "loginResponse", Qt::DirectConnection, Q_ARG(Response, response))); + } + + StubNetworkSettingsProvider provider; + RemoteClient *client = nullptr; +}; + +/** + * @brief A required password change is not a failed login. + * + * The server keeps the session open and accepts only ping and Command_AccountPassword until + * the password is replaced. StatusDisconnecting would make readData() close the socket right + * after this slot returns - before the dialog that collects the new password (queued to the + * UI thread) can send anything - and the pending command would never be answered because the + * ping timer that times it out dies with the connection. StatusLoggedIn would start the + * normal post-login flow for a session that cannot serve it. + */ +TEST_F(ClientLoginStatusTest, PasswordChangeRequiredKeepsTheSessionOpen) +{ + deliverLoginResponse(Response::RespPasswordChangeRequired); + + EXPECT_EQ(client->getStatus(), StatusPasswordChangeRequired); + EXPECT_NE(client->getStatus(), StatusDisconnecting); + EXPECT_NE(client->getStatus(), StatusLoggedIn); +} + +/** @brief Every other login error still ends the connection, as it did before. */ +TEST_F(ClientLoginStatusTest, OtherLoginErrorsDisconnect) +{ + deliverLoginResponse(Response::RespWrongPassword); + + EXPECT_EQ(client->getStatus(), StatusDisconnecting); +} + +} // namespace + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/crash_reporter_test.cpp b/tests/crash_reporter_test.cpp new file mode 100644 index 000000000..e44a34a60 --- /dev/null +++ b/tests/crash_reporter_test.cpp @@ -0,0 +1,427 @@ +#include "client/crash/crash_reporter.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + +TEST(CrashReporterTest, SanitizeLogLineStripsAnsiColorCodes) +{ + ASSERT_EQ(CrashReporter::sanitizeLogLine(QStringLiteral("\033[32mhello\033[0m")), QStringLiteral("hello")); + ASSERT_EQ(CrashReporter::sanitizeLogLine(QStringLiteral("\033[1;31mfatal\033[0m")), QStringLiteral("fatal")); + ASSERT_EQ(CrashReporter::sanitizeLogLine(QStringLiteral("plain line")), QStringLiteral("plain line")); +} + +TEST(CrashReporterTest, RedactCockatriceUrlsKeepsSchemeAndHostOnly) +{ + ASSERT_EQ( + CrashReporter::redactCockatriceUrls(QStringLiteral("open cockatrice://opendeck?token=secret&user=me now")), + QStringLiteral("open cockatrice://opendeck now")); +} + +TEST(CrashReporterTest, RedactCockatriceUrlsIsIdempotent) +{ + // A secret-free URL must not match again after redaction (regression: this hung forever). + ASSERT_EQ(CrashReporter::redactCockatriceUrls(QStringLiteral("open cockatrice://opendeck now")), + QStringLiteral("open cockatrice://opendeck now")); +} + +TEST(CrashReporterTest, RedactCockatriceUrlsLeavesOtherTextAlone) +{ + ASSERT_EQ(CrashReporter::redactCockatriceUrls(QStringLiteral("https://cockatrice.github.io/?token=secret")), + QStringLiteral("https://cockatrice.github.io/?token=secret")); + ASSERT_EQ(CrashReporter::sanitizeLogLine(QStringLiteral("")), QStringLiteral("")); +} + +TEST(CrashReporterTest, InitSkippedWithoutDsn) +{ + CrashReporter reporter; + reporter.init(QString(), QStringLiteral("3.1.2"), QDir::tempPath() + QStringLiteral("/cockatrice-crash-test"), + PersonalSettings::CrashReportingMode::Ask); + ASSERT_FALSE(reporter.isReady()); + reporter.shutdown(); + ASSERT_FALSE(reporter.isReady()); +} + +TEST(CrashReporterTest, InitSkippedWhenNeverSend) +{ + CrashReporter reporter; + reporter.init(QStringLiteral("https://key@sentry.io/1"), QStringLiteral("3.1.2"), + QDir::tempPath() + QStringLiteral("/cockatrice-crash-test"), + PersonalSettings::CrashReportingMode::NeverSend); + ASSERT_FALSE(reporter.isReady()); + reporter.shutdown(); +} + +// The init() tests above compile to no-ops when the SDK is off, so they pass +// regardless of the gating; the gating decision itself is covered here, in both +// build modes, through the SDK-independent helper init() consults. +TEST(CrashReporterTest, ReportingEnabledGating) +{ + using Mode = PersonalSettings::CrashReportingMode; + const QString dsn = QStringLiteral("https://key@sentry.io/1"); + ASSERT_FALSE(CrashReporter::isReportingEnabled(QString(), Mode::Ask)); + ASSERT_FALSE(CrashReporter::isReportingEnabled(QString(), Mode::AlwaysSend)); + ASSERT_FALSE(CrashReporter::isReportingEnabled(QString(), Mode::NeverSend)); + ASSERT_TRUE(CrashReporter::isReportingEnabled(dsn, Mode::Ask)); + ASSERT_TRUE(CrashReporter::isReportingEnabled(dsn, Mode::AlwaysSend)); + ASSERT_FALSE(CrashReporter::isReportingEnabled(dsn, Mode::NeverSend)); +} + +TEST(CrashReporterTest, IsDsnWellFormedAcceptsWhatTheBuildAcceptsAndRejectsUiLinks) +{ + ASSERT_TRUE(CrashReporter::isDsnWellFormed(QStringLiteral("https://abc123@o0.ingest.sentry.io/1234567"))); + ASSERT_TRUE(CrashReporter::isDsnWellFormed(QStringLiteral("http://key@localhost:9000/42"))); + // Unanchored like cmake/CrashReporting.cmake's MATCHES, so a DSN the build baked is never + // rejected here: a trailing path still matches. + ASSERT_TRUE(CrashReporter::isDsnWellFormed(QStringLiteral("https://key@host.example/7/extra"))); + // The project-settings URL is the mistake a tester actually makes. + ASSERT_FALSE(CrashReporter::isDsnWellFormed( + QStringLiteral("https://sentry.io/settings/cockatrice/projects/cockatrice-desktop/keys/"))); + ASSERT_FALSE(CrashReporter::isDsnWellFormed(QStringLiteral("https://key@host.example/not-a-number"))); + ASSERT_FALSE(CrashReporter::isDsnWellFormed(QStringLiteral("o0.ingest.sentry.io/1234567"))); + ASSERT_FALSE(CrashReporter::isDsnWellFormed(QString())); +} + +TEST(CrashReporterTest, DescribeDsnNamesHostAndProjectWithoutTheKey) +{ + const QString described = + CrashReporter::describeDsn(QStringLiteral("https://supersecretkey@o0.ingest.sentry.io/1234567")); + ASSERT_EQ(described, QStringLiteral("o0.ingest.sentry.io (project 1234567)")); + ASSERT_FALSE(described.contains(QStringLiteral("supersecretkey"))); + ASSERT_TRUE(CrashReporter::describeDsn(QStringLiteral("not a dsn")).isEmpty()); + ASSERT_TRUE(CrashReporter::describeDsn(QString()).isEmpty()); +} + +TEST(CrashReporterTest, DescribeOriginMismatchReportsOnlyRealDestinationChanges) +{ + const QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + const QString envelopePath = dir.filePath(QStringLiteral("crash.envelope")); + QFile file(envelopePath); + ASSERT_TRUE(file.open(QIODevice::WriteOnly)); + file.write(QByteArrayLiteral("{\"event_id\":\"x\",\"dsn\":\"https://keyA@hostA.example/111\"}\n")); + file.write(QByteArrayLiteral("{\"type\":\"event\",\"length\":2}\n{}\n")); + file.close(); + + const QString described = + CrashReporter::describeOriginMismatch(envelopePath, QStringLiteral("https://keyB@hostB.example/222")); + ASSERT_FALSE(described.isEmpty()); + ASSERT_TRUE(described.contains(QStringLiteral("hostA.example"))); + ASSERT_TRUE(described.contains(QStringLiteral("hostB.example"))); + ASSERT_FALSE(described.contains(QStringLiteral("keyA"))); + ASSERT_FALSE(described.contains(QStringLiteral("keyB"))); + + // A different key for the SAME host and project is not a routing change and must stay quiet. + ASSERT_TRUE( + CrashReporter::describeOriginMismatch(envelopePath, QStringLiteral("https://otherkey@hostA.example/111")) + .isEmpty()); + // An unreadable envelope, a header without a dsn, or no current dsn are all silent. + ASSERT_TRUE(CrashReporter::describeOriginMismatch(dir.filePath(QStringLiteral("absent.envelope")), + QStringLiteral("https://keyB@hostB.example/222")) + .isEmpty()); + ASSERT_TRUE(CrashReporter::describeOriginMismatch(envelopePath, QString()).isEmpty()); + const QString headerlessPath = dir.filePath(QStringLiteral("no-dsn.envelope")); + QFile headerless(headerlessPath); + ASSERT_TRUE(headerless.open(QIODevice::WriteOnly)); + headerless.write(QByteArrayLiteral("{\"event_id\":\"y\"}\n")); + headerless.close(); + ASSERT_TRUE(CrashReporter::describeOriginMismatch(headerlessPath, QStringLiteral("https://keyB@hostB.example/222")) + .isEmpty()); +} + +TEST(CrashReporterTest, ShutdownWithoutInitIsSafe) +{ + CrashReporter reporter; + ASSERT_FALSE(reporter.isReady()); + reporter.shutdown(); + ASSERT_FALSE(reporter.isReady()); +} + +TEST(CrashReporterTest, ConsentAndSendWithoutDsnAreSafeNoOps) +{ + CrashReporter reporter; + reporter.init(QString(), QStringLiteral("3.1.2"), QDir::tempPath() + QStringLiteral("/cockatrice-crash-test"), + PersonalSettings::CrashReportingMode::Ask); + ASSERT_FALSE(reporter.isReady()); + reporter.giveConsent(); + reporter.revokeConsent(); + ASSERT_FALSE(reporter.isReady()); + ASSERT_FALSE(reporter.sendReport(QStringLiteral("preview"))); + ASSERT_FALSE(reporter.sendReport(QString())); + reporter.shutdown(); + ASSERT_FALSE(reporter.isReady()); +} + +namespace +{ + +QByteArray readFileBytes(const QString &path) +{ + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + return {}; + } + return file.readAll(); +} + +bool writeFileBytes(const QString &path, const QByteArray &content) +{ + QFile file(path); + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { + return false; + } + return file.write(content) == content.size(); +} + +QString uuidOf(char marker) +{ + // 36 chars, 4 hyphens — structurally a UUID, content irrelevant for these tests. + return QStringLiteral("00000000-0000-4000-8000-00000000000%1").arg(marker); +} + +} // namespace + +TEST(CrashReporterTest, AppendAttachmentAppendsWellFormedItemAfterExistingContent) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + const QString path = dir.filePath(QStringLiteral("event.envelope")); + // Existing envelope content deliberately WITHOUT a trailing newline. + const QByteArray original = QByteArrayLiteral("{\"event_id\":\"x\"}\n{\"type\":\"event\",\"length\":2}\n{}"); + ASSERT_TRUE(writeFileBytes(path, original)); + + ASSERT_TRUE( + CrashReporter::appendAttachmentToEnvelope(path, QStringLiteral("crash-log.txt"), QByteArrayLiteral("hello"))); + const QByteArray expected = original + '\n' + + QByteArrayLiteral("{\"type\":\"attachment\",\"length\":5,\"attachment_length\":5," + "\"filename\":\"crash-log.txt\",\"content_type\":\"text/plain\"}\n") + + QByteArrayLiteral("hello"); + ASSERT_EQ(readFileBytes(path), expected); +} + +TEST(CrashReporterTest, AppendAttachmentOnEmptyFileHasNoLeadingNewline) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + const QString path = dir.filePath(QStringLiteral("event.envelope")); + ASSERT_TRUE(writeFileBytes(path, QByteArray())); + + ASSERT_TRUE( + CrashReporter::appendAttachmentToEnvelope(path, QStringLiteral("user-note.txt"), QByteArrayLiteral("note"))); + const QByteArray expected = QByteArrayLiteral("{\"type\":\"attachment\",\"length\":4,\"attachment_length\":4," + "\"filename\":\"user-note.txt\",\"content_type\":\"text/plain\"}\n") + + QByteArrayLiteral("note"); + ASSERT_EQ(readFileBytes(path), expected); +} + +TEST(CrashReporterTest, AppendAttachmentRejectsMissingFile) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + ASSERT_FALSE(CrashReporter::appendAttachmentToEnvelope(dir.filePath(QStringLiteral("missing.envelope")), + QStringLiteral("crash-log.txt"), QByteArrayLiteral("x"))); +} + +TEST(CrashReporterTest, AppendAttachmentLengthIsByteAccurate) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + const QString path = dir.filePath(QStringLiteral("event.envelope")); + ASSERT_TRUE(writeFileBytes(path, QByteArrayLiteral("{}"))); + + const QByteArray utf8Payload = QString::fromUtf8("héllo").toUtf8(); + ASSERT_EQ(utf8Payload.size(), 6); // 5 characters, "é" is 2 bytes + ASSERT_TRUE(CrashReporter::appendAttachmentToEnvelope(path, QStringLiteral("crash-log.txt"), utf8Payload)); + const QByteArray content = readFileBytes(path); + ASSERT_TRUE(content.contains(QByteArrayLiteral("\"length\":6"))); + ASSERT_TRUE(content.contains(QByteArrayLiteral("\"attachment_length\":6"))); + ASSERT_TRUE(content.endsWith(utf8Payload)); +} + +// The SDK's envelope re-parser frames each item's payload by its `length` header; a +// missing or wrong length ends the payload at the first newline, so the remaining log +// lines get parsed as item headers and the whole envelope is rejected (and deleted). +TEST(CrashReporterTest, AppendAttachmentKeepsMultiLinePayloadIntact) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + const QString path = dir.filePath(QStringLiteral("event.envelope")); + ASSERT_TRUE(writeFileBytes(path, QByteArrayLiteral("{}"))); + + const QByteArray payload = QByteArrayLiteral("first line\nsecond line\nthird line"); + ASSERT_TRUE(CrashReporter::appendAttachmentToEnvelope(path, QStringLiteral("crash-log.txt"), payload)); + const QByteArray content = readFileBytes(path); + ASSERT_TRUE(content.contains("\"length\":" + QByteArray::number(payload.size()))); + ASSERT_TRUE(content.contains("\"attachment_length\":" + QByteArray::number(payload.size()))); + ASSERT_TRUE(content.endsWith(payload)); +} + +TEST(CrashReporterTest, PendingEnvelopePathsMatchesSdkNamingInCacheAndRunDirs) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + const QString dbPath = dir.path(); + ASSERT_TRUE(QDir().mkpath(dbPath + QStringLiteral("/cache"))); + ASSERT_TRUE(QDir().mkpath(dbPath + QStringLiteral("/aaaa.run"))); + ASSERT_TRUE(QDir().mkpath(dbPath + QStringLiteral("/other"))); + + const QString retryName = QStringLiteral("1700000000000-0-") + uuidOf('a') + QStringLiteral(".envelope"); + const QString bareName = uuidOf('b') + QStringLiteral(".envelope"); + // Valid: retry-style cache file + bare run-dir file. + ASSERT_TRUE(writeFileBytes(dbPath + QStringLiteral("/cache/") + retryName, QByteArrayLiteral("{}"))); + ASSERT_TRUE(writeFileBytes(dbPath + QStringLiteral("/aaaa.run/") + bareName, QByteArrayLiteral("{}"))); + // Rejected: junk names and the SDK's own marker file. + ASSERT_TRUE(writeFileBytes(dbPath + QStringLiteral("/cache/") + uuidOf('c') + QStringLiteral(".envelope.bak"), + QByteArrayLiteral("{}"))); + ASSERT_TRUE(writeFileBytes(dbPath + QStringLiteral("/cache/") + QStringLiteral("random.envelope"), + QByteArrayLiteral("{}"))); + ASSERT_TRUE(writeFileBytes(dbPath + QStringLiteral("/aaaa.run/") + QStringLiteral("__sentry-crash.envelope"), + QByteArrayLiteral("{}"))); + ASSERT_TRUE(writeFileBytes(dbPath + QStringLiteral("/other/") + bareName, QByteArrayLiteral("{}"))); + + const QStringList found = CrashReporter::pendingEnvelopePaths(dbPath); + ASSERT_EQ(found.size(), 2); + ASSERT_TRUE(found.contains(dbPath + QStringLiteral("/cache/") + retryName)); + ASSERT_TRUE(found.contains(dbPath + QStringLiteral("/aaaa.run/") + bareName)); +} + +TEST(CrashReporterTest, PendingEnvelopePathsWithoutDatabaseIsEmpty) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + ASSERT_TRUE(CrashReporter::pendingEnvelopePaths(dir.filePath(QStringLiteral("no-such-db"))).isEmpty()); + ASSERT_TRUE(CrashReporter::pendingEnvelopePaths(QString()).isEmpty()); +} + +TEST(CrashReporterTest, AttachFoldsLogAndNoteIntoPendingEnvelopes) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + const QString dbPath = dir.path(); + ASSERT_TRUE(QDir().mkpath(dbPath + QStringLiteral("/cache"))); + const QString envelopePath = dbPath + QStringLiteral("/cache/") + uuidOf('d') + QStringLiteral(".envelope"); + ASSERT_TRUE(writeFileBytes(envelopePath, QByteArrayLiteral("{\"event_id\":\"x\"}\n{}\n"))); + + CrashReporter reporter; + // Empty DSN: no SDK, but init() still records the database path the dialog uses. + reporter.init(QString(), QStringLiteral("3.1.2"), dbPath, PersonalSettings::CrashReportingMode::Ask); + ASSERT_EQ(reporter.attachToPendingCrashEnvelopes(QStringLiteral("log line"), QStringLiteral("my note")), 1); + + const QByteArray content = readFileBytes(envelopePath); + ASSERT_TRUE(content.startsWith(QByteArrayLiteral("{\"event_id\":\"x\"}\n{}\n"))); + ASSERT_TRUE(content.contains(QByteArrayLiteral("\"filename\":\"crash-log.txt\""))); + ASSERT_TRUE(content.contains(QByteArrayLiteral("\nlog line"))); + ASSERT_TRUE(content.contains(QByteArrayLiteral("\"filename\":\"user-note.txt\""))); + ASSERT_TRUE(content.endsWith(QByteArrayLiteral("\nmy note"))); +} + +TEST(CrashReporterTest, AttachSkipsNoteWhenEmptyAndFindsRunDirEnvelopes) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + const QString dbPath = dir.path(); + ASSERT_TRUE(QDir().mkpath(dbPath + QStringLiteral("/bbbb.run"))); + const QString envelopePath = dbPath + QStringLiteral("/bbbb.run/") + uuidOf('e') + QStringLiteral(".envelope"); + ASSERT_TRUE(writeFileBytes(envelopePath, QByteArrayLiteral("{}"))); + + // Static overload (the pre-init AlwaysSend path) with an empty note. + ASSERT_EQ(CrashReporter::attachToPendingCrashEnvelopes(dbPath, QStringLiteral("log only"), QString()), 1); + const QByteArray content = readFileBytes(envelopePath); + ASSERT_TRUE(content.contains(QByteArrayLiteral("\"filename\":\"crash-log.txt\""))); + ASSERT_FALSE(content.contains(QByteArrayLiteral("user-note.txt"))); +} + +TEST(CrashReporterTest, BeginSessionOnEmptyDirReportsClean) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + CrashReporter reporter; + ASSERT_FALSE(reporter.beginSession(dir.path())); + reporter.endSession(); +} + +TEST(CrashReporterTest, BeginSessionIgnoresLiveConcurrentInstance) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + // A second QLockFile held in-process stands in for a live concurrent instance: same + // pid, alive, so the scan must leave it alone and report a clean start. + QLockFile liveInstance(dir.filePath(QStringLiteral("session-424242"))); + ASSERT_TRUE(liveInstance.tryLock(0)); + CrashReporter reporter; + ASSERT_FALSE(reporter.beginSession(dir.path())); + ASSERT_TRUE(QFile::exists(liveInstance.fileName())); + reporter.endSession(); + liveInstance.unlock(); +} + +TEST(CrashReporterTest, BeginSessionDetectsStaleDeadOwnerLock) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + // A well-formed lock whose owner is dead: our own pid (so the host matches) but a + // process name that cannot match, which is how QLockFile spots pid reuse. Must be + // reported unclean and cleaned up, even though the file is brand new (this is why + // the scan probes with staleLockTime 0 rather than the 30 s default). + const QString stalePath = dir.filePath(QStringLiteral("session-stale-owner")); + QFile stale(stalePath); + ASSERT_TRUE(stale.open(QIODevice::WriteOnly | QIODevice::Text)); + QTextStream out(&stale); + out << QCoreApplication::applicationPid() << '\n' + << QStringLiteral("definitely-not-the-test-binary") << '\n' + << QSysInfo::machineHostName() << '\n'; + stale.close(); + + CrashReporter reporter; + ASSERT_TRUE(reporter.beginSession(dir.path())); + ASSERT_FALSE(QFile::exists(stalePath)); + reporter.endSession(); +} + +TEST(CrashReporterTest, BeginSessionCleansGarbageFileAndReportsUnclean) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + // An unreadable lock file has no live owner by definition: report unclean and remove it. + const QString garbagePath = dir.filePath(QStringLiteral("session-garbage")); + QFile garbage(garbagePath); + ASSERT_TRUE(garbage.open(QIODevice::WriteOnly)); + garbage.write("not-a-lock-file\x00\xff", 17); + garbage.close(); + + CrashReporter reporter; + ASSERT_TRUE(reporter.beginSession(dir.path())); + ASSERT_FALSE(QFile::exists(garbagePath)); + reporter.endSession(); +} + +TEST(CrashReporterTest, BeginSessionEndSessionRoundTripReportsClean) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + CrashReporter reporter; + ASSERT_FALSE(reporter.beginSession(dir.path())); + reporter.endSession(); + ASSERT_FALSE(reporter.beginSession(dir.path())); + reporter.endSession(); +} + +} // namespace + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/deck_list_model/CMakeLists.txt b/tests/deck_list_model/CMakeLists.txt index cd7d693f3..82a1965d2 100644 --- a/tests/deck_list_model/CMakeLists.txt +++ b/tests/deck_list_model/CMakeLists.txt @@ -21,3 +21,15 @@ cockatrice_add_test( libcockatrice_card libcockatrice_deck_list ) + +cockatrice_add_test( + NAME + collator_sort_test + SOURCES + ${VERSION_STRING_CPP} + collator_sort_test.cpp + LIBS + libcockatrice_models + libcockatrice_card + libcockatrice_deck_list +) diff --git a/tests/deck_list_model/collator_sort_test.cpp b/tests/deck_list_model/collator_sort_test.cpp new file mode 100644 index 000000000..b4ed85525 --- /dev/null +++ b/tests/deck_list_model/collator_sort_test.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include +#include + +namespace +{ +TEST(CollatorSort, NumericNamesSortNaturally) +{ + QStandardItemModel source(0, 2); + const QStringList names = {"Forest 10", "Forest 2", "Forest 1"}; + for (int row = 0; row < names.size(); ++row) { + source.setItem(row, DeckListModelColumns::CARD_NAME, new QStandardItem(names.at(row))); + } + + DeckListSortFilterProxyModel proxy; + proxy.setSourceModel(&source); + proxy.setSortCriteria({"name"}); + proxy.sort(DeckListModelColumns::CARD_NAME, Qt::AscendingOrder); + + QStringList actual; + for (int row = 0; row < proxy.rowCount(); ++row) { + actual.append(proxy.index(row, DeckListModelColumns::CARD_NAME).data(Qt::DisplayRole).toString()); + } + + EXPECT_EQ(actual, (QStringList{"Forest 1", "Forest 2", "Forest 10"})); +} +} // namespace diff --git a/tests/forwarded_address_test.cpp b/tests/forwarded_address_test.cpp new file mode 100644 index 000000000..3ae2bc471 --- /dev/null +++ b/tests/forwarded_address_test.cpp @@ -0,0 +1,37 @@ +#include "forwarded_address.h" + +#include "gtest/gtest.h" + +namespace +{ + +TEST(ForwardedAddressTest, ParsesSingleAddress) +{ + EXPECT_EQ(QHostAddress("203.0.113.7"), parseForwardedAddress("203.0.113.7")); + EXPECT_EQ(QHostAddress("::1"), parseForwardedAddress("::1")); + EXPECT_EQ(QHostAddress("203.0.113.7"), parseForwardedAddress(" 203.0.113.7 ")); +} + +TEST(ForwardedAddressTest, UsesRightmostEntryOfAList) +{ + // nginx's $proxy_add_x_forwarded_for appends its own view of the client to + // whatever the client sent, so only the trailing entry is trustworthy. + EXPECT_EQ(QHostAddress("203.0.113.7"), parseForwardedAddress("198.51.100.9, 203.0.113.7")); + EXPECT_EQ(QHostAddress("203.0.113.7"), parseForwardedAddress("spoofed.invalid, 203.0.113.7")); + EXPECT_EQ(QHostAddress("203.0.113.7"), parseForwardedAddress("203.0.113.7, ")); +} + +TEST(ForwardedAddressTest, KeepsIpv4MappedEntries) +{ + EXPECT_EQ(QHostAddress("::ffff:203.0.113.7"), parseForwardedAddress("::ffff:203.0.113.7")); +} + +TEST(ForwardedAddressTest, RejectsValuesWithoutAnAddress) +{ + EXPECT_TRUE(parseForwardedAddress("").isNull()); + EXPECT_TRUE(parseForwardedAddress(",").isNull()); + EXPECT_TRUE(parseForwardedAddress("unknown").isNull()); + EXPECT_TRUE(parseForwardedAddress("garbage, more garbage").isNull()); +} + +} // namespace diff --git a/tests/game/CMakeLists.txt b/tests/game/CMakeLists.txt new file mode 100644 index 000000000..42c935c8f --- /dev/null +++ b/tests/game/CMakeLists.txt @@ -0,0 +1,18 @@ +cockatrice_add_test( + NAME + game_logic_test + SOURCES + game_test_main.cpp + card_state_test.cpp + card_zone_logic_test.cpp + game_event_handler_test.cpp + player_actions_test.cpp + replay_test.cpp + ${VERSION_STRING_CPP} + LIBS + libcockatrice_game + INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR} + DEFINITIONS + GAME_TEST_DATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" +) diff --git a/tests/game/card_state_test.cpp b/tests/game/card_state_test.cpp new file mode 100644 index 000000000..2f2c43bf4 --- /dev/null +++ b/tests/game/card_state_test.cpp @@ -0,0 +1,110 @@ +#include "gtest/gtest.h" +#include +#include +#include +#include +#include + +namespace +{ + +CardRef cardRefFor(const QString &name) +{ + CardRef cardRef; + cardRef.name = name; + return cardRef; +} + +} // namespace + +TEST(CardStateTest, AttachmentGraphKeepsBothEndsConsistent) +{ + CardState host(nullptr, nullptr, cardRefFor(QStringLiteral("Host")), 1); + CardState guest(nullptr, nullptr, cardRefFor(QStringLiteral("Guest")), 2); + + guest.setAttachedTo(&host); + host.addAttachedCard(&guest); + + EXPECT_EQ(guest.getAttachedTo(), &host); + ASSERT_EQ(host.getAttachedCards().size(), 1); + EXPECT_EQ(host.getAttachedCards().first(), &guest); + + host.removeAttachedCard(&guest); + guest.setAttachedTo(nullptr); + + EXPECT_TRUE(host.getAttachedCards().isEmpty()); + EXPECT_EQ(guest.getAttachedTo(), nullptr); +} + +TEST(CardStateTest, CountersAddUpdateAndClear) +{ + CardState card(nullptr, nullptr, cardRefFor(QStringLiteral("Cat")), 1); + int changes = 0; + QObject::connect(&card, &CardState::countersChanged, [&changes]() { ++changes; }); + + card.insertCounter(1, 2); + EXPECT_EQ(card.getCounters().value(1), 2); + + card.setCounter(1, 3); + EXPECT_EQ(card.getCounters().value(1), 3); + + card.setCounter(1, 0); + EXPECT_FALSE(card.getCounters().contains(1)); + + card.setCounter(2, 1); + card.clearCounters(); + EXPECT_TRUE(card.getCounters().isEmpty()); + + EXPECT_EQ(changes, 5); +} + +TEST(CardStateTest, TapFlipAttackingAndReset) +{ + CardState card(nullptr, nullptr, cardRefFor(QStringLiteral("Cat")), 1); + card.setTapped(true); + card.setFaceDown(true); + card.setAttacking(true); + card.setAnnotation(QStringLiteral("note")); + card.setPT(QStringLiteral("4/4")); + card.setDoesntUntap(true); + card.setDestroyOnZoneChange(true); + card.setColor(QStringLiteral("red")); + card.insertCounter(1, 2); + + card.resetState(true); + + EXPECT_FALSE(card.getTapped()); + EXPECT_FALSE(card.getAttacking()); + EXPECT_TRUE(card.getCounters().isEmpty()); + EXPECT_TRUE(card.getPT().isEmpty()); + EXPECT_FALSE(card.getDoesntUntap()); + EXPECT_EQ(card.getAnnotation(), QStringLiteral("note")); + // resetState() deliberately leaves these three alone. + EXPECT_TRUE(card.getFaceDown()); + EXPECT_EQ(card.getColor(), QStringLiteral("red")); + EXPECT_TRUE(card.getDestroyOnZoneChange()); + + card.setAnnotation(QStringLiteral("other")); + card.resetState(false); + EXPECT_TRUE(card.getAnnotation().isEmpty()); +} + +TEST(CardStateTest, ParsePT) +{ + const QVariantList plain = CardState::parsePT(QStringLiteral("3/3")); + ASSERT_EQ(plain.size(), 2); + EXPECT_EQ(plain.at(0).toString(), QStringLiteral("3")); + EXPECT_EQ(plain.at(1).toString(), QStringLiteral("3")); + + const QVariantList negative = CardState::parsePT(QStringLiteral("-1/2")); + ASSERT_EQ(negative.size(), 2); + EXPECT_EQ(negative.at(0).typeId(), QMetaType::Int); + EXPECT_EQ(negative.at(0).toInt(), -1); + EXPECT_EQ(negative.at(1).toString(), QStringLiteral("2")); + + const QVariantList star = CardState::parsePT(QStringLiteral("*")); + ASSERT_EQ(star.size(), 1); + EXPECT_EQ(star.at(0).toString(), QStringLiteral("*")); + + EXPECT_TRUE(CardState::parsePT(QString()).isEmpty()); +} diff --git a/tests/game/card_zone_logic_test.cpp b/tests/game/card_zone_logic_test.cpp new file mode 100644 index 000000000..7f0c39b47 --- /dev/null +++ b/tests/game/card_zone_logic_test.cpp @@ -0,0 +1,107 @@ +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include + +namespace +{ + +CardRef cardRefFor(const QString &name) +{ + CardRef cardRef; + cardRef.name = name; + return cardRef; +} + +} // namespace + +TEST(CardZoneLogicTest, AddsAndTakesCardsWithoutAGraphicsSide) +{ + HandZoneLogic zone(nullptr, QStringLiteral("hand"), false, false, true); + CardState card(nullptr, nullptr, cardRefFor(QStringLiteral("Lightning Bolt")), 7); + + QSignalSpy cardAddedSpy(&zone, &CardZoneLogic::cardAdded); + QSignalSpy cardCountSpy(&zone, &CardZoneLogic::cardCountChanged); + + zone.addCard(&card, false, 0); + + ASSERT_EQ(zone.getCards().size(), 1); + EXPECT_EQ(zone.getCards().first(), &card); + EXPECT_EQ(card.getZone(), &zone); + EXPECT_EQ(cardAddedSpy.count(), 1); + EXPECT_GE(cardCountSpy.count(), 1); + + EXPECT_EQ(zone.takeCard(0, 7), &card); + EXPECT_TRUE(zone.getCards().isEmpty()); + EXPECT_EQ(card.getId(), 7); +} + +TEST(CardZoneLogicTest, AsksTheGraphicsSideToCreateCards) +{ + HandZoneLogic zone(nullptr, QStringLiteral("library"), false, true, true); + // The graphics side owns the view logic and registers it on the zone it + // shows; adding a card then goes through prepareAddCard and the request + // signal instead of the test emitting the signal itself. + ZoneViewZoneLogic view(nullptr, &zone, 1, true, false, false); + zone.getViews().append(&view); + + QSignalSpy requestSpy(&view, &CardZoneLogic::createCardRequested); + + CardState card(nullptr, nullptr, cardRefFor(QStringLiteral("Lightning Bolt")), 7); + zone.addCard(&card, true, 0, 0); + + ASSERT_EQ(requestSpy.count(), 1); + const QList arguments = requestSpy.takeFirst(); + const ServerInfo_Card cardInfo = arguments.at(0).value(); + EXPECT_EQ(cardInfo.id(), 7); + EXPECT_EQ(cardInfo.name(), "Lightning Bolt"); + EXPECT_TRUE(arguments.at(1).toBool()); + EXPECT_EQ(card.getZone(), &zone); + EXPECT_EQ(zone.getCards().size(), 1); +} + +TEST(CardZoneLogicTest, MoveBetweenZonesUpdatesCardZone) +{ + HandZoneLogic first(nullptr, QStringLiteral("hand"), false, false, true); + HandZoneLogic second(nullptr, QStringLiteral("grave"), false, false, true); + CardState card(nullptr, nullptr, cardRefFor(QStringLiteral("Cat")), 7); + + first.addCard(&card, false, 0); + ASSERT_EQ(first.getCards().size(), 1); + + CardState *taken = first.takeCard(0, 7); + ASSERT_EQ(taken, &card); + EXPECT_TRUE(first.getCards().isEmpty()); + + second.addCard(taken, false, 0); + EXPECT_EQ(second.getCards().size(), 1); + EXPECT_EQ(card.getZone(), &second); +} + +TEST(CardZoneLogicTest, GetCardFindsById) +{ + HandZoneLogic zone(nullptr, QStringLiteral("hand"), false, false, true); + CardState first(nullptr, nullptr, cardRefFor(QStringLiteral("Cat")), 7); + CardState second(nullptr, nullptr, cardRefFor(QStringLiteral("Dog")), 8); + + zone.addCard(&first, false, 0); + zone.addCard(&second, false, 0); + + EXPECT_EQ(zone.getCard(7), &first); + EXPECT_EQ(zone.getCard(8), &second); + EXPECT_EQ(zone.getCard(99), nullptr); +} + +TEST(CardZoneLogicTest, ReorganizeSignalFiresOnAdd) +{ + HandZoneLogic zone(nullptr, QStringLiteral("hand"), false, false, true); + CardState card(nullptr, nullptr, cardRefFor(QStringLiteral("Cat")), 7); + QSignalSpy reorganizeSpy(&zone, &CardZoneLogic::reorganizeCards); + + zone.addCard(&card, true, 0); + + EXPECT_EQ(reorganizeSpy.count(), 1); +} diff --git a/tests/game/data/cards.xml b/tests/game/data/cards.xml new file mode 100644 index 000000000..e315dbf48 --- /dev/null +++ b/tests/game/data/cards.xml @@ -0,0 +1,64 @@ + + + + + Cat + CAT + 0 + Meow! + + 111 + G + 2G + 2 + Creature — Cat + Creature + 3/3 + + + + Grizzly Bears + LEA + 0 + 1 + + + 112 + G + 1G + 2 + Creature — Bear + Creature + 2/2 + + + + Lightning Bolt + LEA + 3 + Lightning Bolt deals 3 damage to any target. + + 113 + R + R + 1 + Instant + Instant + + + + Forest + LEA + 2 + (Add G.) + + 114 + G + + 0 + Land + Land + + + + diff --git a/tests/game/data/spoilers.xml b/tests/game/data/spoilers.xml new file mode 100644 index 000000000..414ae10d4 --- /dev/null +++ b/tests/game/data/spoilers.xml @@ -0,0 +1,4 @@ + + + + diff --git a/tests/game/data/tokens.xml b/tests/game/data/tokens.xml new file mode 100644 index 000000000..30edbb24c --- /dev/null +++ b/tests/game/data/tokens.xml @@ -0,0 +1,21 @@ + + + + + Kitten + CAT + 0 + + 1 + + 211 + G + + + Token + Token + 1/1 + + + + diff --git a/tests/game/game_event_handler_test.cpp b/tests/game/game_event_handler_test.cpp new file mode 100644 index 000000000..2db6a1660 --- /dev/null +++ b/tests/game/game_event_handler_test.cpp @@ -0,0 +1,566 @@ +#include "game_test_fixture.h" + +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + +/** + * Wraps one player-scoped event in the container the server would send and + * pushes it through the real dispatcher, so the test exercises the same + * GameEventHandler -> PlayerEventHandler path as a live client. + */ +template +void sendGameEvent(GameHarness &harness, + int playerId, + const EventType &message, + const GameEventContext *context = nullptr) +{ + GameEventContainer container; + container.set_game_id(42); + if (context != nullptr) { + container.mutable_context()->CopyFrom(*context); + } + + GameEvent *event = container.add_event_list(); + event->set_player_id(playerId); + event->MutableExtension(EventType::ext)->CopyFrom(message); + + harness.game()->getGameEventHandler()->processGameEventContainer(container, harness.client(), + EventProcessingOptions()); +} + +Event_SetCardAttr cardAttrEvent(const char *zoneName, int cardId, CardAttribute attribute, const char *value) +{ + Event_SetCardAttr event; + event.set_zone_name(zoneName); + event.set_card_id(cardId); + event.set_attribute(attribute); + event.set_attr_value(value); + return event; +} + +} // namespace + +TEST(GameEventHandlerTest, MoveCardMovesBetweenZones) +{ + GameHarness harness; + CardState *cat = harness.addCardToZone(ZoneNames::HAND, QStringLiteral("Cat"), 7); + ASSERT_NE(cat, nullptr); + + Event_MoveCard event; + event.set_card_id(7); + event.set_start_player_id(1); + event.set_start_zone(ZoneNames::HAND); + event.set_position(0); + event.set_target_player_id(1); + event.set_target_zone(ZoneNames::TABLE); + event.set_x(0); + event.set_y(TableZoneLogic::tableRowToGridY(0)); + event.set_new_card_id(7); + event.set_face_down(false); + + sendGameEvent(harness, 1, event); + + EXPECT_TRUE(harness.zone(ZoneNames::HAND)->getCards().isEmpty()); + CardZoneLogic *table = harness.zone(ZoneNames::TABLE); + ASSERT_EQ(table->getCards().size(), 1); + EXPECT_EQ(table->getCards().first(), cat); + EXPECT_EQ(cat->getZone(), table); + EXPECT_EQ(cat->getId(), 7); + EXPECT_EQ(cat->getCardRef().name, QStringLiteral("Cat")); + EXPECT_FALSE(cat->getFaceDown()); + EXPECT_EQ(cat->getPT(), QStringLiteral("3/3")); + EXPECT_EQ(cat->getGridPoint(), QPoint(0, TableZoneLogic::tableRowToGridY(0))); +} + +TEST(GameEventHandlerTest, MoveCardFaceDownKeepsTheCardHidden) +{ + GameHarness harness; + CardState *cat = harness.addCardToZone(ZoneNames::HAND, QStringLiteral("Cat"), 7); + ASSERT_NE(cat, nullptr); + + Event_MoveCard event; + event.set_card_id(7); + event.set_start_player_id(1); + event.set_start_zone(ZoneNames::HAND); + event.set_position(0); + event.set_target_player_id(1); + event.set_target_zone(ZoneNames::TABLE); + event.set_x(1); + event.set_y(TableZoneLogic::tableRowToGridY(2)); + event.set_new_card_id(7); + event.set_face_down(true); + + sendGameEvent(harness, 1, event); + + ASSERT_EQ(harness.zone(ZoneNames::TABLE)->getCards().size(), 1); + EXPECT_TRUE(cat->getFaceDown()); + // A face-down card keeps whatever P/T it had instead of picking up the + // printed one from the card database. + EXPECT_TRUE(cat->getPT().isEmpty()); +} + +TEST(GameEventHandlerTest, SetCardAttrUpdatesSingleCard) +{ + GameHarness harness; + CardState *cat = harness.addCardToZone(ZoneNames::TABLE, QStringLiteral("Cat"), 7); + ASSERT_NE(cat, nullptr); + + sendGameEvent(harness, 1, cardAttrEvent(ZoneNames::TABLE, 7, AttrTapped, "1")); + sendGameEvent(harness, 1, cardAttrEvent(ZoneNames::TABLE, 7, AttrAttacking, "1")); + sendGameEvent(harness, 1, cardAttrEvent(ZoneNames::TABLE, 7, AttrPT, "5/5")); + sendGameEvent(harness, 1, cardAttrEvent(ZoneNames::TABLE, 7, AttrAnnotation, "note")); + sendGameEvent(harness, 1, cardAttrEvent(ZoneNames::TABLE, 7, AttrColor, "red")); + sendGameEvent(harness, 1, cardAttrEvent(ZoneNames::TABLE, 7, AttrDoesntUntap, "1")); + sendGameEvent(harness, 1, cardAttrEvent(ZoneNames::TABLE, 7, AttrFaceDown, "1")); + + EXPECT_TRUE(cat->getTapped()); + EXPECT_TRUE(cat->getAttacking()); + EXPECT_EQ(cat->getPT(), QStringLiteral("5/5")); + EXPECT_EQ(cat->getAnnotation(), QStringLiteral("note")); + EXPECT_EQ(cat->getColor(), QStringLiteral("red")); + EXPECT_TRUE(cat->getDoesntUntap()); + EXPECT_TRUE(cat->getFaceDown()); +} + +TEST(GameEventHandlerTest, SetCardAttrWithoutCardIdTouchesEveryCardInZone) +{ + GameHarness harness; + CardState *cat = harness.addCardToZone(ZoneNames::TABLE, QStringLiteral("Cat"), 7); + CardState *bears = harness.addCardToZone(ZoneNames::TABLE, QStringLiteral("Grizzly Bears"), 9); + ASSERT_NE(cat, nullptr); + ASSERT_NE(bears, nullptr); + + Event_SetCardAttr event; + event.set_zone_name(ZoneNames::TABLE); + event.set_attribute(AttrAnnotation); + event.set_attr_value("marked"); + + sendGameEvent(harness, 1, event); + + EXPECT_EQ(cat->getAnnotation(), QStringLiteral("marked")); + EXPECT_EQ(bears->getAnnotation(), QStringLiteral("marked")); +} + +TEST(GameEventHandlerTest, UntapAllLeavesDoesntUntapCardsTapped) +{ + GameHarness harness; + CardState *cat = harness.addCardToZone(ZoneNames::TABLE, QStringLiteral("Cat"), 7); + CardState *bears = harness.addCardToZone(ZoneNames::TABLE, QStringLiteral("Grizzly Bears"), 9); + ASSERT_NE(cat, nullptr); + ASSERT_NE(bears, nullptr); + cat->setDoesntUntap(true); + + sendGameEvent(harness, 1, cardAttrEvent(ZoneNames::TABLE, 7, AttrTapped, "1")); + sendGameEvent(harness, 1, cardAttrEvent(ZoneNames::TABLE, 9, AttrTapped, "1")); + + Event_SetCardAttr untapAll; + untapAll.set_zone_name(ZoneNames::TABLE); + untapAll.set_attribute(AttrTapped); + untapAll.set_attr_value("0"); + + sendGameEvent(harness, 1, untapAll); + + EXPECT_TRUE(cat->getTapped()); + EXPECT_FALSE(bears->getTapped()); +} + +TEST(GameEventHandlerTest, SetCardCounterAddsAndRemoves) +{ + GameHarness harness; + CardState *cat = harness.addCardToZone(ZoneNames::HAND, QStringLiteral("Cat"), 7); + ASSERT_NE(cat, nullptr); + + Event_SetCardCounter add; + add.set_zone_name(ZoneNames::HAND); + add.set_card_id(7); + add.set_counter_id(1); + add.set_counter_value(2); + + sendGameEvent(harness, 1, add); + + EXPECT_EQ(cat->getCounters().value(1), 2); + + Event_SetCardCounter remove = add; + remove.set_counter_value(0); + + sendGameEvent(harness, 1, remove); + + EXPECT_FALSE(cat->getCounters().contains(1)); +} + +TEST(GameEventHandlerTest, AttachCardLinksBothEndsAndUnattachClearsThem) +{ + GameHarness harness; + CardState *host = harness.addCardToZone(ZoneNames::TABLE, QStringLiteral("Cat"), 7); + CardState *guest = harness.addCardToZone(ZoneNames::TABLE, QStringLiteral("Grizzly Bears"), 9); + ASSERT_NE(host, nullptr); + ASSERT_NE(guest, nullptr); + + Event_AttachCard attach; + attach.set_start_zone(ZoneNames::TABLE); + attach.set_card_id(9); + attach.set_target_player_id(1); + attach.set_target_zone(ZoneNames::TABLE); + attach.set_target_card_id(7); + + sendGameEvent(harness, 1, attach); + + EXPECT_EQ(guest->getAttachedTo(), host); + ASSERT_EQ(host->getAttachedCards().size(), 1); + EXPECT_EQ(host->getAttachedCards().first(), guest); + + Event_AttachCard detach; + detach.set_start_zone(ZoneNames::TABLE); + detach.set_card_id(9); + + sendGameEvent(harness, 1, detach); + + EXPECT_EQ(guest->getAttachedTo(), nullptr); + EXPECT_TRUE(host->getAttachedCards().isEmpty()); +} + +TEST(GameEventHandlerTest, ChangeZonePropertiesUpdatesReveal) +{ + GameHarness harness; + CardZoneLogic *deck = harness.zone(ZoneNames::DECK); + ASSERT_NE(deck, nullptr); + + Event_ChangeZoneProperties reveal; + reveal.set_zone_name(ZoneNames::DECK); + reveal.set_always_reveal_top_card(true); + + sendGameEvent(harness, 1, reveal); + EXPECT_TRUE(deck->getAlwaysRevealTopCard()); + + reveal.set_always_reveal_top_card(false); + sendGameEvent(harness, 1, reveal); + EXPECT_FALSE(deck->getAlwaysRevealTopCard()); +} + +TEST(GameEventHandlerTest, DrawCardsMovesNamedCardsFromDeckToHand) +{ + GameHarness harness; + ASSERT_NE(harness.addCardToZone(ZoneNames::DECK, QStringLiteral("Cat"), 7), nullptr); + + Event_DrawCards draw; + draw.set_number(1); + ServerInfo_Card *cardInfo = draw.add_cards(); + cardInfo->set_id(7); + cardInfo->set_name("Cat"); + + sendGameEvent(harness, 1, draw); + + EXPECT_TRUE(harness.zone(ZoneNames::DECK)->getCards().isEmpty()); + ASSERT_EQ(harness.zone(ZoneNames::HAND)->getCards().size(), 1); + CardState *drawn = harness.zone(ZoneNames::HAND)->getCards().first(); + EXPECT_EQ(drawn->getId(), 7); + EXPECT_EQ(drawn->getName(), QStringLiteral("Cat")); +} + +TEST(GameEventHandlerTest, FlipCardHidesAndRevealsTheCard) +{ + GameHarness harness; + CardState *cat = harness.addCardToZone(ZoneNames::TABLE, QStringLiteral("Cat"), 7); + ASSERT_NE(cat, nullptr); + + Event_FlipCard flip; + flip.set_zone_name(ZoneNames::TABLE); + flip.set_card_id(7); + flip.set_face_down(true); + + sendGameEvent(harness, 1, flip); + + EXPECT_TRUE(cat->getFaceDown()); + // Flipping face down does not touch the card reference: the client must not + // learn (or overwrite) the name of a card it is not allowed to see. + EXPECT_EQ(cat->getName(), QStringLiteral("Cat")); + + // Turning it face up carries the real printing, so the state picks it up. + flip.set_face_down(false); + flip.set_card_name("Grizzly Bears"); + + sendGameEvent(harness, 1, flip); + + EXPECT_FALSE(cat->getFaceDown()); + EXPECT_EQ(cat->getName(), QStringLiteral("Grizzly Bears")); +} + +TEST(GameEventHandlerTest, DestroyCardRemovesItAndDetachesAttachedCards) +{ + GameHarness harness; + CardState *host = harness.addCardToZone(ZoneNames::TABLE, QStringLiteral("Cat"), 7); + CardState *guest = harness.addCardToZone(ZoneNames::TABLE, QStringLiteral("Grizzly Bears"), 9); + ASSERT_NE(host, nullptr); + ASSERT_NE(guest, nullptr); + + guest->setAttachedTo(host); + ASSERT_EQ(guest->getAttachedTo(), host); + ASSERT_EQ(host->getAttachedCards().size(), 1); + + Event_DestroyCard destroy; + destroy.set_zone_name(ZoneNames::TABLE); + destroy.set_card_id(7); + + sendGameEvent(harness, 1, destroy); + + CardZoneLogic *table = harness.zone(ZoneNames::TABLE); + ASSERT_EQ(table->getCards().size(), 1); + EXPECT_EQ(table->getCards().first(), guest); + EXPECT_FALSE(table->getCards().contains(host)); + + // The attached card survives the host and is left un-attached, ready to be + // laid out again by the zone. + EXPECT_EQ(guest->getAttachedTo(), nullptr); + EXPECT_TRUE(host->getAttachedCards().isEmpty()); + EXPECT_EQ(guest->getZone(), table); +} + +TEST(GameEventHandlerTest, CreateTokenUsesDatabasePTUnlessFaceDown) +{ + GameHarness harness; + CardZoneLogic *table = harness.zone(ZoneNames::TABLE); + ASSERT_NE(table, nullptr); + + QList created; + QObject::connect(table, &CardZoneLogic::createCardRequested, table, + [&created](const ServerInfo_Card &cardInfo, bool) { created.append(cardInfo); }); + + Event_CreateToken token; + token.set_zone_name(ZoneNames::TABLE); + token.set_card_id(11); + token.set_card_name("Kitten"); + token.set_x(2); + token.set_y(1); + token.set_color("green"); + token.set_annotation("a kitten"); + token.set_destroy_on_zone_change(true); + + sendGameEvent(harness, 1, token); + + ASSERT_EQ(created.size(), 1); + // The event carries no P/T, so the token takes the printed one. + EXPECT_EQ(created.first().pt(), "1/1"); + EXPECT_EQ(created.first().name(), "Kitten"); + EXPECT_EQ(created.first().x(), 2); + EXPECT_EQ(created.first().y(), 1); + EXPECT_EQ(created.first().color(), "green"); + EXPECT_EQ(created.first().annotation(), "a kitten"); + EXPECT_TRUE(created.first().destroy_on_zone_change()); + EXPECT_FALSE(created.first().face_down()); + + // An explicit P/T always wins over the database. + Event_CreateToken explicitPt = token; + explicitPt.set_card_id(12); + explicitPt.set_pt("3/3"); + + sendGameEvent(harness, 1, explicitPt); + + ASSERT_EQ(created.size(), 2); + EXPECT_EQ(created.last().pt(), "3/3"); + + // A face-down token must not leak the printed P/T. + Event_CreateToken faceDown = token; + faceDown.set_card_id(13); + faceDown.set_face_down(true); + + sendGameEvent(harness, 1, faceDown); + + ASSERT_EQ(created.size(), 3); + EXPECT_TRUE(created.last().face_down()); + EXPECT_TRUE(created.last().pt().empty()); +} + +TEST(GameEventHandlerTest, ConcedeAndUnconcedeUpdatePlayerState) +{ + GameHarness harness; + ASSERT_NE(harness.addCardToZone(ZoneNames::TABLE, QStringLiteral("Cat"), 7), nullptr); + EXPECT_FALSE(harness.player()->getConceded()); + + Event_PlayerPropertiesChanged properties; + properties.mutable_player_properties()->set_player_id(1); + properties.mutable_player_properties()->set_conceded(true); + + GameEventContext concedeContext; + concedeContext.MutableExtension(Context_Concede::ext); + + sendGameEvent(harness, 1, properties, &concedeContext); + + EXPECT_TRUE(harness.player()->getConceded()); + // Conceding tears the board down for that player. + EXPECT_TRUE(harness.zone(ZoneNames::TABLE)->getCards().isEmpty()); + + Event_PlayerPropertiesChanged back = properties; + back.mutable_player_properties()->set_conceded(false); + + GameEventContext unconcedeContext; + unconcedeContext.MutableExtension(Context_Unconcede::ext); + + sendGameEvent(harness, 1, back, &unconcedeContext); + + EXPECT_FALSE(harness.player()->getConceded()); +} + +TEST(GameEventHandlerTest, SetActivePlayerAndPhaseUpdateGameState) +{ + GameHarness harness; + GameState *state = harness.game()->getGameState(); + ASSERT_NE(state, nullptr); + EXPECT_EQ(state->getActivePlayer(), -1); + EXPECT_EQ(state->getCurrentPhase(), -1); + + Event_SetActivePlayer setPlayer; + setPlayer.set_active_player_id(1); + + sendGameEvent(harness, 1, setPlayer); + + EXPECT_EQ(state->getActivePlayer(), 1); + + Event_SetActivePhase setPhase; + setPhase.set_phase(3); + + sendGameEvent(harness, 1, setPhase); + + EXPECT_EQ(state->getCurrentPhase(), 3); +} + +TEST(GameEventHandlerTest, CreateSetAndDeletePlayerCounter) +{ + GameHarness harness; + + ServerInfo_Counter counter; + counter.set_id(1); + counter.set_name("life"); + counter.set_radius(1); + counter.set_count(20); + + Event_CreateCounter create; + create.mutable_counter_info()->CopyFrom(counter); + + sendGameEvent(harness, 1, create); + + CounterState *state = harness.player()->getCounters().value(1); + ASSERT_NE(state, nullptr); + EXPECT_EQ(state->getName(), QStringLiteral("life")); + EXPECT_EQ(state->getValue(), 20); + EXPECT_EQ(harness.player()->getLifeCounter(), state); + + Event_SetCounter set; + set.set_counter_id(1); + set.set_value(15); + + sendGameEvent(harness, 1, set); + + EXPECT_EQ(state->getValue(), 15); + + // Updating a counter that does not exist is a no-op, not a crash. + Event_SetCounter unknown = set; + unknown.set_counter_id(99); + sendGameEvent(harness, 1, unknown); + + EXPECT_FALSE(harness.player()->getCounters().contains(99)); + + Event_DelCounter remove; + remove.set_counter_id(1); + + sendGameEvent(harness, 1, remove); + + EXPECT_FALSE(harness.player()->getCounters().contains(1)); + EXPECT_EQ(harness.player()->getLifeCounter(), nullptr); +} + +TEST(GameEventHandlerTest, LeaveRemovesThePlayer) +{ + GameHarness harness; + ASSERT_NE(harness.addCardToZone(ZoneNames::TABLE, QStringLiteral("Cat"), 7), nullptr); + ASSERT_NE(harness.game()->getPlayerManager()->getPlayer(1), nullptr); + + Event_Leave leave; + leave.set_reason(Event_Leave::USER_LEFT); + + sendGameEvent(harness, 1, leave); + + EXPECT_EQ(harness.game()->getPlayerManager()->getPlayer(1), nullptr); + EXPECT_TRUE(harness.game()->getPlayerManager()->getPlayers().isEmpty()); +} + +TEST(GameEventHandlerTest, KickedSignalsGameClosed) +{ + GameHarness harness; + + bool closed = false; + QObject::connect(harness.game()->getGameEventHandler(), &GameEventHandler::gameClosed, + [&closed] { closed = true; }); + + sendGameEvent(harness, 1, Event_Kicked()); + + EXPECT_TRUE(closed); +} + +TEST(GameEventHandlerTest, GameClosedMarksTheSessionClosed) +{ + GameHarness harness; + ASSERT_FALSE(harness.game()->getGameState()->isGameClosed()); + + Event_GameStateChanged started; + started.set_game_started(true); + sendGameEvent(harness, 1, started); + ASSERT_TRUE(harness.game()->getGameMetaInfo()->started()); + + sendGameEvent(harness, 1, Event_GameClosed()); + + EXPECT_TRUE(harness.game()->getGameState()->isGameClosed()); + EXPECT_FALSE(harness.game()->getGameMetaInfo()->started()); +} + +TEST(GameEventHandlerTest, GameHostChangedMovesTheHostRole) +{ + GameHarness harness; + + ServerInfo_User bob; + bob.set_name("Bob"); + bob.set_user_level(ServerInfo_User::IsUser); + ASSERT_NE(harness.game()->getPlayerManager()->addPlayer(2, bob), nullptr); + + sendGameEvent(harness, 2, Event_GameHostChanged()); + + EXPECT_EQ(harness.game()->getGameState()->getHostId(), 2); +} diff --git a/tests/game/game_test_fixture.h b/tests/game/game_test_fixture.h new file mode 100644 index 000000000..e4703c917 --- /dev/null +++ b/tests/game/game_test_fixture.h @@ -0,0 +1,129 @@ +/** + * @file game_test_fixture.h + * + * Fixtures for tests that need a complete game/player/zone graph without the + * client, a server or any graphics object. Commands are captured on a recording + * AbstractClient, which is the same seam the real client uses. + */ + +#ifndef COCKATRICE_TESTS_GAME_TEST_FIXTURE_H +#define COCKATRICE_TESTS_GAME_TEST_FIXTURE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * Captures every command the game logic sends, instead of writing it to a socket. + * + * AbstractClient::sendCommand uses a direct signal/slot connection in the same + * thread, so a command is in `sent` as soon as sendCommand returns. + */ +class RecordingClient : public AbstractClient +{ +public: + QList sent; + +protected: + void sendCommandContainer(const CommandContainer &cont) override + { + sent.append(cont); + } +}; + +/** + * Owns a local, single-client Game with one local player, plus the cards added + * to its zones. + */ +class GameHarness +{ +public: + explicit GameHarness(int playerId = 1) : playerId_(playerId), client_(std::make_unique()) + { + Event_GameJoined event; + event.set_host_id(playerId_); + event.set_player_id(playerId_); + event.mutable_game_info()->set_game_id(42); + event.mutable_game_info()->set_started(true); + + QList clients{client_.get()}; + QMap roomGameTypes; + game_ = std::make_unique(nullptr, true, clients, event, roomGameTypes); + + ServerInfo_User user; + user.set_name("Alice"); + user.set_user_level(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered); + game_->getPlayerManager()->addPlayer(playerId_, user); + } + + ~GameHarness() + { + qDeleteAll(ownedCards_); + ownedCards_.clear(); + } + + GameHarness(const GameHarness &) = delete; + GameHarness &operator=(const GameHarness &) = delete; + + [[nodiscard]] Game *game() const + { + return game_.get(); + } + + [[nodiscard]] RecordingClient *client() const + { + return client_.get(); + } + + [[nodiscard]] PlayerLogic *player() const + { + return game_->getPlayerManager()->getPlayer(playerId_); + } + + [[nodiscard]] CardZoneLogic *zone(const QString &zoneName) const + { + return player()->getZones().value(zoneName); + } + + CardState *addCardToZone(const QString &zoneName, const QString &cardName, int cardId) + { + CardZoneLogic *targetZone = zone(zoneName); + if (!targetZone) { + return nullptr; + } + + auto *card = new CardState(nullptr, targetZone, CardRef{cardName, QString()}, cardId); + // The constructor only stores the ref; this is what fills in the card + // data (table row, P/T, ...) the actions read. + card->refreshCardInfo(); + targetZone->addCard(card, false, 0); + ownedCards_.append(card); + return card; + } + + [[nodiscard]] const CommandContainer &lastCommand() const + { + return client_->sent.last(); + } + +private: + int playerId_; + std::unique_ptr client_; + std::unique_ptr game_; + QList ownedCards_; +}; + +#endif // COCKATRICE_TESTS_GAME_TEST_FIXTURE_H diff --git a/tests/game/game_test_main.cpp b/tests/game/game_test_main.cpp new file mode 100644 index 000000000..47b9a6b8e --- /dev/null +++ b/tests/game/game_test_main.cpp @@ -0,0 +1,126 @@ +/** + * @file game_test_main.cpp + * + * Test entry point for the headless game-logic suite. The game library has no + * graphics dependency, so a QCoreApplication is enough; the only process-wide + * setup left is the card database, which the game logic still reaches through + * CardDatabaseManager and which therefore has to exist before the first zone is + * constructed. + */ + +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + +class GameTestPathProvider : public ICardDatabasePathProvider +{ +public: + explicit GameTestPathProvider(QString baseDir) : baseDir_(std::move(baseDir)) + { + } + + QString getCardDatabasePath() const override + { + return baseDir_ + QStringLiteral("/cards.xml"); + } + + QString getCustomCardDatabasePath() const override + { + return baseDir_ + QStringLiteral("/customsets/"); + } + + QString getTokenDatabasePath() const override + { + return baseDir_ + QStringLiteral("/tokens.xml"); + } + + QString getSpoilerCardDatabasePath() const override + { + return baseDir_ + QStringLiteral("/spoilers.xml"); + } + +private: + QString baseDir_; +}; + +bool copyFixture(const QString &fileName, const QString &targetDir) +{ + const QString source = QString::fromUtf8(GAME_TEST_DATA_DIR) + QLatin1Char('/') + fileName; + const QString target = targetDir + QLatin1Char('/') + fileName; + if (QFile::copy(source, target)) { + return true; + } + + std::fprintf(stderr, "Failed to copy test fixture %s to %s\n", qPrintable(source), qPrintable(target)); + return false; +} + +} // namespace + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + + // gtest_discover_tests runs the binary with --gtest_list_tests while the + // build is still in progress. No test runs there, and building the card + // database would leave process-lifetime allocations that the sanitizer + // leg's default leak detection reports at exit, failing discovery. + if (::testing::GTEST_FLAG(list_tests)) { + return RUN_ALL_TESTS(); + } + + QStandardPaths::setTestModeEnabled(true); + + QTemporaryDir home; + if (!home.isValid()) { + std::fprintf(stderr, "Failed to create a temporary home directory\n"); + return EXIT_FAILURE; + } + qputenv("HOME", home.path().toLocal8Bit()); + + QCoreApplication app(argc, argv); + QLoggingCategory::setFilterRules(QStringLiteral("card_database.loading=false\n" + "card_database.loading.success_or_failure=false\n" + "settings_cache.*=false")); + + // Copy the fixtures so the database loader never writes its .cache file into + // the source tree, then point the singleton at the copy. + QTemporaryDir dataDir; + if (!dataDir.isValid() || !QDir().mkpath(dataDir.filePath(QStringLiteral("customsets")))) { + std::fprintf(stderr, "Failed to create a temporary card database directory\n"); + return EXIT_FAILURE; + } + if (!copyFixture(QStringLiteral("cards.xml"), dataDir.path()) || + !copyFixture(QStringLiteral("tokens.xml"), dataDir.path()) || + !copyFixture(QStringLiteral("spoilers.xml"), dataDir.path())) { + return EXIT_FAILURE; + } + + // Function-local static: constructed before the CardDatabase singleton below, + // so it is destroyed after it and the raw pointer stored in the manager stays + // valid for the database's lifetime. + static GameTestPathProvider pathProvider(dataDir.path()); + CardDatabaseManager::setCardDatabasePathProvider(&pathProvider); + + CardDatabase *database = CardDatabaseManager::getInstance(); + database->loadCardDatabases(); + if (database->getLoadStatus() != Ok) { + std::fprintf(stderr, "Failed to load the game test card database (status %d)\n", + static_cast(database->getLoadStatus())); + return EXIT_FAILURE; + } + + return RUN_ALL_TESTS(); +} diff --git a/tests/game/player_actions_test.cpp b/tests/game/player_actions_test.cpp new file mode 100644 index 000000000..11ae7338d --- /dev/null +++ b/tests/game/player_actions_test.cpp @@ -0,0 +1,119 @@ +#include "game_test_fixture.h" + +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include + +TEST(PlayerActionsTest, PlayCreatureSendsMoveToTable) +{ + GameHarness harness; + CardState *cat = harness.addCardToZone(ZoneNames::HAND, QStringLiteral("Cat"), 7); + ASSERT_NE(cat, nullptr); + + harness.player()->getPlayerActions()->playCard(cat, false); + + ASSERT_FALSE(harness.client()->sent.isEmpty()); + const CommandContainer &container = harness.lastCommand(); + ASSERT_EQ(container.game_command_size(), 1); + const Command_MoveCard command = container.game_command(0).GetExtension(Command_MoveCard::ext); + + EXPECT_EQ(command.start_player_id(), 1); + EXPECT_EQ(command.start_zone(), std::string(ZoneNames::HAND)); + EXPECT_EQ(command.target_player_id(), 1); + EXPECT_EQ(command.target_zone(), std::string(ZoneNames::TABLE)); + EXPECT_EQ(command.x(), -1); + EXPECT_EQ(command.y(), TableZoneLogic::tableRowToGridY(0)); + + ASSERT_EQ(command.cards_to_move().card_size(), 1); + const CardToMove &card = command.cards_to_move().card(0); + EXPECT_EQ(card.card_id(), 7); + EXPECT_TRUE(card.has_face_down()); + EXPECT_FALSE(card.face_down()); + EXPECT_TRUE(card.has_pt()); + EXPECT_EQ(card.pt(), "3/3"); + EXPECT_TRUE(card.has_tapped()); + EXPECT_FALSE(card.tapped()); +} + +TEST(PlayerActionsTest, PlayInstantSendsMoveToStack) +{ + GameHarness harness; + CardState *bolt = harness.addCardToZone(ZoneNames::HAND, QStringLiteral("Lightning Bolt"), 8); + ASSERT_NE(bolt, nullptr); + + harness.player()->getPlayerActions()->playCard(bolt, false); + + ASSERT_FALSE(harness.client()->sent.isEmpty()); + const CommandContainer &container = harness.lastCommand(); + ASSERT_EQ(container.game_command_size(), 1); + const Command_MoveCard command = container.game_command(0).GetExtension(Command_MoveCard::ext); + + EXPECT_EQ(command.target_zone(), std::string(ZoneNames::STACK)); + EXPECT_EQ(command.x(), -1); + EXPECT_EQ(command.y(), 0); + + ASSERT_EQ(command.cards_to_move().card_size(), 1); + const CardToMove &card = command.cards_to_move().card(0); + EXPECT_EQ(card.card_id(), 8); + // The stack branch deliberately leaves the other attributes unset. + EXPECT_FALSE(card.has_face_down()); + EXPECT_FALSE(card.has_pt()); + EXPECT_FALSE(card.has_tapped()); +} + +TEST(PlayerActionsTest, PlayFaceDownCreatureUsesFaceDownRow) +{ + GameHarness harness; + CardState *cat = harness.addCardToZone(ZoneNames::HAND, QStringLiteral("Cat"), 7); + ASSERT_NE(cat, nullptr); + + harness.player()->getPlayerActions()->playCard(cat, true); + + ASSERT_FALSE(harness.client()->sent.isEmpty()); + const CommandContainer &container = harness.lastCommand(); + ASSERT_EQ(container.game_command_size(), 1); + const Command_MoveCard command = container.game_command(0).GetExtension(Command_MoveCard::ext); + + EXPECT_EQ(command.target_zone(), std::string(ZoneNames::TABLE)); + EXPECT_EQ(command.x(), -1); + EXPECT_EQ(command.y(), TableZoneLogic::tableRowToGridY(2)); + + ASSERT_EQ(command.cards_to_move().card_size(), 1); + const CardToMove &card = command.cards_to_move().card(0); + EXPECT_EQ(card.card_id(), 7); + EXPECT_TRUE(card.has_face_down()); + EXPECT_TRUE(card.face_down()); + EXPECT_FALSE(card.has_pt()); + EXPECT_TRUE(card.has_tapped()); + EXPECT_FALSE(card.tapped()); +} + +TEST(PlayerActionsTest, PlayCiptCreatureSendsTapped) +{ + GameHarness harness; + CardState *bears = harness.addCardToZone(ZoneNames::HAND, QStringLiteral("Grizzly Bears"), 9); + ASSERT_NE(bears, nullptr); + + harness.player()->getPlayerActions()->playCard(bears, false); + + ASSERT_FALSE(harness.client()->sent.isEmpty()); + const CommandContainer &container = harness.lastCommand(); + ASSERT_EQ(container.game_command_size(), 1); + const Command_MoveCard command = container.game_command(0).GetExtension(Command_MoveCard::ext); + + EXPECT_EQ(command.target_zone(), std::string(ZoneNames::TABLE)); + + ASSERT_EQ(command.cards_to_move().card_size(), 1); + const CardToMove &card = command.cards_to_move().card(0); + EXPECT_EQ(card.card_id(), 9); + EXPECT_TRUE(card.has_face_down()); + EXPECT_FALSE(card.face_down()); + EXPECT_TRUE(card.has_pt()); + EXPECT_EQ(card.pt(), "2/2"); + EXPECT_TRUE(card.has_tapped()); + EXPECT_TRUE(card.tapped()); +} diff --git a/tests/game/replay_test.cpp b/tests/game/replay_test.cpp new file mode 100644 index 000000000..218ffdf51 --- /dev/null +++ b/tests/game/replay_test.cpp @@ -0,0 +1,349 @@ +/** + * @file replay_test.cpp + * + * Replays a synthetic GameReplay through the real dispatcher. This is the only + * place that exercises PlayerLogic::processPlayerInfo / processCardAttachment + * end to end: the game state arrives as a GAME_STATE_CHANGED event and the + * graphics side is replaced by a small materializer that mirrors what + * CardZone::onCreateCardRequested does with the logic objects. + * + * The replay is built in code rather than from a .cor file: event application + * is deterministic (the client never shuffles on its own for a replay), and a + * checked-in binary fixture would be opaque about what it asserts. + */ + +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ + +ServerInfo_Card makeCard(int id, const QString &name, int x = -1, int y = -1) +{ + ServerInfo_Card card; + card.set_id(id); + card.set_name(name.toStdString()); + card.set_x(x); + card.set_y(y); + return card; +} + +ServerInfo_Zone makeZone(const char *name, + ServerInfo_Zone::ZoneType type, + bool withCoords, + const QList &cards, + int cardCount = 0) +{ + ServerInfo_Zone zone; + zone.set_name(name); + zone.set_type(type); + zone.set_with_coords(withCoords); + for (const auto &card : cards) { + zone.add_card_list()->CopyFrom(card); + } + if (cardCount > 0) { + zone.set_card_count(cardCount); + } + return zone; +} + +ServerInfo_Player makePlayer(int playerId, const QString &name, const QList &zones) +{ + ServerInfo_Player player; + ServerInfo_PlayerProperties *properties = player.mutable_properties(); + properties->set_player_id(playerId); + properties->mutable_user_info()->set_name(name.toStdString()); + properties->mutable_user_info()->set_user_level(ServerInfo_User::IsUser); + for (const auto &zone : zones) { + player.add_zone_list()->CopyFrom(zone); + } + return player; +} + +template GameEventContainer makeContainer(int playerId, const EventType &message) +{ + GameEventContainer container; + container.set_game_id(42); + + GameEvent *event = container.add_event_list(); + event->set_player_id(playerId); + event->MutableExtension(EventType::ext)->CopyFrom(message); + + return container; +} + +/** + * Stands in for the graphics side of a replay: when the logic asks for a card + * item, create the CardState and add it to the zone, copying the state fields + * the real CardItem::processCardInfo applies. + * + * The production view owns the visual item; the state itself is all a headless + * test needs, and this is the smallest thing that lets processPlayerInfo build + * a real zone. + */ +class HeadlessZoneView : public QObject +{ +public: + using QObject::QObject; + + ~HeadlessZoneView() override + { + qDeleteAll(cards_); + cards_.clear(); + } + + void attach(PlayerLogic *player) + { + for (CardZoneLogic *zone : player->getZones()) { + connect(zone, &CardZoneLogic::createCardRequested, this, + [this, zone](const ServerInfo_Card &cardInfo, bool reorganize) { + materialize(zone, cardInfo, reorganize); + }); + } + } + +private: + void materialize(CardZoneLogic *zone, const ServerInfo_Card &info, bool reorganize) + { + const CardRef cardRef{QString::fromStdString(info.name()), QString::fromStdString(info.provider_id())}; + auto *card = new CardState(nullptr, zone, cardRef, info.id()); + // The real CardItem constructor refreshes the card info before + // processCardInfo overwrites the state fields. + card->refreshCardInfo(); + + card->clearCounters(); + for (int i = 0; i < info.counter_list_size(); ++i) { + card->insertCounter(info.counter_list(i).id(), info.counter_list(i).value()); + } + card->setAttacking(info.attacking()); + card->setFaceDown(info.face_down()); + card->setPT(QString::fromStdString(info.pt())); + card->setAnnotation(QString::fromStdString(info.annotation())); + card->setColor(QString::fromStdString(info.color())); + card->setTapped(info.tapped()); + card->setDestroyOnZoneChange(info.destroy_on_zone_change()); + card->setDoesntUntap(info.doesnt_untap()); + + zone->addCard(card, reorganize, info.x(), info.y()); + cards_.append(card); + } + + QList cards_; +}; + +/** Owns a Replay built from a GameReplay and feeds its containers through it. */ +class ReplayHarness +{ +public: + explicit ReplayHarness(GameReplay replay) + : replayProto_(std::move(replay)), view_(std::make_unique()), + replay_(std::make_unique(nullptr, &replayProto_, false)) + { + QObject::connect(replay_->getPlayerManager(), &PlayerManager::playerAdded, view_.get(), + &HeadlessZoneView::attach); + } + + ReplayHarness(const ReplayHarness &) = delete; + ReplayHarness &operator=(const ReplayHarness &) = delete; + + void processAll() + { + for (const GameEventContainer &container : replayProto_.event_list()) { + replay_->getGameEventHandler()->processGameEventContainer(container, nullptr, EventProcessingOptions()); + } + } + + [[nodiscard]] Replay *replay() const + { + return replay_.get(); + } + + [[nodiscard]] PlayerLogic *player(int playerId) const + { + return replay_->getPlayerManager()->getPlayer(playerId); + } + +private: + GameReplay replayProto_; + std::unique_ptr view_; + std::unique_ptr replay_; +}; + +GameReplay makeReplay(const QList &containers) +{ + GameReplay replay; + replay.mutable_game_info()->set_game_id(42); + replay.mutable_game_info()->set_started(true); + for (const auto &container : containers) { + replay.add_event_list()->CopyFrom(container); + } + return replay; +} + +Event_GameStateChanged initialGameState() +{ + Event_GameStateChanged state; + state.set_game_started(true); + state.set_active_player_id(1); + state.set_active_phase(0); + return state; +} + +} // namespace + +TEST(ReplayTest, MaterializesGameStateZonesAndAttachments) +{ + Event_GameStateChanged state = initialGameState(); + + ServerInfo_Card cat = makeCard(7, QStringLiteral("Cat")); + // Cross-player attachment: Alice's card is attached to Bob's creature. + cat.set_attach_player_id(2); + cat.set_attach_zone(ZoneNames::TABLE); + cat.set_attach_card_id(9); + + const ServerInfo_Player alice = makePlayer(1, QStringLiteral("Alice"), + {makeZone(ZoneNames::HAND, ServerInfo_Zone::PrivateZone, false, {cat}), + makeZone(ZoneNames::TABLE, ServerInfo_Zone::PublicZone, true, + {makeCard(9, QStringLiteral("Grizzly Bears"), 2, 1)}), + makeZone(ZoneNames::DECK, ServerInfo_Zone::HiddenZone, false, {}, 2)}); + const ServerInfo_Player bob = makePlayer(2, QStringLiteral("Bob"), + {makeZone(ZoneNames::TABLE, ServerInfo_Zone::PublicZone, true, + {makeCard(9, QStringLiteral("Grizzly Bears"), 1, 0)})}); + + state.add_player_list()->CopyFrom(alice); + state.add_player_list()->CopyFrom(bob); + + ReplayHarness harness(makeReplay({makeContainer(-1, state)})); + harness.processAll(); + + PlayerLogic *aliceLogic = harness.player(1); + PlayerLogic *bobLogic = harness.player(2); + ASSERT_NE(aliceLogic, nullptr); + ASSERT_NE(bobLogic, nullptr); + + CardZoneLogic *hand = aliceLogic->getZones().value(ZoneNames::HAND); + CardZoneLogic *table = aliceLogic->getZones().value(ZoneNames::TABLE); + CardZoneLogic *deck = aliceLogic->getZones().value(ZoneNames::DECK); + ASSERT_NE(hand, nullptr); + ASSERT_NE(table, nullptr); + ASSERT_NE(deck, nullptr); + + ASSERT_EQ(hand->getCards().size(), 1); + CardState *catState = hand->getCards().first(); + EXPECT_EQ(catState->getId(), 7); + EXPECT_EQ(catState->getName(), QStringLiteral("Cat")); + + ASSERT_EQ(table->getCards().size(), 1); + CardState *host = table->getCards().first(); + EXPECT_EQ(host->getName(), QStringLiteral("Grizzly Bears")); + EXPECT_EQ(host->getGridPoint(), QPoint(2, 1)); + + // The hidden deck only carries a count, and the view materializes that many + // unknown cards. + EXPECT_EQ(deck->getCards().size(), 2); + for (const CardState *hidden : deck->getCards()) { + EXPECT_EQ(hidden->getId(), -1); + } + + // processCardAttachment links both sides after every player is materialized. + CardZoneLogic *bobTable = bobLogic->getZones().value(ZoneNames::TABLE); + ASSERT_NE(bobTable, nullptr); + ASSERT_EQ(bobTable->getCards().size(), 1); + CardState *bobHost = bobTable->getCards().first(); + EXPECT_EQ(catState->getAttachedTo(), bobHost); + ASSERT_EQ(bobHost->getAttachedCards().size(), 1); + EXPECT_EQ(bobHost->getAttachedCards().first(), catState); + // The identically numbered card on Alice's own table is a different card. + EXPECT_TRUE(host->getAttachedCards().isEmpty()); + + EXPECT_TRUE(harness.replay()->getGameState()->isGameStateKnown()); +} + +TEST(ReplayTest, AppliesMovesAttributesAndCountersInOrder) +{ + Event_GameStateChanged state = initialGameState(); + state.add_player_list()->CopyFrom(makePlayer( + 1, QStringLiteral("Alice"), + {makeZone(ZoneNames::HAND, ServerInfo_Zone::PrivateZone, false, {makeCard(7, QStringLiteral("Cat"))}), + makeZone(ZoneNames::TABLE, ServerInfo_Zone::PublicZone, true, + {makeCard(9, QStringLiteral("Grizzly Bears"), 0, TableZoneLogic::tableRowToGridY(0))}), + makeZone(ZoneNames::DECK, ServerInfo_Zone::HiddenZone, false, {}, 2)})); + + Event_MoveCard move; + move.set_card_id(7); + move.set_start_player_id(1); + move.set_start_zone(ZoneNames::HAND); + move.set_position(0); + move.set_target_player_id(1); + move.set_target_zone(ZoneNames::TABLE); + move.set_x(3); + move.set_y(TableZoneLogic::tableRowToGridY(1)); + move.set_new_card_id(7); + move.set_face_down(false); + + Event_SetCardAttr tap; + tap.set_zone_name(ZoneNames::TABLE); + tap.set_card_id(7); + tap.set_attribute(AttrTapped); + tap.set_attr_value("1"); + + Event_SetCardCounter counter; + counter.set_zone_name(ZoneNames::TABLE); + counter.set_card_id(7); + counter.set_counter_id(1); + counter.set_counter_value(2); + + ReplayHarness harness(makeReplay( + {makeContainer(-1, state), makeContainer(1, move), makeContainer(1, tap), makeContainer(1, counter)})); + harness.processAll(); + + PlayerLogic *alice = harness.player(1); + ASSERT_NE(alice, nullptr); + CardZoneLogic *hand = alice->getZones().value(ZoneNames::HAND); + CardZoneLogic *table = alice->getZones().value(ZoneNames::TABLE); + ASSERT_NE(hand, nullptr); + ASSERT_NE(table, nullptr); + + EXPECT_TRUE(hand->getCards().isEmpty()); + ASSERT_EQ(table->getCards().size(), 2); + + CardState *moved = table->getCard(7); + ASSERT_NE(moved, nullptr); + EXPECT_EQ(moved->getGridPoint(), QPoint(3, TableZoneLogic::tableRowToGridY(1))); + EXPECT_EQ(moved->getPT(), QStringLiteral("3/3")); + EXPECT_TRUE(moved->getTapped()); + EXPECT_EQ(moved->getCounters().value(1), 2); + + // The untouched creature keeps its own position and state. + CardState *bears = table->getCard(9); + ASSERT_NE(bears, nullptr); + EXPECT_EQ(bears->getGridPoint(), QPoint(0, TableZoneLogic::tableRowToGridY(0))); + EXPECT_FALSE(bears->getTapped()); +} diff --git a/tests/holder_line_test.cpp b/tests/holder_line_test.cpp new file mode 100644 index 000000000..8588d8515 --- /dev/null +++ b/tests/holder_line_test.cpp @@ -0,0 +1,102 @@ +#include "holder_line.h" + +#include +#include + +namespace +{ +Holder runningProcess(const std::wstring &imageName, unsigned long processId) +{ + Holder holder; + holder.imageName = imageName; + holder.processId = processId; + return holder; +} + +Holder service(const std::wstring &appName, const std::wstring &shortName, unsigned long processId) +{ + Holder holder; + holder.appName = appName; + holder.serviceShortName = shortName; + holder.processId = processId; + holder.isService = true; + return holder; +} +} // namespace + +TEST(HolderLine, NamesARunningProcessTheWayTaskManagerDoes) +{ + EXPECT_EQ(formatHolder(runningProcess(L"cockatrice.exe", 24340)), + std::wstring(L"cockatrice.exe (pid 24340, running)")); +} + +TEST(HolderLine, NamesAServiceByTheNameTheUserCanSearchFor) +{ + // The image of a service is often a shared host process, so the image name would + // point at the wrong thing; both of the service's own names are kept. + Holder holder = service(L"Sophos Anti-Virus", L"SAVService", 4412); + holder.imageName = L"svchost.exe"; + EXPECT_EQ(formatHolder(holder), std::wstring(L"Sophos Anti-Virus (pid 4412, service SAVService)")); +} + +TEST(HolderLine, FallsBackFromTheImageToTheApplicationName) +{ + // Restart Manager can name a process whose image this one is not allowed to open. + Holder holder; + holder.appName = L"Cockatrice"; + holder.processId = 7; + EXPECT_EQ(formatHolder(holder), std::wstring(L"Cockatrice (pid 7, running)")); +} + +TEST(HolderLine, NamesAnEmptyHolderRatherThanPrintingAnEmptyParenthesis) +{ + Holder holder; + holder.processId = 9; + EXPECT_EQ(formatHolder(holder), std::wstring(L"an unnamed process (pid 9, running)")); +} + +TEST(HolderLine, JoinsHoldersIntoOneLine) +{ + const std::vector holders = { + runningProcess(L"cockatrice.exe", 1), + runningProcess(L"oracle.exe", 2), + }; + EXPECT_EQ(formatHolders(holders), std::wstring(L"cockatrice.exe (pid 1, running); oracle.exe (pid 2, running)")); +} + +TEST(HolderLine, CountsTheHoldersItLeavesOut) +{ + std::vector holders; + for (unsigned long processId = 1; processId <= HOLDER_LINE_MAX + 2; ++processId) { + holders.push_back(runningProcess(L"cockatrice.exe", processId)); + } + const std::wstring line = formatHolders(holders); + // The dialog has to stay readable, but a list that silently stopped at five would + // hide that there is more to close. + EXPECT_NE(line.find(L"pid 5, running"), std::wstring::npos); + EXPECT_EQ(line.find(L"pid 6, running"), std::wstring::npos); + EXPECT_NE(line.find(L"and 2 more"), std::wstring::npos); +} + +TEST(HolderLine, StopsNamingHoldersBeforeTheLineOutgrowsTheDialog) +{ + // A Windows service name can be 255 characters, and the abort dialog has to fit the + // list next to its own text and the log path in a 1024-character NSIS string. + std::vector holders; + for (unsigned long processId = 1; processId <= 3; ++processId) { + holders.push_back(service(std::wstring(255, L'S'), L"S", processId)); + } + const std::wstring line = formatHolders(holders); + // The first holder is still named in full; the others are counted rather than written + // out until the line overflows. + EXPECT_EQ(line.compare(0, 255, std::wstring(255, L'S')), 0); + EXPECT_NE(line.find(L"(pid 1, service S)"), std::wstring::npos); + EXPECT_EQ(line.find(L"pid 2"), std::wstring::npos); + EXPECT_NE(line.find(L"and 2 more"), std::wstring::npos); + EXPECT_LE(line.size(), HOLDER_LINE_CHAR_MAX + 20); +} + +TEST(HolderLine, FormatsNothingAsAnEmptyLine) +{ + EXPECT_EQ(formatHolders({}), std::wstring()); +} diff --git a/tests/installer_close_server_test.cpp b/tests/installer_close_server_test.cpp new file mode 100644 index 000000000..158d0ce47 --- /dev/null +++ b/tests/installer_close_server_test.cpp @@ -0,0 +1,101 @@ +#include "installer_close_name.h" +#include "installer_close_server.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +constexpr int HANDSHAKE_TIMEOUT_MS = 5000; + +/// The bytes both ends exchange, from the header they both include. The separate +/// WireCommandIsThePinnedBytes test is what catches an accidental change to the literal. +const QByteArray CLOSE_COMMAND = QByteArray::fromRawData(INSTALLER_CLOSE_COMMAND, sizeof(INSTALLER_CLOSE_COMMAND) - 1); + +/// Scopes the socket to this test process, so a Cockatrice the developer happens to be +/// running cannot claim the name and the test cannot close it. Kept short on purpose: +/// QLocalServer on Unix puts the socket file under QDir::tempPath(), and macOS caps that +/// path at 104 bytes (sun_path). A longer name passes on Linux (/tmp) and fails to listen +/// on the macOS CI runners, whose temp directory is already ~48 characters. +QByteArray testUserName() +{ + return QByteArrayLiteral("t") + QByteArray::number(QCoreApplication::applicationPid()); +} +} // namespace + +// The probe builds the name from the process id and the account name it reads from the +// environment; the client builds it with scopedLocalServerName(). They have to produce the +// same string or the installer can never reach the instance it is trying to close. +TEST(InstallerCloseServerTest, SocketNameMatchesTheProbeForTheSameProcessAndUser) +{ + constexpr unsigned long PROCESS_ID = 4242; + const QString userName = QStringLiteral("installer-test"); + const QString expected = QStringLiteral("CockatriceInstallerClose-4242-installer-test"); + + EXPECT_EQ(expected, InstallerCloseServer::socketNameForUser(PROCESS_ID, userName)); + EXPECT_EQ(expected.toStdWString(), installerCloseSocketName(PROCESS_ID, userName.toStdWString())); +} + +TEST(InstallerCloseServerTest, SocketNameScopesTheProcessIdByTheCurrentUser) +{ + qputenv("USER", testUserName()); + + EXPECT_EQ(QStringLiteral("CockatriceInstallerClose-4242-") + QString::fromLatin1(testUserName()), + InstallerCloseServer::socketName(4242)); +} + +// The line is a wire format shared with the Windows-only probe, which cannot be compiled +// into this test. Pinning the exact bytes here makes a change to the shared header a +// deliberate protocol change rather than a typo that only shows up in the installer. +TEST(InstallerCloseServerTest, WireCommandIsThePinnedBytes) +{ + EXPECT_EQ(QByteArrayLiteral("COCKATRICE_INSTALLER_CLOSE\n"), CLOSE_COMMAND); + EXPECT_EQ(27, CLOSE_COMMAND.size()); +} + +// The installer only writes the command; the listening instance is what turns it into a +// close. Anything but that exact line must not be treated as a request. +TEST(InstallerCloseServerTest, CloseRequestIsEmittedForTheProbeCommand) +{ + qputenv("USER", testUserName()); + + InstallerCloseServer server; + ASSERT_TRUE(server.listen()) << "the installer close socket could not be started"; + + int closeRequests = 0; + QObject::connect(&server, &InstallerCloseServer::closeRequested, &server, [&closeRequests]() { ++closeRequests; }); + + QLocalSocket socket; + socket.connectToServer(InstallerCloseServer::socketName(QCoreApplication::applicationPid())); + ASSERT_TRUE(socket.waitForConnected(HANDSHAKE_TIMEOUT_MS)) << "could not reach the installer close socket"; + ASSERT_EQ(CLOSE_COMMAND.size(), socket.write(CLOSE_COMMAND)) << "the close request was not accepted"; + + // No waitForBytesWritten(): on Windows QLocalSocket completes the write through the + // event dispatcher, and this test runs the server in the same thread, so a blocking + // wait would hold the loop the server needs to accept and read. The loop below is the + // real handshake: it flushes the write and delivers the server's readyRead. + QEventLoop loop; + QTimer timeout; + timeout.setSingleShot(true); + QObject::connect(&timeout, &QTimer::timeout, &loop, &QEventLoop::quit); + QObject::connect(&server, &InstallerCloseServer::closeRequested, &loop, &QEventLoop::quit); + timeout.start(HANDSHAKE_TIMEOUT_MS); + loop.exec(); + + EXPECT_EQ(1, closeRequests) << "the probe's line must reach the close handler exactly once"; +} + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/local_client_lifetime_test.cpp b/tests/local_client_lifetime_test.cpp new file mode 100644 index 000000000..95a2b1380 --- /dev/null +++ b/tests/local_client_lifetime_test.cpp @@ -0,0 +1,149 @@ +/** + * @file local_client_lifetime_test.cpp + * @brief Tests that a LocalClient survives the death of the LocalServer it talks to. + * @ingroup Tests + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// The server library refers to this global; the real applications define it in main.cpp. +RNG_Abstract *rng = nullptr; + +namespace +{ + +// GTest's main() creates no QCoreApplication, which QObject::deleteLater() needs. +const QCoreApplication &application() +{ + static int argc = 1; + static char executable[] = "local_client_lifetime_test"; + static char *argv[] = {executable, nullptr}; + static const QCoreApplication app(argc, argv); + return app; +} + +/// Collects the warnings one logging category emits while this object is alive. +/// +/// The handler is process-global and Qt does not nest handlers, so only one capture can be +/// installed at a time; GTest running one case at a time is what makes that safe. +class CategoryWarningCapture +{ +public: + explicit CategoryWarningCapture(const char *_category) : category(_category) + { + previousHandler = qInstallMessageHandler(captureMessage); + s_active = this; + } + + ~CategoryWarningCapture() + { + qInstallMessageHandler(previousHandler); + s_active = nullptr; + } + + CategoryWarningCapture(const CategoryWarningCapture &) = delete; + CategoryWarningCapture &operator=(const CategoryWarningCapture &) = delete; + + const QStringList &warnings() const + { + return capturedWarnings; + } + +private: + static void captureMessage(QtMsgType type, const QMessageLogContext &context, const QString &message) + { + if (s_active != nullptr && type == QtWarningMsg && context.category != nullptr && + std::strcmp(s_active->category, context.category) == 0) { + s_active->capturedWarnings.append(message); + } + + // Forward everything else, so a failure elsewhere in the test is still visible. + if (previousHandler != nullptr) { + previousHandler(type, context, message); + return; + } + std::fprintf(stderr, "%s\n", qPrintable(qFormatLogMessage(type, context, message))); + } + + const char *category; + QStringList capturedWarnings; + + static CategoryWarningCapture *s_active; + static QtMessageHandler previousHandler; +}; + +CategoryWarningCapture *CategoryWarningCapture::s_active = nullptr; +QtMessageHandler CategoryWarningCapture::previousHandler = nullptr; + +class LocalClientLifetimeTest : public ::testing::Test +{ +protected: + void SetUp() override + { + (void)application(); + + server = std::make_unique(); + LocalServerInterface *lsi = server->newConnection(); + ASSERT_NE(lsi, nullptr); + // Building the client logs in and joins room 0 through that interface, exactly as + // MainWindow::startLocalGame() does. + client = std::make_unique(lsi, QStringLiteral("lifetime-test-player"), + QStringLiteral("lifetime-test-client-id")); + } + + void TearDown() override + { + // Either case may already have deleted one of these. + client.reset(); + server.reset(); + } + + std::unique_ptr server; + std::unique_ptr client; +}; + +TEST_F(LocalClientLifetimeTest, SendAfterServerDeletedIsDropped) +{ + server.reset(); // also destroys the LocalServerInterface the client was built on + + CategoryWarningCapture capture("local_client"); + // A default container stands in for a command still queued when the local game ended. + client->sendCommandContainer(CommandContainer()); + + ASSERT_EQ(capture.warnings().size(), 1); + EXPECT_TRUE(capture.warnings().first().contains(QStringLiteral("dropping command"))) + << capture.warnings().first().toStdString(); + + // The surviving client is intact: reading its state must not touch the deleted interface. + EXPECT_EQ(client->getUserName(), QStringLiteral("lifetime-test-player")); +} + +TEST_F(LocalClientLifetimeTest, DestroyingClientAfterServerIsSafe) +{ + server.reset(); + + CategoryWarningCapture capture("local_client"); + client.reset(); // must neither crash nor touch the destroyed interface + + // The destructor itself must have been silent. + EXPECT_TRUE(capture.warnings().isEmpty()); + + // A working capture proves the emptiness above is real rather than a broken handler. + qCWarning(LocalClientLog) << "local_client_lifetime_test capture self-check"; + EXPECT_EQ(capture.warnings().size(), 1); +} + +} // namespace diff --git a/tests/model_contract/CMakeLists.txt b/tests/model_contract/CMakeLists.txt new file mode 100644 index 000000000..3251c1443 --- /dev/null +++ b/tests/model_contract/CMakeLists.txt @@ -0,0 +1,17 @@ +cockatrice_add_test( + NAME + model_contract_test + SOURCES + ${VERSION_STRING_CPP} + model_contract_test.cpp + LIBS + libcockatrice_models + libcockatrice_card + libcockatrice_deck_list + libcockatrice_settings + INCLUDE_DIRS + ${CMAKE_CURRENT_SOURCE_DIR}/../support + ${CMAKE_CURRENT_SOURCE_DIR}/../carddatabase + DEFINITIONS + CARDDB_DATADIR="${CMAKE_SOURCE_DIR}/tests/carddatabase/data/" +) diff --git a/tests/model_contract/model_contract_test.cpp b/tests/model_contract/model_contract_test.cpp new file mode 100644 index 000000000..aefa26f3d --- /dev/null +++ b/tests/model_contract/model_contract_test.cpp @@ -0,0 +1,137 @@ +#include "libcockatrice/deck_list/deck_list.h" +#include "libcockatrice/deck_list/tree/inner_deck_list_node.h" +#include "libcockatrice/models/database/card_database_display_model.h" +#include "libcockatrice/models/database/card_database_model.h" +#include "libcockatrice/models/database/card_set/card_sets_model.h" +#include "libcockatrice/models/deck_list/deck_list_model.h" +#include "model_contract_checker.h" +#include "test_card_database_path_provider.h" + +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace +{ +QSharedPointer makeDeck() +{ + return QSharedPointer(new DeckList()); +} + +void loadCardDatabase(CardDatabase &database) +{ + database.loadCardDatabases(); + ASSERT_EQ(database.getLoadStatus(), Ok); +} + +TEST(ModelContract, DeckListModelKeepsContractWhileZonesAndCardsChange) +{ + const QSharedPointer deck = makeDeck(); + DeckListModel model(nullptr, deck); + + test_support::ModelContractChecker checker(&model); + + ASSERT_NE(deck->getTree()->addCustomZone(DECK_ZONE_MAIN, "Removal"), nullptr); + model.rebuildTree(); + + CardInfoPtr bolt = CardInfo::newInstance("Lightning Bolt"); + const QModelIndex added = model.addCard(ExactCard(bolt), DECK_ZONE_MAIN); + ASSERT_TRUE(added.isValid()); + + CardInfoPtr bears = CardInfo::newInstance("Grizzly Bears"); + bears->setProperty(Mtg::MainCardType, "Creature"); + const QModelIndex grouped = model.addCard(ExactCard(bears), "Removal"); + ASSERT_TRUE(grouped.isValid()); + + ASSERT_TRUE(model.removeRows(grouped.row(), 1, grouped.parent())); +} + +TEST(ModelContract, DeckListModelEmitsCardAddedSignal) +{ + const QSharedPointer deck = makeDeck(); + DeckListModel model(nullptr, deck); + QSignalSpy spy(&model, &DeckListModel::cardAddedAt); + + const QModelIndex added = model.addCard(ExactCard(CardInfo::newInstance("Lightning Bolt")), DECK_ZONE_MAIN); + + ASSERT_TRUE(added.isValid()); + EXPECT_EQ(spy.count(), 1); +} + +TEST(ModelContract, CardDatabaseModelAndDisplayProxyKeepContract) +{ + CardDatabase database(nullptr, new NoopCardPreferenceProvider(), new TestCardDatabasePathProvider(), + new NoopCardSetPriorityController()); + ASSERT_NO_FATAL_FAILURE(loadCardDatabase(database)); + + CardDatabaseModel source(&database, false); + test_support::ModelContractChecker sourceChecker(&source); + + CardDatabaseDisplayModel display; + display.setSourceModel(&source); + test_support::ModelContractChecker displayChecker(&display); + + ASSERT_GT(source.rowCount(), 0); + EXPECT_EQ(display.rowCount(), source.rowCount()); + + // A card row is a leaf, so its index must not claim child columns. The + // tester does not check this, hence the explicit assertion. + const QModelIndex sourceCard = source.index(0, 0); + ASSERT_TRUE(sourceCard.isValid()); + EXPECT_EQ(source.columnCount(sourceCard), 0); + const QModelIndex displayCard = display.index(0, 0); + ASSERT_TRUE(displayCard.isValid()); + EXPECT_EQ(display.columnCount(displayCard), 0); + + QSignalSpy dirtySpy(&display, &CardDatabaseDisplayModel::modelDirty); + display.setCardName("Do"); + ASSERT_TRUE(dirtySpy.wait()); + EXPECT_GT(display.rowCount(), 0); + EXPECT_LT(display.rowCount(), source.rowCount()); +} + +TEST(ModelContract, SetsModelKeepsContract) +{ + CardDatabase database(nullptr, new NoopCardPreferenceProvider(), new TestCardDatabasePathProvider(), + new NoopCardSetPriorityController()); + ASSERT_NO_FATAL_FAILURE(loadCardDatabase(database)); + + SetsModel model(&database); + test_support::ModelContractChecker checker(&model); + + ASSERT_GT(model.rowCount(), 0); + ASSERT_GT(model.columnCount(), 0); +} + +TEST(ModelContract, SettingsSignalIsObservableWithQSignalSpy) +{ + QTemporaryDir tempDir; + ASSERT_TRUE(tempDir.isValid()); + DownloadSettings settings(tempDir.path() + "/", nullptr); + QSignalSpy spy(&settings, &DownloadSettings::downloadSpoilerStatusChanged); + + settings.setDownloadSpoilerStatus(true); + + ASSERT_EQ(spy.count(), 1); +} +} // namespace + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/password_hash_test.cpp b/tests/password_hash_test.cpp index 2a504b81c..8e7eded72 100644 --- a/tests/password_hash_test.cpp +++ b/tests/password_hash_test.cpp @@ -1,7 +1,9 @@ #include "gtest/gtest.h" #include #include +#include #include +#include #include #include @@ -39,6 +41,215 @@ TEST(PasswordHashTest, TokenHasExpectedLength) const QString token = PasswordHasher::generateActivationToken(); ASSERT_EQ(token.size(), 16); } + +TEST(PasswordHashTest, DeriveKeyMatchesKnownVector) +{ + // RFC 7914 scrypt test vector, P="password", S="NaCl", N=1024, r=8, p=16 + const QByteArray expected = QByteArray::fromHex("fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b" + "3731622eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cb" + "dfa2cc0640"); + const QByteArray derived = PasswordHasher::deriveKey("password", QByteArray("NaCl"), 1024, 8, 16); + ASSERT_EQ(derived.toHex(), expected.toHex()); +} + +TEST(PasswordHashTest, DeriveKeyCoversTheWholeAcceptedRangeOfR) +{ + // costParamsAreSane accepts r up to 32. The memory budget handed to OpenSSL has to cover + // scrypt's XY array (256 * r bytes) on top of V and B, or derivation fails for r >= 17. + for (const int r : {17, 32}) { + const QByteArray derived = PasswordHasher::deriveKey("password", QByteArray("NaCl"), 1024, r, 1); + ASSERT_FALSE(derived.isEmpty()) << "deriveKey failed for accepted r=" << r; + ASSERT_EQ(derived.size(), SCRYPT_VERIFIER_LENGTH); + } +} + +TEST(PasswordHashTest, PasswordVerifierRoundTrip) +{ + const QString stored = PasswordHasher::generatePasswordVerifier("hunter2"); + ASSERT_FALSE(stored.isEmpty()); + ASSERT_TRUE(stored.startsWith("$scrypt$")); + + const PasswordVerifier parsed = PasswordHasher::parsePasswordVerifier(stored); + ASSERT_TRUE(parsed.isValid); + ASSERT_EQ(parsed.format, PasswordFormat::Scrypt); + ASSERT_EQ(parsed.n, SCRYPT_N); + ASSERT_EQ(parsed.r, SCRYPT_R); + ASSERT_EQ(parsed.p, SCRYPT_P); + ASSERT_EQ(parsed.salt.size(), SCRYPT_SALT_LENGTH); + ASSERT_EQ(parsed.verifier.size(), SCRYPT_VERIFIER_LENGTH); +} + +TEST(PasswordHashTest, PasswordVerifierInvalidInput) +{ + ASSERT_FALSE(PasswordHasher::parsePasswordVerifier("garbage").isValid); + ASSERT_FALSE(PasswordHasher::parsePasswordVerifier("$scrypt$not-an-int$8$1$AAAA$BBBB").isValid); + ASSERT_FALSE(PasswordHasher::parsePasswordVerifier("$scrypt$1024$8$1$AAAA$too-short").isValid); + ASSERT_FALSE(PasswordHasher::parsePasswordVerifier("$pbkdf2-sha512$1000$AAAA$BBBB").isValid); +} + +TEST(PasswordHashTest, LegacyFormatDetection) +{ + ASSERT_TRUE(PasswordHasher::isLegacyFormat("salt+hash")); + ASSERT_FALSE(PasswordHasher::isLegacyFormat(PasswordHasher::generatePasswordVerifier("password"))); +} + +TEST(PasswordHashTest, DeriveKeyDependsOnCostParameters) +{ + const QByteArray keyA = PasswordHasher::deriveKey("password", QByteArray("NaCl"), 1024, 8, 16); + const QByteArray keyB = PasswordHasher::deriveKey("password", QByteArray("NaCl"), 2048, 8, 16); + const QByteArray keyC = PasswordHasher::deriveKey("password", QByteArray("NaCl"), 1024, 8, 1); + ASSERT_NE(keyA, keyB); + ASSERT_NE(keyA, keyC); +} + +TEST(PasswordHashTest, ComputeResponseIsDeterministic) +{ + const QByteArray nonce = QByteArray("a nonce value"); + const QByteArray key = QByteArray("the verifier bytes"); + const QByteArray r1 = PasswordHasher::computeResponse(key, nonce); + const QByteArray r2 = PasswordHasher::computeResponse(key, nonce); + const QByteArray r3 = PasswordHasher::computeResponse(QByteArray("a different key"), nonce); + ASSERT_EQ(r1, r2); + ASSERT_NE(r1, r3); +} + +TEST(PasswordHashTest, ConstantTimeEquals) +{ + ASSERT_TRUE(PasswordHasher::constantTimeEquals(QByteArray("same"), QByteArray("same"))); + ASSERT_FALSE(PasswordHasher::constantTimeEquals(QByteArray("same"), QByteArray("diff"))); + ASSERT_FALSE(PasswordHasher::constantTimeEquals(QByteArray("short"), QByteArray("longer"))); +} + +TEST(PasswordHashTest, CostParamsAreSane) +{ + // Accept the recommended interactive parameters and the RFC 7914 test vector's. + ASSERT_TRUE(PasswordHasher::costParamsAreSane(SCRYPT_N, SCRYPT_R, SCRYPT_P)); + ASSERT_TRUE(PasswordHasher::costParamsAreSane(1024, 8, 16)); + + // n must be in [1024, 2**20] and a power of two. + ASSERT_FALSE(PasswordHasher::costParamsAreSane(512, 8, 1)); + ASSERT_FALSE(PasswordHasher::costParamsAreSane(1 << 21, 8, 1)); + ASSERT_FALSE(PasswordHasher::costParamsAreSane(1025, 8, 1)); + ASSERT_FALSE(PasswordHasher::costParamsAreSane(0, 8, 1)); + ASSERT_FALSE(PasswordHasher::costParamsAreSane(-1024, 8, 1)); + + // r in [1, 32], p in [1, 16]. + ASSERT_FALSE(PasswordHasher::costParamsAreSane(1024, 0, 1)); + ASSERT_FALSE(PasswordHasher::costParamsAreSane(1024, 33, 1)); + ASSERT_FALSE(PasswordHasher::costParamsAreSane(1024, 8, 0)); + ASSERT_FALSE(PasswordHasher::costParamsAreSane(1024, 8, 17)); +} + +TEST(PasswordHashTest, ParsePasswordVerifierRejectsHostileCostParams) +{ + // 16 bytes of salt and 64 bytes of verifier, base64 encoded. + const QString saltB64 = QLatin1String("c2FsdHNhbHRzYWx0c2FsdA=="); + const QString verifierB64 = QString(QByteArray(SCRYPT_VERIFIER_LENGTH, '\x42').toBase64()); + + ASSERT_TRUE( + PasswordHasher::parsePasswordVerifier(QString("$scrypt$1024$8$1$%1$%2").arg(saltB64).arg(verifierB64)).isValid); + + // n not a power of two, below 1024, or above 2**20. + ASSERT_FALSE( + PasswordHasher::parsePasswordVerifier(QString("$scrypt$1025$8$1$%1$%2").arg(saltB64).arg(verifierB64)).isValid); + ASSERT_FALSE( + PasswordHasher::parsePasswordVerifier(QString("$scrypt$512$8$1$%1$%2").arg(saltB64).arg(verifierB64)).isValid); + ASSERT_FALSE( + PasswordHasher::parsePasswordVerifier(QString("$scrypt$1073741824$8$1$%1$%2").arg(saltB64).arg(verifierB64)) + .isValid); + + // r and p out of range. + ASSERT_FALSE(PasswordHasher::parsePasswordVerifier(QString("$scrypt$1024$33$1$%1$%2").arg(saltB64).arg(verifierB64)) + .isValid); + ASSERT_FALSE(PasswordHasher::parsePasswordVerifier(QString("$scrypt$1024$8$17$%1$%2").arg(saltB64).arg(verifierB64)) + .isValid); +} + +TEST(PasswordHashTest, VerifyPasswordLegacyRow) +{ + const QString salt = PasswordHasher::generateRandomSalt(); + const QString legacyStored = PasswordHasher::computeHash("correct horse", salt); + ASSERT_TRUE(PasswordHasher::verifyPassword("correct horse", legacyStored)); + ASSERT_FALSE(PasswordHasher::verifyPassword("battery staple", legacyStored)); +} + +TEST(PasswordHashTest, VerifyPasswordScryptRow) +{ + const QString scryptStored = PasswordHasher::generatePasswordVerifier("correct horse"); + // Regression for the changeUserPassword bug that re-hashed the old password with + // a 16-char salt torn out of the "$scrypt$..." string, which could never match. + ASSERT_TRUE(PasswordHasher::verifyPassword("correct horse", scryptStored)); + ASSERT_FALSE(PasswordHasher::verifyPassword("battery staple", scryptStored)); + ASSERT_FALSE(PasswordHasher::verifyPassword("correct horse", "garbage")); +} + +TEST(CryptoUtilTest, SecretRoundTrip) +{ + const QByteArray key = CryptoUtil::randomBytes(32); + const QByteArray plaintext = PasswordHasher::computeHash("correct horse", "saltsaltsaltsalt").toUtf8(); + const QByteArray aad = "alice"; + + const QString sealed = CryptoUtil::encryptSecret(plaintext, key, aad); + ASSERT_FALSE(sealed.isEmpty()); + ASSERT_TRUE(sealed.startsWith("enc1:")); + ASSERT_EQ(CryptoUtil::decryptSecret(sealed, key, aad), plaintext); +} + +TEST(CryptoUtilTest, SealIsNonDeterministic) +{ + const QByteArray key = CryptoUtil::randomBytes(32); + const QByteArray plaintext = "same secret"; + // Fresh random nonce per seal: identical inputs must not produce identical blobs. + ASSERT_NE(CryptoUtil::encryptSecret(plaintext, key), CryptoUtil::encryptSecret(plaintext, key)); +} + +TEST(CryptoUtilTest, RejectsWrongKey) +{ + const QByteArray key = CryptoUtil::randomBytes(32); + const QByteArray otherKey = CryptoUtil::randomBytes(32); + const QString sealed = CryptoUtil::encryptSecret("secret", key, "alice"); + ASSERT_FALSE(sealed.isEmpty()); + ASSERT_TRUE(CryptoUtil::decryptSecret(sealed, otherKey, "alice").isEmpty()); +} + +TEST(CryptoUtilTest, RejectsWrongAad) +{ + const QByteArray key = CryptoUtil::randomBytes(32); + const QString sealed = CryptoUtil::encryptSecret("secret", key, "alice"); + ASSERT_FALSE(sealed.isEmpty()); + ASSERT_TRUE(CryptoUtil::decryptSecret(sealed, key, "bob").isEmpty()); + ASSERT_TRUE(CryptoUtil::decryptSecret(sealed, key).isEmpty()); +} + +TEST(CryptoUtilTest, RejectsTampering) +{ + const QByteArray key = CryptoUtil::randomBytes(32); + const QString sealed = CryptoUtil::encryptSecret("secret", key); + ASSERT_FALSE(sealed.isEmpty()); + + // Flip a character inside the ciphertext segment (keep it valid base64). + QStringList parts = sealed.split(':'); + ASSERT_EQ(parts.size(), 3); + QByteArray ciphertext = QByteArray::fromBase64(parts.at(2).toUtf8()); + ciphertext[0] = static_cast(ciphertext.at(0) ^ 0x01); + parts[2] = QString(ciphertext.toBase64()); + ASSERT_TRUE(CryptoUtil::decryptSecret(parts.join(':'), key).isEmpty()); +} + +TEST(CryptoUtilTest, RejectsMalformedInput) +{ + const QByteArray key = CryptoUtil::randomBytes(32); + ASSERT_TRUE(CryptoUtil::decryptSecret("garbage", key).isEmpty()); + ASSERT_TRUE(CryptoUtil::decryptSecret("enc1:notbase64!!:also-not!", key).isEmpty()); + ASSERT_TRUE(CryptoUtil::decryptSecret("enc1:AAAAAAAAAAAAAAAA:AAAA", key).isEmpty()); + ASSERT_TRUE(CryptoUtil::decryptSecret("", key).isEmpty()); + // A short or oversized key must never reach the cipher. + ASSERT_TRUE( + CryptoUtil::decryptSecret("enc1:AAAAAAAAAAAAAAAA:AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", CryptoUtil::randomBytes(16)) + .isEmpty()); + ASSERT_TRUE(CryptoUtil::encryptSecret("secret", CryptoUtil::randomBytes(16)).isEmpty()); +} + } // namespace int main(int argc, char **argv) diff --git a/tests/payload_paths_test.cpp b/tests/payload_paths_test.cpp new file mode 100644 index 000000000..c4576d751 --- /dev/null +++ b/tests/payload_paths_test.cpp @@ -0,0 +1,57 @@ +#include "payload_paths.h" + +#include +#include + +namespace +{ +/// The directory part of a path spelled with the same case as the install directory, which +/// is the comparison the probe makes once both sides are folded - see isSameDirectory in +/// restart_manager_holders.cpp. Kept as a plain string here so the shape of a path can be +/// tested without Windows. +bool runsFrom(const std::wstring &imagePath, const std::wstring &installDir) +{ + return parentDirectoryOf(imagePath) == installDir; +} +} // namespace + +TEST(PayloadPaths, SplitsAnImagePathIntoItsDirectoryAndName) +{ + EXPECT_EQ(fileNameOf(L"D:\\Games\\Cockatrice\\cockatrice.exe"), std::wstring(L"cockatrice.exe")); + EXPECT_EQ(parentDirectoryOf(L"D:\\Games\\Cockatrice\\cockatrice.exe"), std::wstring(L"D:\\Games\\Cockatrice")); + EXPECT_EQ(fileNameOf(L"cockatrice.exe"), std::wstring(L"cockatrice.exe")); + EXPECT_EQ(parentDirectoryOf(L"cockatrice.exe"), std::wstring()); + EXPECT_EQ(parentDirectoryOf(L"D:/Games/Cockatrice/cockatrice.exe"), std::wstring(L"D:/Games/Cockatrice")); +} + +TEST(PayloadPaths, OnlyAnImageInTheInstallDirectoryRunsFromIt) +{ + const std::wstring installDir = L"D:\\Games\\Cockatrice"; + EXPECT_TRUE(runsFrom(L"D:\\Games\\Cockatrice\\cockatrice.exe", installDir)); + // A folder the user keeps other applications in is not the installation, and neither is + // a sibling whose name merely begins with the same letters. + EXPECT_FALSE(runsFrom(L"D:\\Games\\Cockatrice\\old\\cockatrice.exe", installDir)); + EXPECT_FALSE(runsFrom(L"D:\\Games\\Cockatrice-old\\cockatrice.exe", installDir)); + EXPECT_FALSE(runsFrom(L"D:\\Games\\cockatrice.exe", installDir)); +} + +TEST(PayloadPaths, KnowsTheProgramsThePayloadInstalls) +{ + EXPECT_TRUE(isPayloadProgram(L"cockatrice.exe")); + EXPECT_TRUE(isPayloadProgram(L"oracle.exe")); + EXPECT_TRUE(isPayloadProgram(L"servatrice.exe")); + EXPECT_TRUE(isPayloadProgram(L"COCKATRICE.EXE")); + EXPECT_TRUE(isPayloadProgram(L"Servatrice.Exe")); +} + +TEST(PayloadPaths, LeavesEveryOtherProgramAlone) +{ + // A program the user keeps in the install directory, the uninstaller, a service host and + // a name that only looks like a payload program are all not instances of the payload. + EXPECT_FALSE(isPayloadProgram(L"uninstall.exe")); + EXPECT_FALSE(isPayloadProgram(L"tool.exe")); + EXPECT_FALSE(isPayloadProgram(L"svchost.exe")); + EXPECT_FALSE(isPayloadProgram(L"cockatrice2.exe")); + EXPECT_FALSE(isPayloadProgram(L"mycockatrice.exe")); + EXPECT_FALSE(isPayloadProgram(L"")); +} diff --git a/tests/picture_loader_benchmark/CMakeLists.txt b/tests/picture_loader_benchmark/CMakeLists.txt index 0684f58f4..9b8d15999 100644 --- a/tests/picture_loader_benchmark/CMakeLists.txt +++ b/tests/picture_loader_benchmark/CMakeLists.txt @@ -15,8 +15,9 @@ cockatrice_add_manual_test( settings_cache_mock.cpp LIBS libcockatrice_card - libcockatrice_settings libcockatrice_interfaces + libcockatrice_network + libcockatrice_settings libcockatrice_utility INCLUDE_DIRS ${CMAKE_SOURCE_DIR}/cockatrice/src diff --git a/tests/server_card_counter_test.cpp b/tests/server_card_counter_test.cpp index 6308d80b9..6b884864d 100644 --- a/tests/server_card_counter_test.cpp +++ b/tests/server_card_counter_test.cpp @@ -8,10 +8,14 @@ #include #include #include +#include #include #include #include +// Referenced by other translation units in the server_remote library. +RNG_Abstract *rng = nullptr; + TEST(ServerCardCounter, IncrementNewCounter) { Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0); diff --git a/tests/server_forced_password_change_test.cpp b/tests/server_forced_password_change_test.cpp new file mode 100644 index 000000000..9717b610b --- /dev/null +++ b/tests/server_forced_password_change_test.cpp @@ -0,0 +1,234 @@ +/** @file server_forced_password_change_test.cpp + * @brief Tests for the restricted session a forced password change leaves behind at login. + * @ingroup Tests + */ + +#include "libcockatrice/protocol/pb/response.pb.h" +#include "libcockatrice/protocol/pb/server_message.pb.h" +#include "server.h" +#include "server_database_interface.h" +#include "server_protocolhandler.h" + +#include +#include +#include +#include +#include +#include +#include + +RNG_Abstract *rng = nullptr; // referenced by the server_remote library + +namespace +{ + +class MockDatabaseInterface : public Server_DatabaseInterface +{ +public: + AuthenticationResult checkUserPassword(Server_ProtocolHandler *, + const QString &, + const QString &, + const QString &, + QString &, + int &, + bool) override + { + return PasswordChangeRequired; + } + ServerInfo_User getUserData(const QString &name, bool withId) override + { + ServerInfo_User user; + user.set_name(name.toStdString()); + user.set_user_level(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered | ServerInfo_User::IsModerator); + if (withId) { + user.set_id(1); + } + return user; + } + int getNextReplayId() override + { + return 1; + } + int getNextGameId() override + { + return 1; + } + int getActiveUserCount(QString) override + { + return 0; + } +}; + +class FakeServer : public Server +{ +public: + FakeServer() + { + setDatabaseInterface(new MockDatabaseInterface()); + } +}; + +class TestHandler : public Server_ProtocolHandler +{ +public: + TestHandler(Server *_server, Server_DatabaseInterface *_db) : Server_ProtocolHandler(_server, _db) + { + } + + QString getAddress() const override + { + return {}; + } + QString getConnectionType() const override + { + return {}; + } + + Response::ResponseCode lastResponseCode = Response::RespNothing; + int lastExtendedSessionCommand = -1; + +protected: + Response::ResponseCode + processExtendedSessionCommand(int cmdType, const SessionCommand & /*cmd*/, ResponseContainer & /*rc*/) override + { + lastExtendedSessionCommand = cmdType; + return Response::RespOk; + } + + void transmitProtocolItem(const ServerMessage &item) override + { + if (item.message_type() == ServerMessage::RESPONSE) { + lastResponseCode = item.response().response_code(); + } + } +}; + +class ForcedPasswordChangeTest : public ::testing::Test +{ +protected: + FakeServer server; + TestHandler handler{&server, server.getDatabaseInterface()}; + + void login() + { + CommandContainer cont; + cont.set_cmd_id(1); + Command_Login cmd; + cmd.set_user_name("mod"); + cont.add_session_command()->MutableExtension(Command_Login::ext)->CopyFrom(cmd); + handler.processCommandContainer(cont); + } + + Response::ResponseCode sendPing() + { + CommandContainer cont; + cont.set_cmd_id(2); + cont.add_session_command()->MutableExtension(Command_Ping::ext); + handler.processCommandContainer(cont); + return handler.lastResponseCode; + } + + Response::ResponseCode sendSessionCommand() + { + CommandContainer cont; + cont.set_cmd_id(3); + cont.add_session_command()->MutableExtension(Command_ListRooms::ext); + handler.processCommandContainer(cont); + return handler.lastResponseCode; + } + + Response::ResponseCode sendRoomCommand() + { + CommandContainer cont; + cont.set_cmd_id(4); + cont.add_room_command()->MutableExtension(Command_RoomSay::ext)->set_message("hello"); + handler.processCommandContainer(cont); + return handler.lastResponseCode; + } + + Response::ResponseCode sendMixedSessionAndRoomCommand() + { + CommandContainer cont; + cont.set_cmd_id(5); + cont.add_session_command()->MutableExtension(Command_Ping::ext); + cont.add_room_command()->MutableExtension(Command_RoomSay::ext)->set_message("hello"); + handler.processCommandContainer(cont); + return handler.lastResponseCode; + } + + Response::ResponseCode sendAccountPassword() + { + CommandContainer cont; + cont.set_cmd_id(6); + Command_AccountPassword cmd; + cmd.set_old_password("temporary-password"); + cmd.set_new_password("replacement-password"); + cont.add_session_command()->MutableExtension(Command_AccountPassword::ext)->CopyFrom(cmd); + handler.processCommandContainer(cont); + return handler.lastResponseCode; + } +}; + +TEST_F(ForcedPasswordChangeTest, LoginReportsTheRequiredChange) +{ + login(); + + EXPECT_EQ(Response::RespPasswordChangeRequired, handler.lastResponseCode); + ASSERT_NE(nullptr, handler.getUserInfo()); + EXPECT_EQ("mod", QString::fromStdString(handler.getUserInfo()->name())); +} + +TEST_F(ForcedPasswordChangeTest, PendingSessionIsNotRegisteredAsAUser) +{ + login(); + // The session may change its password, but it must not take a user slot or appear in + // the session table before the client reconnects with the new password. + EXPECT_FALSE(handler.getUserInfo()->has_session_id()); + EXPECT_EQ(0, server.getUsersCount()); +} + +TEST_F(ForcedPasswordChangeTest, PendingModeratorIsNotReportedOnline) +{ + login(); + server.addClient(&handler); + + EXPECT_TRUE(server.getOnlineModeratorList().isEmpty()); +} + +TEST_F(ForcedPasswordChangeTest, OnlyPingAndPasswordChangeAreServed) +{ + login(); + + EXPECT_EQ(Response::RespOk, sendPing()); + EXPECT_EQ(Response::RespFunctionNotAllowed, sendSessionCommand()); + EXPECT_EQ(Response::RespFunctionNotAllowed, sendRoomCommand()); + // Dispatch prefers game/room over session, so a container carrying both must not slip past + // the restriction by carrying a session command. + EXPECT_EQ(Response::RespFunctionNotAllowed, sendMixedSessionAndRoomCommand()); +} + +TEST_F(ForcedPasswordChangeTest, AccountPasswordReachesTheCommand) +{ + login(); + // The forced change is useless if the gate refuses the command it needs. ACCOUNT_PASSWORD is + // served by the extended-command hook, so it must arrive there instead of being filtered. + EXPECT_EQ(Response::RespOk, sendAccountPassword()); + EXPECT_EQ(SessionCommand::ACCOUNT_PASSWORD, handler.lastExtendedSessionCommand); +} + +TEST_F(ForcedPasswordChangeTest, CompletedLoginIsStillReportedOnline) +{ + TestHandler loggedIn{&server, server.getDatabaseInterface()}; + ServerInfo_User user; + user.set_name("online-mod"); + user.set_user_level(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered | ServerInfo_User::IsModerator); + user.set_session_id(7); + loggedIn.setUserInfo(user); + server.addClient(&loggedIn); + + const QList moderators = server.getOnlineModeratorList(); + ASSERT_EQ(1, moderators.size()); + EXPECT_EQ("online-mod", moderators.first()); +} + +} // namespace diff --git a/tests/server_game_join_test.cpp b/tests/server_game_join_test.cpp index 8a2ae8c45..93895ab5c 100644 --- a/tests/server_game_join_test.cpp +++ b/tests/server_game_join_test.cpp @@ -4,6 +4,7 @@ */ #include "game/game_config.h" +#include "game/role_info.h" #include "game/server_game.h" #include "libcockatrice/protocol/pb/response.pb.h" #include "server.h" @@ -119,13 +120,13 @@ TEST_F(GameJoinOverrideTest, StaffBypassPasswordRestriction) game = makeGame(true, false, false, true); // A plain user cannot override the password even with the override flag set. - EXPECT_EQ(game->checkJoin(&plainUser, "wrong", false, true, false), Response::RespWrongPassword); + EXPECT_EQ(game->checkJoin(&plainUser, "wrong", {}, true), Response::RespWrongPassword); // Moderators and judges may enter any game regardless of the password. - EXPECT_EQ(game->checkJoin(&moderator, "wrong", false, true, false), Response::RespOk); - EXPECT_EQ(game->checkJoin(&judge, "wrong", false, true, false), Response::RespOk); + EXPECT_EQ(game->checkJoin(&moderator, "wrong", {}, true), Response::RespOk); + EXPECT_EQ(game->checkJoin(&judge, "wrong", {}, true), Response::RespOk); // Without the override flag judges are still subject to the password. - EXPECT_EQ(game->checkJoin(&judge, "wrong", false, false, true), Response::RespWrongPassword); - EXPECT_EQ(game->checkJoin(&judge, "secret", false, false, true), Response::RespOk); + EXPECT_EQ(game->checkJoin(&judge, "wrong", {.isJudge = true}, false), Response::RespWrongPassword); + EXPECT_EQ(game->checkJoin(&judge, "secret", {.isJudge = true}, false), Response::RespOk); } TEST_F(GameJoinOverrideTest, StaffBypassRegisteredOnlyRestriction) @@ -133,9 +134,9 @@ TEST_F(GameJoinOverrideTest, StaffBypassRegisteredOnlyRestriction) game = makeGame(false, true, false, true); // Without the override flag the only-registered restriction still applies. - EXPECT_EQ(game->checkJoin(&unregisteredJudge, QString(), false, false, false), Response::RespUserLevelTooLow); + EXPECT_EQ(game->checkJoin(&unregisteredJudge, QString(), {}, false), Response::RespUserLevelTooLow); // An unregistered judge may enter when overriding restrictions. - EXPECT_EQ(game->checkJoin(&unregisteredJudge, QString(), false, true, false), Response::RespOk); + EXPECT_EQ(game->checkJoin(&unregisteredJudge, QString(), {}, true), Response::RespOk); } TEST_F(GameJoinOverrideTest, StaffBypassBuddiesOnlyRestriction) @@ -143,10 +144,10 @@ TEST_F(GameJoinOverrideTest, StaffBypassBuddiesOnlyRestriction) game = makeGame(false, false, true, true); // A plain user who is not on the creator's buddy list gets rejected. - EXPECT_EQ(game->checkJoin(&plainUser, QString(), false, true, false), Response::RespOnlyBuddies); + EXPECT_EQ(game->checkJoin(&plainUser, QString(), {}, true), Response::RespOnlyBuddies); // Moderators and judges bypass the buddies-only restriction. - EXPECT_EQ(game->checkJoin(&moderator, QString(), false, true, false), Response::RespOk); - EXPECT_EQ(game->checkJoin(&judge, QString(), false, true, false), Response::RespOk); + EXPECT_EQ(game->checkJoin(&moderator, QString(), {}, true), Response::RespOk); + EXPECT_EQ(game->checkJoin(&judge, QString(), {}, true), Response::RespOk); } TEST_F(GameJoinOverrideTest, StaffBypassSpectatorsNotAllowedRestriction) @@ -154,11 +155,11 @@ TEST_F(GameJoinOverrideTest, StaffBypassSpectatorsNotAllowedRestriction) game = makeGame(false, false, false, false); // A plain user cannot spectate when the game disallows spectators. - EXPECT_EQ(game->checkJoin(&plainUser, QString(), true, false, false), Response::RespSpectatorsNotAllowed); + EXPECT_EQ(game->checkJoin(&plainUser, QString(), {.isSpectator = true}, false), Response::RespSpectatorsNotAllowed); // Moderators and judges may spectate any game regardless of the password // and the spectator restriction. - EXPECT_EQ(game->checkJoin(&moderator, "wrong", true, true, false), Response::RespOk); - EXPECT_EQ(game->checkJoin(&judge, "wrong", true, true, false), Response::RespOk); + EXPECT_EQ(game->checkJoin(&moderator, "wrong", {.isSpectator = true}, true), Response::RespOk); + EXPECT_EQ(game->checkJoin(&judge, "wrong", {.isSpectator = true}, true), Response::RespOk); } TEST_F(GameJoinOverrideTest, JudgeOverrideDoesNotGrantJudgeJoinToPlainUser) @@ -166,8 +167,8 @@ TEST_F(GameJoinOverrideTest, JudgeOverrideDoesNotGrantJudgeJoinToPlainUser) game = makeGame(false, false, false, true); // joining with join_as_judge still requires the judge flag even when overriding. - EXPECT_EQ(game->checkJoin(&plainUser, QString(), false, true, true), Response::RespUserLevelTooLow); - EXPECT_EQ(game->checkJoin(&judge, QString(), false, true, true), Response::RespOk); + EXPECT_EQ(game->checkJoin(&plainUser, QString(), {.isJudge = true}, true), Response::RespUserLevelTooLow); + EXPECT_EQ(game->checkJoin(&judge, QString(), {.isJudge = true}, true), Response::RespOk); } } // namespace diff --git a/tests/server_logger_test.cpp b/tests/server_logger_test.cpp new file mode 100644 index 000000000..3c2d047cc --- /dev/null +++ b/tests/server_logger_test.cpp @@ -0,0 +1,201 @@ +#include "server_logger.h" + +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include + +namespace +{ + +/** + * @brief Redirects std::cout for the lifetime of the object so the logger's console + * sink can be asserted on. + */ +class CoutCapture +{ +public: + CoutCapture() : previousBuffer(std::cout.rdbuf(captured.rdbuf())) + { + } + ~CoutCapture() + { + std::cout.rdbuf(previousBuffer); + } + + QString text() const + { + return QString::fromStdString(captured.str()); + } + +private: + std::ostringstream captured; + std::streambuf *previousBuffer; +}; + +// The console sink is written by the logger thread; drive it directly here, as the event +// loop would in the server. +void flushLogger(ServerLogger &logger) +{ + QMetaObject::invokeMethod(&logger, "flushBuffer", Qt::DirectConnection); +} + +// Regression: --log-to-console used to be gated behind a successfully opened logfile, so an +// empty server/logfile silenced every diagnostic, including critical startup errors. +TEST(ServerLoggerTest, ConsoleSinkDoesNotDependOnLogFile) +{ + ServerLogger logger(true, true, QString()); + logger.startLog(QString()); + + CoutCapture capture; + logger.logMessage("could not open the database"); + flushLogger(logger); + + EXPECT_TRUE(capture.text().contains("could not open the database")) << capture.text().toStdString(); +} + +// writelog only describes the logfile; disabling it must not silence the console. +TEST(ServerLoggerTest, ConsoleSinkSurvivesDisabledFileLog) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + + ServerLogger logger(true, false, QString()); + logger.startLog(dir.filePath("server.log")); + + CoutCapture capture; + logger.logMessage("startup failed"); + flushLogger(logger); + + EXPECT_TRUE(capture.text().contains("startup failed")) << capture.text().toStdString(); +} + +// logfilters selects what goes into the logfile; the console is not filtered. +TEST(ServerLoggerTest, ConsoleSinkIgnoresLogFilters) +{ + ServerLogger logger(true, true, "database"); + logger.startLog(QString()); + + CoutCapture capture; + logger.logMessage("unrelated message"); + flushLogger(logger); + + EXPECT_TRUE(capture.text().contains("unrelated message")) << capture.text().toStdString(); +} + +TEST(ServerLoggerTest, StaysQuietWhenConsoleIsDisabled) +{ + ServerLogger logger(false, false, QString()); + logger.startLog(QString()); + + CoutCapture capture; + logger.logMessage("not for the console"); + flushLogger(logger); + + EXPECT_TRUE(capture.text().isEmpty()) << capture.text().toStdString(); +} + +TEST(ServerLoggerTest, IsLoggingEnabledMirrorsTheSinks) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + + // The console is a sink of its own: messages must not be discarded for want of a logfile. + { + ServerLogger logger(true, false, QString()); + EXPECT_TRUE(logger.isLoggingEnabled()); + } + + // The logfile sink needs an open file and writelog; reloadConfig() carries the SIGHUP change. + { + ServerLogger logger(false, true, QString()); + EXPECT_FALSE(logger.isLoggingEnabled()) << "no logfile is open yet"; + logger.startLog(dir.filePath("enabled.log")); + EXPECT_TRUE(logger.isLoggingEnabled()); + logger.reloadConfig(false, QString()); + EXPECT_FALSE(logger.isLoggingEnabled()) << "writelog=false leaves no sink enabled"; + } +} + +TEST(ServerLoggerTest, WritesMatchingFilteredMessagesToTheLogFile) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + const QString logPath = dir.filePath("server.log"); + + { + ServerLogger logger(false, true, "database"); + logger.startLog(logPath); + logger.logMessage("database is unavailable"); + logger.logMessage("unrelated message"); + } + + QFile logFile(logPath); + ASSERT_TRUE(logFile.open(QIODevice::ReadOnly)); + const QString contents = QString::fromUtf8(logFile.readAll()); + EXPECT_TRUE(contents.contains("database is unavailable")) << contents.toStdString(); + EXPECT_FALSE(contents.contains("unrelated message")) << contents.toStdString(); +} + +// SIGHUP reloads the configuration; the logger must pick up the new filter. +TEST(ServerLoggerTest, ReloadConfigAppliesANewFilter) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + const QString logPath = dir.filePath("server.log"); + + { + ServerLogger logger(false, true, "database"); + logger.startLog(logPath); + logger.logMessage("filtered out"); + logger.reloadConfig(true, QString()); + logger.logMessage("kept after reload"); + } + + QFile logFile(logPath); + ASSERT_TRUE(logFile.open(QIODevice::ReadOnly)); + const QString contents = QString::fromUtf8(logFile.readAll()); + EXPECT_FALSE(contents.contains("filtered out")) << contents.toStdString(); + EXPECT_TRUE(contents.contains("kept after reload")) << contents.toStdString(); +} + +TEST(ServerLoggerTest, DisabledFileLogLeavesTheLogFileEmpty) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + const QString logPath = dir.filePath("server.log"); + + { + ServerLogger logger(false, false, QString()); + logger.startLog(logPath); + logger.logMessage("not for the logfile"); + } + + QFile logFile(logPath); + ASSERT_TRUE(logFile.open(QIODevice::ReadOnly)); + EXPECT_TRUE(logFile.readAll().isEmpty()); +} + +// A filter list consisting only of separators activates filtering with no entries; the old +// trimmed-empty check suppressed the logfile in that case and this preserves it. +TEST(ServerLoggerTest, DegenerateFilterSuppressesTheLogFile) +{ + QTemporaryDir dir; + ASSERT_TRUE(dir.isValid()); + const QString logPath = dir.filePath("server.log"); + + { + ServerLogger logger(false, true, ","); + logger.startLog(logPath); + logger.logMessage("anything"); + } + + QFile logFile(logPath); + ASSERT_TRUE(logFile.open(QIODevice::ReadOnly)); + EXPECT_TRUE(logFile.readAll().isEmpty()); +} + +} // namespace diff --git a/tests/server_room_channels_test.cpp b/tests/server_room_channels_test.cpp new file mode 100644 index 000000000..006c9fa39 --- /dev/null +++ b/tests/server_room_channels_test.cpp @@ -0,0 +1,550 @@ +/** @file server_room_channels_test.cpp + * @brief Tests for room chat channels: fan-out, privilege gating, whitelist, history replay and audit logging. + * @ingroup Tests + */ + +#include "libcockatrice/protocol/pb/response.pb.h" +#include "libcockatrice/protocol/pb/server_message.pb.h" +#include "server.h" +#include "server_database_interface.h" +#include "server_protocolhandler.h" +#include "server_room.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +RNG_Abstract *rng = nullptr; // referenced by the server_remote library + +namespace +{ + +class MockDatabaseInterface : public Server_DatabaseInterface +{ +public: + struct LogEntry + { + QString senderName; + QString message; + LogMessage_TargetType targetType; + int targetId; + QString targetName; + QString channel; + }; + QList logEntries; + + void logMessage(const int /* senderId */, + const QString &senderName, + const QString & /* senderIp */, + const QString &message, + LogMessage_TargetType targetType, + const int targetId, + const QString &targetName, + const QString &channel = QString()) override + { + logEntries.append({senderName, message, targetType, targetId, targetName, channel}); + } + + AuthenticationResult checkUserPassword(Server_ProtocolHandler *, + const QString &, + const QString &, + const QString &, + QString &, + int &, + bool) override + { + return NotLoggedIn; + } + int getNextReplayId() override + { + return 1; + } + int getNextGameId() override + { + return 1; + } + int getActiveUserCount(QString) override + { + return 0; + } + ServerInfo_User getUserData(const QString &, bool) override + { + return ServerInfo_User(); + } +}; + +class FakeServer : public Server +{ +public: + explicit FakeServer(Server_DatabaseInterface *db) : Server() + { + setDatabaseInterface(db); + } + void insertRoom(Server_Room *newRoom) + { + addRoom(newRoom); + } +}; + +class TestRoomHandler : public Server_ProtocolHandler +{ +public: + TestRoomHandler(Server *_server, Server_DatabaseInterface *_db) : Server_ProtocolHandler(_server, _db) + { + } + + QString getAddress() const override + { + return {}; + } + QString getConnectionType() const override + { + return {}; + } + + void authenticate(const ServerInfo_User &userInfo_) + { + setUserInfo(userInfo_); + authState = PasswordRight; + } + void attachToRoom(Server_Room *targetRoom) + { + rooms.insert(targetRoom->getId(), targetRoom); + } + + QList receivedRoomEvents; + Response::ResponseCode lastResponseCode = Response::RespNothing; + + void clearReceived() + { + receivedRoomEvents.clear(); + lastResponseCode = Response::RespNothing; + } + +protected: + void transmitProtocolItem(const ServerMessage &item) override + { + if (item.message_type() == ServerMessage::ROOM_EVENT) { + receivedRoomEvents.append(item.room_event()); + } else if (item.message_type() == ServerMessage::RESPONSE) { + lastResponseCode = item.response().response_code(); + } + } +}; + +class RoomChannelTest : public ::testing::Test +{ +protected: + MockDatabaseInterface db; + FakeServer server{&db}; + QList catalog; + Server_Room *room = nullptr; + TestRoomHandler mod{&server, &db}; + TestRoomHandler adminUser{&server, &db}; + TestRoomHandler user{&server, &db}; + ServerInfo_User moderator; + ServerInfo_User admin; + ServerInfo_User plainUser; + + void SetUp() override + { + ServerInfo_RoomChannel lobby; + lobby.set_id("lobby"); + lobby.set_display_name("Lobby"); + lobby.set_access_level(ServerInfo_RoomChannel::Public); + catalog.append(lobby); + + ServerInfo_RoomChannel mods; + mods.set_id("mods"); + mods.set_display_name("Moderators"); + mods.set_access_level(ServerInfo_RoomChannel::Moderator); + catalog.append(mods); + + ServerInfo_RoomChannel ops; + ops.set_id("ops"); + ops.set_display_name("Admins"); + ops.set_access_level(ServerInfo_RoomChannel::Admin); + catalog.append(ops); + + room = new Server_Room(1, 1000, "test room", "", "", "", false, "Welcome", QStringList(), &server, catalog); + + moderator.set_name("mod"); + moderator.set_user_level(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered | + ServerInfo_User::IsModerator); + // Mirrors the database layer: the admin bit grants IsAdmin alongside + // IsModerator, so admins reach moderator channels via the moderator flag. + admin.set_name("admin"); + admin.set_user_level(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered | ServerInfo_User::IsAdmin | + ServerInfo_User::IsModerator); + plainUser.set_name("user"); + plainUser.set_user_level(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered); + } + + void TearDown() override + { + delete room; + } + + // Make @p handler a member of the room directly, without history replay or JOIN_ROOM bookkeeping. + void joinMember(TestRoomHandler &handler, const ServerInfo_User &userInfo_) + { + handler.authenticate(userInfo_); + handler.attachToRoom(room); + room->addClient(&handler); + } + + void sendMainSay(TestRoomHandler &handler, const QString &message) + { + CommandContainer cont; + cont.set_cmd_id(1); + cont.set_room_id(room->getId()); + Command_RoomSay cmd; + cmd.set_message(message.toStdString()); + cont.add_room_command()->MutableExtension(Command_RoomSay::ext)->CopyFrom(cmd); + handler.processCommandContainer(cont); + } + + void sendChannelSay(TestRoomHandler &handler, const QString &channelId, const QString &message) + { + CommandContainer cont; + cont.set_cmd_id(1); + cont.set_room_id(room->getId()); + Command_RoomChannelSay cmd; + cmd.set_channel_id(channelId.toStdString()); + cmd.set_message(message.toStdString()); + cont.add_room_command()->MutableExtension(Command_RoomChannelSay::ext)->CopyFrom(cmd); + handler.processCommandContainer(cont); + } + + void joinThroughCommand(TestRoomHandler &handler) + { + CommandContainer cont; + cont.set_cmd_id(1); + Command_JoinRoom cmd; + cmd.set_room_id(room->getId()); + cont.add_session_command()->MutableExtension(Command_JoinRoom::ext)->CopyFrom(cmd); + handler.processCommandContainer(cont); + } + + static QList channelSayEvents(const TestRoomHandler &handler) + { + QList result; + for (const RoomEvent &event : handler.receivedRoomEvents) { + if (static_cast(getPbExtension(event)) == RoomEvent::ROOM_CHANNEL_SAY) { + result.append(event); + } + } + return result; + } + + static QList roomSayEvents(const TestRoomHandler &handler) + { + QList result; + for (const RoomEvent &event : handler.receivedRoomEvents) { + if (static_cast(getPbExtension(event)) == RoomEvent::ROOM_SAY) { + result.append(event); + } + } + return result; + } + + static QString channelIdOf(const RoomEvent &event) + { + return QString::fromStdString(event.GetExtension(Event_RoomChannelSay::ext).channel_id()); + } + + static QString messageOf(const RoomEvent &event) + { + return QString::fromStdString(event.GetExtension(Event_RoomChannelSay::ext).message()); + } + + static QString mainMessageOf(const RoomEvent &event) + { + return QString::fromStdString(event.GetExtension(Event_RoomSay::ext).message()); + } +}; + +TEST_F(RoomChannelTest, MainSayStillBroadcastsToAllRoomMembers) +{ + joinMember(mod, moderator); + joinMember(user, plainUser); + mod.clearReceived(); + user.clearReceived(); + + sendMainSay(user, "hello everyone"); + + EXPECT_EQ(user.lastResponseCode, Response::RespOk); + EXPECT_EQ(roomSayEvents(mod).size(), 1); + EXPECT_EQ(mainMessageOf(roomSayEvents(mod).first()), "hello everyone"); + EXPECT_EQ(roomSayEvents(user).size(), 1); + EXPECT_EQ(mainMessageOf(roomSayEvents(user).first()), "hello everyone"); + EXPECT_TRUE(channelSayEvents(mod).isEmpty()); + EXPECT_TRUE(channelSayEvents(user).isEmpty()); +} + +TEST_F(RoomChannelTest, PublicChannelSaysBroadcastToEveryone) +{ + joinMember(mod, moderator); + joinMember(user, plainUser); + mod.clearReceived(); + user.clearReceived(); + + sendChannelSay(user, "lobby", "is there a game tonight?"); + + EXPECT_EQ(user.lastResponseCode, Response::RespOk); + EXPECT_EQ(channelSayEvents(mod).size(), 1); + EXPECT_EQ(channelIdOf(channelSayEvents(mod).first()), "lobby"); + EXPECT_EQ(messageOf(channelSayEvents(mod).first()), "is there a game tonight?"); + EXPECT_EQ(channelSayEvents(user).size(), 1); +} + +TEST_F(RoomChannelTest, ModeratorChannelReachesModeratorAndAbove) +{ + joinMember(mod, moderator); + joinMember(adminUser, admin); + joinMember(user, plainUser); + mod.clearReceived(); + adminUser.clearReceived(); + user.clearReceived(); + + sendChannelSay(mod, "mods", "quiet channel"); + + EXPECT_EQ(mod.lastResponseCode, Response::RespOk); + EXPECT_EQ(channelSayEvents(mod).size(), 1); + EXPECT_EQ(channelIdOf(channelSayEvents(mod).first()), "mods"); + // An admin holds the moderator flag alongside the admin flag and so also receives the channel message. + EXPECT_EQ(channelSayEvents(adminUser).size(), 1); + EXPECT_TRUE(channelSayEvents(user).isEmpty()); + EXPECT_TRUE(roomSayEvents(user).isEmpty()); +} + +TEST_F(RoomChannelTest, AdminReachesModeratorChannel) +{ + joinMember(mod, moderator); + joinMember(adminUser, admin); + mod.clearReceived(); + adminUser.clearReceived(); + + sendChannelSay(adminUser, "mods", "admin checking in"); + + EXPECT_EQ(adminUser.lastResponseCode, Response::RespOk); + EXPECT_EQ(channelSayEvents(mod).size(), 1); + EXPECT_EQ(channelSayEvents(adminUser).size(), 1); + EXPECT_EQ(messageOf(channelSayEvents(mod).first()), "admin checking in"); +} + +TEST_F(RoomChannelTest, UserBelowRequiredLevelCannotSendToChannel) +{ + joinMember(mod, moderator); + joinMember(user, plainUser); + mod.clearReceived(); + user.clearReceived(); + + sendChannelSay(user, "mods", "let me in"); + + EXPECT_EQ(user.lastResponseCode, Response::RespFunctionNotAllowed); + EXPECT_TRUE(channelSayEvents(mod).isEmpty()); + EXPECT_TRUE(channelSayEvents(user).isEmpty()); + bool loggedAsChannel = false; + for (const MockDatabaseInterface::LogEntry &entry : db.logEntries) { + loggedAsChannel |= entry.targetType == Server_DatabaseInterface::MessageTargetRoomChannel; + } + EXPECT_FALSE(loggedAsChannel); +} + +TEST_F(RoomChannelTest, AdminChannelRejectsModerator) +{ + joinMember(mod, moderator); + joinMember(adminUser, admin); + mod.clearReceived(); + adminUser.clearReceived(); + + sendChannelSay(mod, "ops", "not allowed"); + + EXPECT_EQ(mod.lastResponseCode, Response::RespFunctionNotAllowed); + EXPECT_TRUE(channelSayEvents(mod).isEmpty()); + EXPECT_TRUE(channelSayEvents(adminUser).isEmpty()); +} + +TEST_F(RoomChannelTest, AdminChannelReachesAdminsOnly) +{ + joinMember(mod, moderator); + joinMember(adminUser, admin); + joinMember(user, plainUser); + mod.clearReceived(); + adminUser.clearReceived(); + user.clearReceived(); + + sendChannelSay(adminUser, "ops", "staff only"); + + EXPECT_EQ(adminUser.lastResponseCode, Response::RespOk); + EXPECT_EQ(channelSayEvents(adminUser).size(), 1); + EXPECT_TRUE(channelSayEvents(mod).isEmpty()); + EXPECT_TRUE(channelSayEvents(user).isEmpty()); +} + +TEST_F(RoomChannelTest, UnknownChannelIdIsRejected) +{ + joinMember(user, plainUser); + user.clearReceived(); + + sendChannelSay(user, "not-a-channel", "spam"); + + EXPECT_EQ(user.lastResponseCode, Response::RespInvalidCommand); + EXPECT_TRUE(channelSayEvents(user).isEmpty()); + EXPECT_TRUE(db.logEntries.isEmpty()); +} + +TEST_F(RoomChannelTest, MissingAccessLevelDefaultsToPublic) +{ + ServerInfo_RoomChannel defaultAccess; + defaultAccess.set_id("public-by-default"); + defaultAccess.set_display_name("Default Access"); + catalog.append(defaultAccess); + auto *secondRoom = new Server_Room(2, 1000, "second", "", "", "", false, "", QStringList(), &server, catalog); + + TestRoomHandler joiner{&server, &db}; + joiner.authenticate(plainUser); + joiner.attachToRoom(secondRoom); + secondRoom->addClient(&joiner); + + CommandContainer cont; + cont.set_cmd_id(1); + cont.set_room_id(2); + Command_RoomChannelSay cmd; + cmd.set_channel_id("public-by-default"); + cmd.set_message("hi"); + cont.add_room_command()->MutableExtension(Command_RoomChannelSay::ext)->CopyFrom(cmd); + joiner.processCommandContainer(cont); + + EXPECT_EQ(joiner.lastResponseCode, Response::RespOk); + EXPECT_EQ(channelSayEvents(joiner).size(), 1); + + delete secondRoom; +} + +TEST_F(RoomChannelTest, HistoryStoredPerChannel) +{ + room->say("user", "main message", QString(), false); + room->say("user", "lobby message", "lobby", false); + room->say("user", "mods message", "mods", false); + + const QList mainHistory = room->getChatHistory(Server_Room::MAIN_CHANNEL_KEY); + EXPECT_EQ(mainHistory.size(), 1); + EXPECT_EQ(QString::fromStdString(mainHistory.first().message()), "main message"); + EXPECT_TRUE(QString::fromStdString(mainHistory.first().channel()).isEmpty()); + + const QList lobbyHistory = room->getChatHistory("lobby"); + EXPECT_EQ(lobbyHistory.size(), 1); + EXPECT_EQ(QString::fromStdString(lobbyHistory.first().message()), "lobby message"); + EXPECT_EQ(QString::fromStdString(lobbyHistory.first().channel()), "lobby"); + + const QList modsHistory = room->getChatHistory("mods"); + EXPECT_EQ(modsHistory.size(), 1); + EXPECT_EQ(QString::fromStdString(modsHistory.first().message()), "mods message"); + EXPECT_EQ(QString::fromStdString(modsHistory.first().channel()), "mods"); + + // A channel id maps to its own history bucket, never to the main one. + EXPECT_EQ(room->getChatHistory("").size(), mainHistory.size()); +} + +TEST_F(RoomChannelTest, JoinReplaysOnlyVisibleChannelHistory) +{ + server.insertRoom(room); + joinMember(mod, moderator); + joinMember(user, plainUser); + + room->say("user", "main message", QString(), false); + room->say("user", "lobby message", "lobby", false); + room->say("mod", "mods message", "mods", false); + room->say("admin", "ops message", "ops", false); + + TestRoomHandler lateUser{&server, &db}; + lateUser.authenticate(plainUser); + joinThroughCommand(lateUser); + + EXPECT_EQ(lateUser.lastResponseCode, Response::RespOk); + const QList lateChannelEvents = channelSayEvents(lateUser); + // Replay: one main history event, one public channel history event, the welcome message -- but no restricted + // history. + EXPECT_EQ(roomSayEvents(lateUser).size(), 2); + EXPECT_EQ(lateChannelEvents.size(), 1); + EXPECT_EQ(channelIdOf(lateChannelEvents.first()), "lobby"); + + TestRoomHandler lateMod{&server, &db}; + lateMod.authenticate(moderator); + joinThroughCommand(lateMod); + + EXPECT_EQ(lateMod.lastResponseCode, Response::RespOk); + const QList lateModChannelEvents = channelSayEvents(lateMod); + EXPECT_EQ(lateModChannelEvents.size(), 2); + EXPECT_EQ(channelIdOf(lateModChannelEvents.at(0)), "lobby"); + EXPECT_EQ(channelIdOf(lateModChannelEvents.at(1)), "mods"); + + TestRoomHandler lateAdmin{&server, &db}; + lateAdmin.authenticate(admin); + joinThroughCommand(lateAdmin); + + EXPECT_EQ(lateAdmin.lastResponseCode, Response::RespOk); + const QList lateAdminChannelEvents = channelSayEvents(lateAdmin); + // An admin sees the moderator channel and the admin channel on top of the public one. + EXPECT_EQ(lateAdminChannelEvents.size(), 3); + EXPECT_EQ(channelIdOf(lateAdminChannelEvents.at(0)), "lobby"); + EXPECT_EQ(channelIdOf(lateAdminChannelEvents.at(1)), "mods"); + EXPECT_EQ(channelIdOf(lateAdminChannelEvents.at(2)), "ops"); +} + +TEST_F(RoomChannelTest, AuditLoggingTagsChannelMessages) +{ + joinMember(user, plainUser); + + sendMainSay(user, "main log line"); + sendChannelSay(user, "lobby", "channel log line"); + + ASSERT_EQ(db.logEntries.size(), 2); + EXPECT_EQ(db.logEntries.at(0).targetType, Server_DatabaseInterface::MessageTargetRoom); + EXPECT_TRUE(db.logEntries.at(0).channel.isEmpty()); + EXPECT_EQ(db.logEntries.at(1).targetType, Server_DatabaseInterface::MessageTargetRoomChannel); + EXPECT_EQ(db.logEntries.at(1).channel, "lobby"); + EXPECT_EQ(db.logEntries.at(1).senderName, "user"); + EXPECT_EQ(db.logEntries.at(1).message, "channel log line"); +} + +TEST_F(RoomChannelTest, PublicChannelRelaysOverIslButRestrictedChannelsDoNot) +{ + QList relMessages; + QObject::connect(&server, &Server::sigSendIslMessage, + [&relMessages](const IslMessage &message, int) { relMessages.append(message); }); + + room->say("mod", "public msg", "lobby"); + room->say("mod", "mods msg", "mods"); + room->say("admin", "ops msg", "ops"); + + ASSERT_EQ(relMessages.size(), 1); + const RoomEvent &relayed = relMessages.first().room_event(); + EXPECT_EQ(static_cast(getPbExtension(relayed)), RoomEvent::ROOM_CHANNEL_SAY); + EXPECT_EQ(channelIdOf(relayed), "lobby"); + EXPECT_EQ(messageOf(relayed), "public msg"); +} + +} // namespace + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} \ No newline at end of file diff --git a/tests/settings/CMakeLists.txt b/tests/settings/CMakeLists.txt index 98bed1f4b..9dbda9d6b 100644 --- a/tests/settings/CMakeLists.txt +++ b/tests/settings/CMakeLists.txt @@ -1,5 +1,7 @@ cockatrice_add_test(NAME settings_manager_test SOURCES settings_manager_test.cpp LIBS libcockatrice_settings) +cockatrice_add_test(NAME servers_settings_test SOURCES servers_settings_test.cpp LIBS libcockatrice_settings) + cockatrice_add_test( NAME settings_defaults_test diff --git a/tests/settings/servers_settings_test.cpp b/tests/settings/servers_settings_test.cpp new file mode 100644 index 000000000..5d5953999 --- /dev/null +++ b/tests/settings/servers_settings_test.cpp @@ -0,0 +1,228 @@ +#include "gtest/gtest.h" +#include +#include +#include +#include + +namespace +{ +class ServersSettingsTest : public ::testing::Test +{ +protected: + QTemporaryDir tempDir; + QString settingsPath; + + void SetUp() override + { + settingsPath = tempDir.path() + "/"; + } + + QString settingsFile() const + { + return settingsPath + "servers.ini"; + } + + void writeLegacyServer(int index, + const QString &saveName, + const QString &host, + const QString &port, + const QString &username, + const QString &password, + bool savePassword, + const QString &site) + { + QSettings settings(settingsFile(), QSettings::IniFormat); + const QString prefix = QStringLiteral("server/server_details/"); + settings.setValue(prefix + QStringLiteral("saveName%1").arg(index), saveName); + settings.setValue(prefix + QStringLiteral("server%1").arg(index), host); + settings.setValue(prefix + QStringLiteral("port%1").arg(index), port); + settings.setValue(prefix + QStringLiteral("username%1").arg(index), username); + settings.setValue(prefix + QStringLiteral("password%1").arg(index), password); + settings.setValue(prefix + QStringLiteral("savePassword%1").arg(index), savePassword); + settings.setValue(prefix + QStringLiteral("site%1").arg(index), site); + } +}; + +TEST_F(ServersSettingsTest, AddServerStoresAllFields) +{ + ServersSettings settings(settingsPath, nullptr); + settings.addNewServer("Alpha", "alpha.example", "4748", "alice", "secret", true, "https://alpha.example"); + + const auto servers = settings.getServers(); + ASSERT_EQ(servers.size(), 1); + EXPECT_EQ(servers.first().saveName, QString("Alpha")); + EXPECT_EQ(servers.first().host, QString("alpha.example")); + EXPECT_EQ(servers.first().port, QString("4748")); + EXPECT_EQ(servers.first().username, QString("alice")); + EXPECT_EQ(servers.first().password, QString("secret")); + EXPECT_TRUE(servers.first().savePassword); + EXPECT_EQ(servers.first().site, QString("https://alpha.example")); +} + +TEST_F(ServersSettingsTest, AddExistingHostAndPortUpdatesInsteadOfDuplicating) +{ + ServersSettings settings(settingsPath, nullptr); + settings.addNewServer("Alpha", "alpha.example", "4748", "alice", "secret", true, "site-a"); + settings.addNewServer("Renamed", "alpha.example", "4748", "bob", "new-secret", true, "site-b"); + + const auto servers = settings.getServers(); + ASSERT_EQ(servers.size(), 1); + EXPECT_EQ(servers.first().saveName, QString("Renamed")); + EXPECT_EQ(servers.first().username, QString("bob")); + EXPECT_EQ(servers.first().password, QString("new-secret")); + EXPECT_EQ(servers.first().site, QString("site-b")); +} + +TEST_F(ServersSettingsTest, RemoveServerRemovesOnlyMatchingHostAndKeepsOrder) +{ + ServersSettings settings(settingsPath, nullptr); + settings.addNewServer("Alpha", "alpha.example", "4748", "alice", "a", true, ""); + settings.addNewServer("Beta", "beta.example", "4748", "bob", "b", true, ""); + settings.addNewServer("Gamma", "gamma.example", "4748", "carol", "c", true, ""); + + settings.removeServer("beta.example"); + + const auto servers = settings.getServers(); + ASSERT_EQ(servers.size(), 2); + EXPECT_EQ(servers.at(0).saveName, QString("Alpha")); + EXPECT_EQ(servers.at(1).saveName, QString("Gamma")); +} + +TEST_F(ServersSettingsTest, RemovingLastServerDropsItsStaleArraySlot) +{ + ServersSettings settings(settingsPath, nullptr); + settings.addNewServer("Alpha", "alpha.example", "4748", "alice", "a", true, ""); + settings.addNewServer("Beta", "beta.example", "4748", "bob", "b", true, ""); + settings.addNewServer("Gamma", "gamma.example", "4748", "carol", "gamma-secret", true, ""); + + settings.removeServer("gamma.example"); + + QSettings raw(settingsFile(), QSettings::IniFormat); + EXPECT_FALSE(raw.contains("server/server_details/servers/3/password")); + EXPECT_FALSE(raw.contains("server/server_details/servers/3/saveName")); + ASSERT_EQ(settings.getServers().size(), 2); +} + +TEST_F(ServersSettingsTest, UpdateWithoutLossKeepsCredentials) +{ + ServersSettings settings(settingsPath, nullptr); + settings.addNewServer("Alpha", "alpha.example", "4748", "alice", "secret", true, ""); + + EXPECT_TRUE(settings.updateExistingServerWithoutLoss("Renamed", "alpha.example", "1234", "site")); + + const auto server = settings.getServer(settings.findServerIndex("alpha.example", "1234")); + ASSERT_TRUE(server.has_value()); + EXPECT_EQ(server->saveName, QString("Renamed")); + EXPECT_EQ(server->username, QString("alice")); + EXPECT_EQ(server->password, QString("secret")); + EXPECT_TRUE(server->savePassword); + EXPECT_EQ(server->site, QString("site")); +} + +TEST_F(ServersSettingsTest, SetServerPasswordUpdatesOnlyTheNamedProfile) +{ + ServersSettings settings(settingsPath, nullptr); + settings.addNewServer("Alpha", "alpha.example", "4748", "alice", "old", true, ""); + settings.addNewServer("Beta", "beta.example", "4748", "bob", "keep", true, ""); + + settings.setServerPassword("Beta", "new"); + + EXPECT_EQ(settings.getServer(0)->password, QString("old")); + EXPECT_EQ(settings.getServer(1)->password, QString("new")); +} + +TEST_F(ServersSettingsTest, LegacyProfilesAreMigrated) +{ + { + QSettings settings(settingsFile(), QSettings::IniFormat); + settings.setValue("server/server_details/totalServers", 2); + } + writeLegacyServer(1, "Alpha", "alpha.example", "4748", "alice", "secret", true, "site-a"); + writeLegacyServer(2, "Beta", "beta.example", "4748", "bob", "other", false, "site-b"); + + ServersSettings settings(settingsPath, nullptr); + + const auto servers = settings.getServers(); + ASSERT_EQ(servers.size(), 2); + EXPECT_EQ(servers.at(0).saveName, QString("Alpha")); + EXPECT_EQ(servers.at(0).host, QString("alpha.example")); + EXPECT_TRUE(servers.at(0).savePassword); + EXPECT_EQ(servers.at(1).saveName, QString("Beta")); + EXPECT_FALSE(servers.at(1).savePassword); + + QSettings raw(settingsFile(), QSettings::IniFormat); + EXPECT_FALSE(raw.contains("server/server_details/totalServers")); + EXPECT_FALSE(raw.contains("server/server_details/saveName1")); + EXPECT_FALSE(raw.contains("server/server_details/server1")); +} + +TEST_F(ServersSettingsTest, MigrationRunsOnce) +{ + writeLegacyServer(1, "Alpha", "alpha.example", "4748", "alice", "secret", true, ""); + { + QSettings settings(settingsFile(), QSettings::IniFormat); + settings.setValue("server/server_details/totalServers", 1); + } + + { + ServersSettings first(settingsPath, nullptr); + ASSERT_EQ(first.getServers().size(), 1); + } + + ServersSettings second(settingsPath, nullptr); + ASSERT_EQ(second.getServers().size(), 1); +} + +TEST_F(ServersSettingsTest, LegacyProfileAddedAfterMigrationIsMerged) +{ + { + ServersSettings first(settingsPath, nullptr); + first.addNewServer("Alpha", "alpha.example", "4748", "alice", "secret", true, ""); + } + + // An older build writes its own profile back in the legacy format after the + // array migration already happened. + writeLegacyServer(1, "Beta", "beta.example", "4748", "bob", "other", true, "site-b"); + // A duplicate of a profile the array already holds must not be added twice. + writeLegacyServer(2, "Alpha duplicate", "alpha.example", "4748", "eve", "dupe", true, ""); + { + QSettings settings(settingsFile(), QSettings::IniFormat); + settings.setValue("server/server_details/totalServers", 2); + } + + ServersSettings settings(settingsPath, nullptr); + + const auto servers = settings.getServers(); + ASSERT_EQ(servers.size(), 2); + EXPECT_EQ(servers.at(0).saveName, QString("Alpha")); + EXPECT_EQ(servers.at(0).password, QString("secret")); + EXPECT_EQ(servers.at(1).saveName, QString("Beta")); + EXPECT_EQ(servers.at(1).host, QString("beta.example")); + EXPECT_EQ(servers.at(1).username, QString("bob")); + EXPECT_EQ(servers.at(1).password, QString("other")); + EXPECT_TRUE(servers.at(1).savePassword); + EXPECT_EQ(servers.at(1).site, QString("site-b")); + + QSettings raw(settingsFile(), QSettings::IniFormat); + EXPECT_FALSE(raw.contains("server/server_details/totalServers")); + EXPECT_FALSE(raw.contains("server/server_details/saveName1")); + EXPECT_FALSE(raw.contains("server/server_details/server2")); + + // The merge is persisted, so a further start adds nothing. + ServersSettings again(settingsPath, nullptr); + ASSERT_EQ(again.getServers().size(), 2); +} + +TEST_F(ServersSettingsTest, OutOfRangeIndexReturnsNullopt) +{ + ServersSettings settings(settingsPath, nullptr); + EXPECT_FALSE(settings.getServer(-1).has_value()); + EXPECT_FALSE(settings.getServer(0).has_value()); +} +} // namespace + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tests/settings/settings_defaults_test.cpp b/tests/settings/settings_defaults_test.cpp index 5c3e9ba74..156e52695 100644 --- a/tests/settings/settings_defaults_test.cpp +++ b/tests/settings/settings_defaults_test.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -285,6 +286,32 @@ TEST_F(SettingsDefaultsTest, Chat_HighlightColor_Default) ASSERT_EQ(s.getChatHighlightColor(), QString("A6120D")); } +TEST_F(SettingsDefaultsTest, Chat_ChannelUnreadColor_Default) +{ + ChatSettings s(settingsPath, nullptr); + ASSERT_EQ(s.getChannelUnreadColor(), QString("1A7B00")); +} + +TEST_F(SettingsDefaultsTest, Chat_ChannelUnreadColor_SetAndGet) +{ + ChatSettings s(settingsPath, nullptr); + s.setChannelUnreadColor(QString("0000FF")); + ASSERT_EQ(s.getChannelUnreadColor(), QString("0000FF")); +} + +TEST_F(SettingsDefaultsTest, Chat_ChannelMentionColor_Default) +{ + ChatSettings s(settingsPath, nullptr); + ASSERT_EQ(s.getChannelMentionColor(), QString("A6120D")); +} + +TEST_F(SettingsDefaultsTest, Chat_ChannelMentionColor_SetAndGet) +{ + ChatSettings s(settingsPath, nullptr); + s.setChannelMentionColor(QString("FF0000")); + ASSERT_EQ(s.getChannelMentionColor(), QString("FF0000")); +} + TEST_F(SettingsDefaultsTest, Chat_MentionForeground_Default) { ChatSettings s(settingsPath, nullptr); @@ -341,6 +368,32 @@ TEST_F(SettingsDefaultsTest, Personal_ShowTipsOnStartup_Default) ASSERT_EQ(s.getShowTipsOnStartup(), true); } +TEST_F(SettingsDefaultsTest, Personal_CrashReportingMode_Default) +{ + PersonalSettings s(settingsPath, nullptr); + ASSERT_EQ(s.getCrashReportingMode(), PersonalSettings::CrashReportingMode::Ask); +} + +TEST_F(SettingsDefaultsTest, Personal_CrashReportingMode_SetAndGet) +{ + PersonalSettings s(settingsPath, nullptr); + s.setCrashReportingMode(PersonalSettings::CrashReportingMode::AlwaysSend); + ASSERT_EQ(s.getCrashReportingMode(), PersonalSettings::CrashReportingMode::AlwaysSend); + s.setCrashReportingMode(PersonalSettings::CrashReportingMode::NeverSend); + ASSERT_EQ(s.getCrashReportingMode(), PersonalSettings::CrashReportingMode::NeverSend); + s.setCrashReportingMode(PersonalSettings::CrashReportingMode::Ask); + ASSERT_EQ(s.getCrashReportingMode(), PersonalSettings::CrashReportingMode::Ask); +} + +TEST_F(SettingsDefaultsTest, Personal_CrashReportingMode_ClampsCorruptValue) +{ + QSettings raw(settingsPath + "personal.ini", QSettings::IniFormat); + raw.setValue("personal/crashReportingMode", 99); + raw.sync(); + PersonalSettings s(settingsPath, nullptr); + ASSERT_EQ(s.getCrashReportingMode(), PersonalSettings::CrashReportingMode::Ask); +} + // --- DownloadSettings --- TEST_F(SettingsDefaultsTest, Download_PicDownload_Default) diff --git a/tests/support/model_contract_checker.h b/tests/support/model_contract_checker.h new file mode 100644 index 000000000..9b493ff6e --- /dev/null +++ b/tests/support/model_contract_checker.h @@ -0,0 +1,35 @@ +#ifndef COCKATRICE_TEST_MODEL_CONTRACT_CHECKER_H +#define COCKATRICE_TEST_MODEL_CONTRACT_CHECKER_H + +#include +#include +#include + +namespace test_support +{ +/** + * @brief Keeps a QAbstractItemModelTester alive for the lifetime of a scope. + * + * The tester only observes the model while it exists, so construct this before + * the mutations a test wants to validate and keep it in scope until the end. + * Fatal reporting mode aborts on the first contract violation; a warning would + * let the test pass while printing a line nobody reads in CI. + */ +class ModelContractChecker +{ +public: + explicit ModelContractChecker(QAbstractItemModel *model) + : tester( + std::make_unique(model, QAbstractItemModelTester::FailureReportingMode::Fatal)) + { + } + + ModelContractChecker(const ModelContractChecker &) = delete; + ModelContractChecker &operator=(const ModelContractChecker &) = delete; + +private: + std::unique_ptr tester; +}; +} // namespace test_support + +#endif // COCKATRICE_TEST_MODEL_CONTRACT_CHECKER_H diff --git a/tests/translation_loader_test.cpp b/tests/translation_loader_test.cpp index 745cd04bc..7a59e1b9b 100644 --- a/tests/translation_loader_test.cpp +++ b/tests/translation_loader_test.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include #include #include @@ -19,6 +20,11 @@ #define TRANSLATION_LOADER_TEST_DIR "" #endif +// The client's translation files, whose language codes are the languages the language selector offers. +#ifndef COCKATRICE_TRANSLATIONS_DIR +#define COCKATRICE_TRANSLATIONS_DIR "" +#endif + namespace { const QString testContext = QStringLiteral("TranslationLoaderTest"); @@ -233,6 +239,49 @@ TEST(TranslationLoaderTest, AvailableLanguagesListsTheTranslationsThatCanBeLoade EXPECT_TRUE(TranslationLoader::availableLanguages(QStringLiteral("translation_loader_test"), {}).isEmpty()); } +TEST(TranslationLoaderTest, LanguageDisplayNamesDoNotNeedATranslationFile) +{ + // The regression this guards: the selector took every label from the language's own .qm file, so it + // went blank as soon as translators had not translated the "English" placeholder in the i18n context. + EXPECT_EQ(QStringLiteral("Deutsch (German)"), TranslationLoader::languageDisplayName(QStringLiteral("de"))); + EXPECT_EQ(QStringLiteral("简体中文 (Chinese Simplified)"), + TranslationLoader::languageDisplayName(QStringLiteral("zh-Hans"))); + EXPECT_EQ(QStringLiteral("English, arr! (Pirate English)"), + TranslationLoader::languageDisplayName(QStringLiteral("en@pirate"))); +} + +TEST(TranslationLoaderTest, EveryLanguageTheClientShipsHasItsOwnDisplayName) +{ + // The shipped translation files are what the selector offers, so each of them has to end up with a + // label: an empty one is what made the dropdown look broken, and two equal ones are a coin flip. + const QDir translations(QStringLiteral(COCKATRICE_TRANSLATIONS_DIR)); + const QStringList files = translations.entryList({QStringLiteral("cockatrice_*.ts")}, QDir::Files); + ASSERT_FALSE(files.isEmpty()); + + QStringList languages; + QSet displayNames; + for (const QString &file : files) { + const QString language = QFileInfo(file).completeBaseName().section(QLatin1Char('_'), 1); + const QString displayName = TranslationLoader::languageDisplayName(language); + EXPECT_FALSE(displayName.isEmpty()) << language.toStdString(); + languages << language; + displayNames.insert(displayName); + } + + EXPECT_EQ(files.size(), displayNames.size()); + + // The selector preselects this one for an unset setting, so it has to be offered at all. + EXPECT_TRUE(languages.contains(TranslationLoader::defaultLanguageCode())); +} + +TEST(TranslationLoaderTest, UnknownLanguagesFallBackToTheNativeNameThenTheCode) +{ + // A language added after the table, and a code Qt does not know either. + EXPECT_FALSE(TranslationLoader::languageDisplayName(QStringLiteral("sv_FI")).isEmpty()); + EXPECT_EQ(QStringLiteral("zz_QQ"), TranslationLoader::languageDisplayName(QStringLiteral("zz_QQ"))); + EXPECT_TRUE(TranslationLoader::languageDisplayName(QString()).isEmpty()); +} + TEST(TranslationLoaderTest, LoadApplicationReportsAMissingTranslationDirectory) { QTemporaryDir emptyDir; diff --git a/tests/trusted_sources_test.cpp b/tests/trusted_sources_test.cpp new file mode 100644 index 000000000..a16b21f2f --- /dev/null +++ b/tests/trusted_sources_test.cpp @@ -0,0 +1,58 @@ +#include "trusted_sources.h" + +#include "gtest/gtest.h" +#include + +namespace +{ + +const QString kDefault = QStringLiteral("127.0.0.1,::1"); + +TEST(TrustedSourcesTest, MatchesListedAddresses) +{ + ASSERT_TRUE(isTrustedSource(kDefault, QHostAddress("127.0.0.1"))); + ASSERT_TRUE(isTrustedSource(kDefault, QHostAddress("::1"))); + ASSERT_FALSE(isTrustedSource(kDefault, QHostAddress("10.0.0.1"))); +} + +TEST(TrustedSourcesTest, MatchesIpv4MappedPeerAgainstPlainIpv4Entry) +{ + // A dual-stack listener reports a local IPv4 proxy as ::ffff:127.0.0.1; + // it must still match the default 127.0.0.1 entry. + ASSERT_TRUE(isTrustedSource(kDefault, QHostAddress("::ffff:127.0.0.1"))); +} + +TEST(TrustedSourcesTest, MatchesPlainIpv4PeerAgainstMappedEntry) +{ + ASSERT_TRUE(isTrustedSource("::ffff:192.0.2.7", QHostAddress("192.0.2.7"))); +} + +TEST(TrustedSourcesTest, DoesNotMatchSubstrings) +{ + // Substring matching would let 27.0.0.1 or 0.0.1 pass as 127.0.0.1. + ASSERT_FALSE(isTrustedSource(kDefault, QHostAddress("27.0.0.1"))); + ASSERT_FALSE(isTrustedSource("192.0.2.10", QHostAddress("192.0.2.1"))); + ASSERT_FALSE(isTrustedSource(kDefault, QHostAddress(":"))); +} + +TEST(TrustedSourcesTest, ToleratesWhitespaceAndCase) +{ + ASSERT_TRUE(isTrustedSource(" 192.0.2.7 , 2001:DB8::1 ", QHostAddress("2001:db8::1"))); + ASSERT_TRUE(isTrustedSource(" 192.0.2.7 , 2001:DB8::1 ", QHostAddress("192.0.2.7"))); +} + +TEST(TrustedSourcesTest, IgnoresMalformedEntriesAndNullAddresses) +{ + ASSERT_FALSE(isTrustedSource("not-an-ip,,", QHostAddress("127.0.0.1"))); + ASSERT_FALSE(isTrustedSource("", QHostAddress("127.0.0.1"))); + ASSERT_FALSE(isTrustedSource(kDefault, QHostAddress())); +} + +TEST(TrustedSourcesTest, StringOverloadParsesAddress) +{ + ASSERT_TRUE(isTrustedSource(kDefault, QStringLiteral("::ffff:127.0.0.1"))); + ASSERT_FALSE(isTrustedSource(kDefault, QStringLiteral("27.0.0.1"))); + ASSERT_FALSE(isTrustedSource(kDefault, QString())); +} + +} // namespace diff --git a/tests/user_data_backup_test.cpp b/tests/user_data_backup_test.cpp new file mode 100644 index 000000000..b74b26fa3 --- /dev/null +++ b/tests/user_data_backup_test.cpp @@ -0,0 +1,581 @@ +#include "gtest/gtest.h" +#include +#include +#include +#include + +namespace +{ + +TEST(UserDataBackup, CopyDirectoryKeepsNestedStructure) +{ + QTemporaryDir sourceDir; + QTemporaryDir destParent; + ASSERT_TRUE(sourceDir.isValid()); + ASSERT_TRUE(destParent.isValid()); + QDir source(sourceDir.path()); + ASSERT_TRUE(source.mkpath("decks/sub")); + QFile deck(source.absoluteFilePath("decks/sub/mydeck.cod")); + ASSERT_TRUE(deck.open(QIODevice::WriteOnly | QIODevice::Text)); + deck.write("\n"); + deck.close(); + + QString dest = QDir(destParent.path()).absoluteFilePath("copy"); + EXPECT_TRUE(UserDataBackup::copyDirectoryRecursively(sourceDir.path(), dest)); + EXPECT_TRUE(QFile::exists(dest + "/decks/sub/mydeck.cod")); +} + +TEST(UserDataBackup, CopyDirectoryKeepsHiddenFiles) +{ + QTemporaryDir sourceDir; + QTemporaryDir destParent; + ASSERT_TRUE(sourceDir.isValid()); + ASSERT_TRUE(destParent.isValid()); + QFile hidden(QDir(sourceDir.path()).absoluteFilePath(".hidden.ini")); + ASSERT_TRUE(hidden.open(QIODevice::WriteOnly | QIODevice::Text)); + hidden.write("[general]\n"); + hidden.close(); + + QString dest = QDir(destParent.path()).absoluteFilePath("copy"); + EXPECT_TRUE(UserDataBackup::copyDirectoryRecursively(sourceDir.path(), dest)); + EXPECT_TRUE(QFile::exists(dest + "/.hidden.ini")); +} + +TEST(UserDataBackup, CopyDirectoryOverwritesExistingTarget) +{ + QTemporaryDir sourceDir; + QTemporaryDir destParent; + ASSERT_TRUE(sourceDir.isValid()); + ASSERT_TRUE(destParent.isValid()); + const QString sourceFile = QDir(sourceDir.path()).absoluteFilePath("settings.ini"); + QFile first(sourceFile); + ASSERT_TRUE(first.open(QIODevice::WriteOnly | QIODevice::Text)); + first.write("version=1\n"); + first.close(); + + QString dest = QDir(destParent.path()).absoluteFilePath("copy"); + ASSERT_TRUE(UserDataBackup::copyDirectoryRecursively(sourceDir.path(), dest)); + + QFile second(sourceFile); + ASSERT_TRUE(second.open(QIODevice::WriteOnly | QIODevice::Text)); + second.write("version=2\n"); + second.close(); + + EXPECT_TRUE(UserDataBackup::copyDirectoryRecursively(sourceDir.path(), dest)); + QFile result(dest + "/settings.ini"); + ASSERT_TRUE(result.open(QIODevice::ReadOnly | QIODevice::Text)); + EXPECT_EQ(result.readAll(), QByteArray("version=2\n")); +} + +TEST(UserDataBackup, CopyEmptyDirectorySucceeds) +{ + QTemporaryDir sourceDir; + QTemporaryDir destParent; + ASSERT_TRUE(sourceDir.isValid()); + ASSERT_TRUE(destParent.isValid()); + ASSERT_TRUE(QDir(sourceDir.path()).mkpath("empty")); + + QString dest = QDir(destParent.path()).absoluteFilePath("copy"); + EXPECT_TRUE(UserDataBackup::copyDirectoryRecursively(sourceDir.path(), dest)); + EXPECT_TRUE(QDir(dest + "/empty").exists()); +} +TEST(UserDataBackup, CopyMissingDirectoryFails) +{ + QTemporaryDir destParent; + ASSERT_TRUE(destParent.isValid()); + EXPECT_FALSE( + UserDataBackup::copyDirectoryRecursively("/nonexistent-cockatrice-path-xyz", destParent.path() + "/copy")); +} + +TEST(UserDataBackup, BackupDirectoryCreatesTimestampedCopy) +{ + QTemporaryDir sourceDir; + QTemporaryDir backupRoot; + ASSERT_TRUE(sourceDir.isValid()); + ASSERT_TRUE(backupRoot.isValid()); + QFile deck(QDir(sourceDir.path()).absoluteFilePath("mydeck.cod")); + ASSERT_TRUE(deck.open(QIODevice::WriteOnly | QIODevice::Text)); + deck.write("\n"); + deck.close(); + + QStringList locations; + QString sessionDir; + EXPECT_TRUE(UserDataBackup::backupAtRiskPaths({sourceDir.path()}, backupRoot.path(), &locations, &sessionDir)); + ASSERT_EQ(locations.size(), 1); + EXPECT_TRUE(QFile::exists(locations.first() + "/mydeck.cod")); + EXPECT_TRUE(locations.first().startsWith(QDir(backupRoot.path()).absolutePath())); +} + +TEST(UserDataBackup, BackupSingleFile) +{ + QTemporaryDir sourceDir; + QTemporaryDir backupRoot; + ASSERT_TRUE(sourceDir.isValid()); + ASSERT_TRUE(backupRoot.isValid()); + QString cardDb = QDir(sourceDir.path()).absoluteFilePath("cards.xml"); + QFile db(cardDb); + ASSERT_TRUE(db.open(QIODevice::WriteOnly | QIODevice::Text)); + db.write("\n"); + db.close(); + + QStringList locations; + EXPECT_TRUE(UserDataBackup::backupAtRiskPaths({cardDb}, backupRoot.path(), &locations, nullptr)); + ASSERT_EQ(locations.size(), 1); + EXPECT_TRUE(QFile::exists(locations.first())); + EXPECT_TRUE(locations.first().endsWith("cards.xml")); +} + +TEST(UserDataBackup, BackupMissingSourceFails) +{ + QTemporaryDir backupRoot; + ASSERT_TRUE(backupRoot.isValid()); + QStringList locations; + QString sessionDir; + EXPECT_FALSE(UserDataBackup::backupAtRiskPaths({QDir(backupRoot.path()).absoluteFilePath("does-not-exist.cod")}, + backupRoot.path(), &locations, &sessionDir)); + EXPECT_TRUE(locations.isEmpty()); + EXPECT_TRUE(sessionDir.isEmpty()); +} + +TEST(UserDataBackup, BackupPathsShareSingleSessionDir) +{ + QTemporaryDir sourceDir; + QTemporaryDir backupRoot; + ASSERT_TRUE(sourceDir.isValid()); + ASSERT_TRUE(backupRoot.isValid()); + QDir source(sourceDir.path()); + ASSERT_TRUE(source.mkpath("decks")); + ASSERT_TRUE(source.mkpath("settings")); + QFile deck(source.absoluteFilePath("decks/mydeck.cod")); + ASSERT_TRUE(deck.open(QIODevice::WriteOnly | QIODevice::Text)); + deck.write("\n"); + deck.close(); + QFile settings(source.absoluteFilePath("settings/settings.ini")); + ASSERT_TRUE(settings.open(QIODevice::WriteOnly | QIODevice::Text)); + settings.write("[general]\n"); + settings.close(); + + QStringList locations; + QString sessionDir; + EXPECT_TRUE( + UserDataBackup::backupAtRiskPaths({source.absoluteFilePath("decks"), source.absoluteFilePath("settings")}, + backupRoot.path(), &locations, &sessionDir)); + ASSERT_EQ(locations.size(), 2); + // Both backups live in the same timestamped session, even across a second boundary. + for (const QString &location : locations) { + EXPECT_TRUE(location.startsWith(QDir(sessionDir).absolutePath())); + } + EXPECT_TRUE(QFile::exists(QDir(sessionDir).absoluteFilePath("decks/mydeck.cod"))); + EXPECT_TRUE(QFile::exists(QDir(sessionDir).absoluteFilePath("settings/settings.ini"))); +} + +TEST(UserDataBackup, BackupPathsWithSameNameDoNotCollide) +{ + QTemporaryDir firstDir; + QTemporaryDir secondDir; + QTemporaryDir backupRoot; + ASSERT_TRUE(firstDir.isValid()); + ASSERT_TRUE(secondDir.isValid()); + ASSERT_TRUE(backupRoot.isValid()); + for (const QString &dir : {firstDir.path(), secondDir.path()}) { + QFile deck(QDir(dir).absoluteFilePath("cards.xml")); + ASSERT_TRUE(deck.open(QIODevice::WriteOnly | QIODevice::Text)); + deck.write("\n"); + deck.close(); + } + + QStringList locations; + EXPECT_TRUE(UserDataBackup::backupAtRiskPaths( + {QDir(firstDir.path()).absoluteFilePath("cards.xml"), QDir(secondDir.path()).absoluteFilePath("cards.xml")}, + backupRoot.path(), &locations, nullptr)); + ASSERT_EQ(locations.size(), 2); + EXPECT_NE(locations.at(0), locations.at(1)); + EXPECT_TRUE(QFile::exists(locations.at(0))); + EXPECT_TRUE(QFile::exists(locations.at(1))); +} + +TEST(UserDataBackup, RapidBackupsGetUniqueSessionDirs) +{ + QTemporaryDir sourceDir; + QTemporaryDir backupRoot; + ASSERT_TRUE(sourceDir.isValid()); + ASSERT_TRUE(backupRoot.isValid()); + QFile deck(QDir(sourceDir.path()).absoluteFilePath("mydeck.cod")); + ASSERT_TRUE(deck.open(QIODevice::WriteOnly | QIODevice::Text)); + deck.write("\n"); + deck.close(); + + QString firstSession; + QString secondSession; + QStringList locations; + EXPECT_TRUE(UserDataBackup::backupAtRiskPaths({sourceDir.path()}, backupRoot.path(), &locations, &firstSession)); + EXPECT_TRUE(UserDataBackup::backupAtRiskPaths({sourceDir.path()}, backupRoot.path(), &locations, &secondSession)); + EXPECT_NE(firstSession, secondSession); +} + +TEST(UserDataBackup, DefaultBackupRootIsUsable) +{ + QString root = UserDataBackup::defaultBackupRoot(); + EXPECT_FALSE(root.isEmpty()); + EXPECT_TRUE(QDir(root).isAbsolute()); +} + +TEST(UserDataBackup, DefaultBackupRootHasNoDotDotSegments) +{ + QString root = UserDataBackup::defaultBackupRoot(); + EXPECT_FALSE(root.contains(QStringLiteral(".."))); + EXPECT_EQ(root, QDir::cleanPath(root)); +} + +TEST(UserDataBackup, ListSessionsFindsRecentAndPrunesOld) +{ + QTemporaryDir sourceDir; + QTemporaryDir backupRoot; + ASSERT_TRUE(sourceDir.isValid()); + ASSERT_TRUE(backupRoot.isValid()); + QFile deck(QDir(sourceDir.path()).absoluteFilePath("mydeck.cod")); + ASSERT_TRUE(deck.open(QIODevice::WriteOnly | QIODevice::Text)); + deck.write("\n"); + deck.close(); + + QStringList locations; + QString firstSession; + QString secondSession; + ASSERT_TRUE(UserDataBackup::backupAtRiskPaths({sourceDir.path()}, backupRoot.path(), &locations, &firstSession)); + ASSERT_TRUE(UserDataBackup::backupAtRiskPaths({sourceDir.path()}, backupRoot.path(), &locations, &secondSession)); + + const QStringList sessions = UserDataBackup::listBackupSessions(backupRoot.path()); + ASSERT_EQ(sessions.size(), 2); + // Newest first + EXPECT_EQ(sessions.first(), secondSession); + + const QString recent = UserDataBackup::findRecentUsableBackupSession(backupRoot.path(), {sourceDir.path()}, 24); + EXPECT_EQ(recent, secondSession); + + const QStringList reusedLocations = UserDataBackup::listSessionBackupLocations(recent); + EXPECT_FALSE(reusedLocations.isEmpty()); + for (const QString &location : reusedLocations) { + EXPECT_TRUE(location.startsWith(QDir(recent).absolutePath())); + EXPECT_TRUE(QFile::exists(location)); + } + + UserDataBackup::pruneOldBackupSessions(backupRoot.path(), 1); + const QStringList pruned = UserDataBackup::listBackupSessions(backupRoot.path()); + ASSERT_EQ(pruned.size(), 1); + EXPECT_EQ(pruned.first(), secondSession); +} + +TEST(UserDataBackup, FindRecentReturnsEmptyWhenNone) +{ + QTemporaryDir backupRoot; + ASSERT_TRUE(backupRoot.isValid()); + EXPECT_TRUE(UserDataBackup::listBackupSessions(backupRoot.path()).isEmpty()); + EXPECT_TRUE(UserDataBackup::findRecentUsableBackupSession(backupRoot.path(), {backupRoot.path() + "/missing"}, 24) + .isEmpty()); + EXPECT_TRUE(UserDataBackup::findRecentCoveringBackupSession(backupRoot.path(), {backupRoot.path() + "/missing"}, 24) + .isEmpty()); + EXPECT_TRUE(UserDataBackup::listSessionBackupLocations(backupRoot.path() + "/missing").isEmpty()); + EXPECT_FALSE(UserDataBackup::isBackupSessionComplete(backupRoot.path() + "/missing")); + // Pruning an empty root must not fail + UserDataBackup::pruneOldBackupSessions(backupRoot.path(), 5); +} + +TEST(UserDataBackup, AllMissingSourcesCreateNoSession) +{ + QTemporaryDir backupRoot; + ASSERT_TRUE(backupRoot.isValid()); + QStringList locations; + QString sessionDir; + EXPECT_FALSE(UserDataBackup::backupAtRiskPaths({backupRoot.path() + "/nope1", backupRoot.path() + "/nope2"}, + backupRoot.path(), &locations, &sessionDir)); + EXPECT_TRUE(locations.isEmpty()); + EXPECT_TRUE(sessionDir.isEmpty()); + // No empty session left behind to block a retry or evict good sessions + EXPECT_TRUE(UserDataBackup::listBackupSessions(backupRoot.path()).isEmpty()); +} + +TEST(UserDataBackup, EmptySessionsAreSkippedAndPruned) +{ + QTemporaryDir backupRoot; + ASSERT_TRUE(backupRoot.isValid()); + QString emptySession; + ASSERT_TRUE(UserDataBackup::createBackupSessionDir(backupRoot.path(), &emptySession)); + // An empty session from a failed attempt must never block a retry + EXPECT_TRUE(UserDataBackup::listSessionBackupLocations(emptySession).isEmpty()); + EXPECT_FALSE(UserDataBackup::isBackupSessionComplete(emptySession)); + EXPECT_TRUE(UserDataBackup::findRecentUsableBackupSession(backupRoot.path(), {backupRoot.path() + "/missing"}, 24) + .isEmpty()); + EXPECT_TRUE(UserDataBackup::findRecentCoveringBackupSession(backupRoot.path(), {backupRoot.path() + "/missing"}, 24) + .isEmpty()); + UserDataBackup::pruneOldBackupSessions(backupRoot.path(), 5); + EXPECT_FALSE(QDir(emptySession).exists()); +} + +TEST(UserDataBackup, SuccessfulBackupIsMarkedComplete) +{ + QTemporaryDir sourceDir; + QTemporaryDir backupRoot; + ASSERT_TRUE(sourceDir.isValid()); + ASSERT_TRUE(backupRoot.isValid()); + QFile deck(QDir(sourceDir.path()).absoluteFilePath("mydeck.cod")); + ASSERT_TRUE(deck.open(QIODevice::WriteOnly | QIODevice::Text)); + deck.write("\n"); + deck.close(); + + QStringList locations; + QString sessionDir; + EXPECT_TRUE(UserDataBackup::backupAtRiskPaths({sourceDir.path()}, backupRoot.path(), &locations, &sessionDir)); + EXPECT_TRUE(UserDataBackup::isBackupSessionComplete(sessionDir)); + // Marker files are bookkeeping and must not leak into the user-facing locations + const QStringList listed = UserDataBackup::listSessionBackupLocations(sessionDir); + EXPECT_FALSE(listed.isEmpty()); + for (const QString &location : listed) { + EXPECT_FALSE(location.endsWith(QStringLiteral("backup.complete"))); + EXPECT_FALSE(location.endsWith(QStringLiteral("backup.incomplete"))); + } +} + +TEST(UserDataBackup, IncompleteMarkerIsNotUsableButRemainsCovering) +{ + QTemporaryDir sourceDir; + QTemporaryDir backupRoot; + ASSERT_TRUE(sourceDir.isValid()); + ASSERT_TRUE(backupRoot.isValid()); + QFile deck(QDir(sourceDir.path()).absoluteFilePath("mydeck.cod")); + ASSERT_TRUE(deck.open(QIODevice::WriteOnly | QIODevice::Text)); + deck.write("\n"); + deck.close(); + + QStringList locations; + QString sessionDir; + ASSERT_TRUE(UserDataBackup::backupAtRiskPaths({sourceDir.path()}, backupRoot.path(), &locations, &sessionDir)); + ASSERT_TRUE(QFile::remove(QDir(sessionDir).absoluteFilePath("backup.complete"))); + QFile marker(QDir(sessionDir).absoluteFilePath("backup.incomplete")); + ASSERT_TRUE(marker.open(QIODevice::WriteOnly | QIODevice::Text)); + marker.write("incomplete\n"); + marker.close(); + + EXPECT_FALSE(UserDataBackup::isBackupSessionComplete(sessionDir)); + // Incomplete sessions are skipped by the usable search but still found by the + // covering search, so a failed copy falls back to the salvaged content. + EXPECT_TRUE(UserDataBackup::findRecentUsableBackupSession(backupRoot.path(), {sourceDir.path()}, 24).isEmpty()); + EXPECT_EQ(UserDataBackup::findRecentCoveringBackupSession(backupRoot.path(), {sourceDir.path()}, 24), sessionDir); +} + +TEST(UserDataBackup, SessionWithoutMarkerCountsAsIncomplete) +{ + QTemporaryDir backupRoot; + ASSERT_TRUE(backupRoot.isValid()); + QString sessionDir; + ASSERT_TRUE(UserDataBackup::createBackupSessionDir(backupRoot.path(), &sessionDir)); + ASSERT_TRUE(QDir(sessionDir).mkpath("decks")); + QFile deck(QDir(sessionDir).absoluteFilePath("decks/mydeck.cod")); + ASSERT_TRUE(deck.open(QIODevice::WriteOnly | QIODevice::Text)); + deck.write("\n"); + deck.close(); + EXPECT_FALSE(UserDataBackup::isBackupSessionComplete(sessionDir)); + // No source manifest either, so neither search reuses it. + EXPECT_TRUE(UserDataBackup::findRecentUsableBackupSession(backupRoot.path(), {backupRoot.path() + "/missing"}, 24) + .isEmpty()); + EXPECT_TRUE(UserDataBackup::findRecentCoveringBackupSession(backupRoot.path(), {backupRoot.path() + "/missing"}, 24) + .isEmpty()); +} + +TEST(UserDataBackup, FindRecentIgnoresExpiredSessions) +{ + QTemporaryDir sourceDir; + QTemporaryDir backupRoot; + ASSERT_TRUE(sourceDir.isValid()); + ASSERT_TRUE(backupRoot.isValid()); + QFile deck(QDir(sourceDir.path()).absoluteFilePath("mydeck.cod")); + ASSERT_TRUE(deck.open(QIODevice::WriteOnly | QIODevice::Text)); + deck.write("\n"); + deck.close(); + + QStringList locations; + QString sessionDir; + ASSERT_TRUE(UserDataBackup::backupAtRiskPaths({sourceDir.path()}, backupRoot.path(), &locations, &sessionDir)); + EXPECT_TRUE(UserDataBackup::findRecentUsableBackupSession(backupRoot.path(), {sourceDir.path()}, 0).isEmpty()); + EXPECT_TRUE(UserDataBackup::findRecentCoveringBackupSession(backupRoot.path(), {sourceDir.path()}, 0).isEmpty()); +} + +TEST(UserDataBackup, EstimateBackupSize) +{ + QTemporaryDir sourceDir; + ASSERT_TRUE(sourceDir.isValid()); + const QString missing = QDir(sourceDir.path()).absoluteFilePath("missing"); + EXPECT_EQ(UserDataBackup::estimateBackupSize({missing}), 0); + QFile deck(QDir(sourceDir.path()).absoluteFilePath("mydeck.cod")); + ASSERT_TRUE(deck.open(QIODevice::WriteOnly | QIODevice::Text)); + deck.write("123456"); + deck.close(); + EXPECT_EQ(UserDataBackup::estimateBackupSize({sourceDir.path(), missing}), 6); + // Early-out: once over the cap the exact total no longer matters + EXPECT_GE(UserDataBackup::estimateBackupSize({sourceDir.path()}, 1), 1); +} + +TEST(UserDataBackup, SessionCoversSourcesRequiresManifestMatch) +{ + QTemporaryDir sourceA; + QTemporaryDir sourceB; + QTemporaryDir backupRoot; + ASSERT_TRUE(sourceA.isValid()); + ASSERT_TRUE(sourceB.isValid()); + ASSERT_TRUE(backupRoot.isValid()); + QFile fa(QDir(sourceA.path()).absoluteFilePath("a.cod")); + ASSERT_TRUE(fa.open(QIODevice::WriteOnly | QIODevice::Text)); + fa.write("a"); + fa.close(); + + QStringList locations; + QString sessionDir; + ASSERT_TRUE(UserDataBackup::backupAtRiskPaths({sourceA.path()}, backupRoot.path(), &locations, &sessionDir)); + EXPECT_TRUE(UserDataBackup::sessionCoversSources(sessionDir, {sourceA.path()})); + EXPECT_FALSE(UserDataBackup::sessionCoversSources(sessionDir, {sourceA.path(), sourceB.path()})); + EXPECT_FALSE(UserDataBackup::sessionCoversSources(sessionDir, {sourceB.path()})); +} + +TEST(UserDataBackup, SessionWithoutManifestIsNotCovering) +{ + QTemporaryDir backupRoot; + ASSERT_TRUE(backupRoot.isValid()); + QString sessionDir; + ASSERT_TRUE(UserDataBackup::createBackupSessionDir(backupRoot.path(), &sessionDir)); + ASSERT_TRUE(QDir(sessionDir).mkpath("decks")); + QFile deck(QDir(sessionDir).absoluteFilePath("decks/mydeck.cod")); + ASSERT_TRUE(deck.open(QIODevice::WriteOnly | QIODevice::Text)); + deck.write("\n"); + deck.close(); + EXPECT_FALSE(UserDataBackup::sessionCoversSources(sessionDir, {QDir(sessionDir).absoluteFilePath("decks")})); +} + +TEST(UserDataBackup, ManifestIsHiddenFromSessionLocations) +{ + QTemporaryDir sourceDir; + QTemporaryDir backupRoot; + ASSERT_TRUE(sourceDir.isValid()); + ASSERT_TRUE(backupRoot.isValid()); + QFile deck(QDir(sourceDir.path()).absoluteFilePath("mydeck.cod")); + ASSERT_TRUE(deck.open(QIODevice::WriteOnly | QIODevice::Text)); + deck.write("x"); + deck.close(); + + QStringList locations; + QString sessionDir; + ASSERT_TRUE(UserDataBackup::backupAtRiskPaths({sourceDir.path()}, backupRoot.path(), &locations, &sessionDir)); + for (const QString &location : UserDataBackup::listSessionBackupLocations(sessionDir)) { + EXPECT_FALSE(location.endsWith(QStringLiteral("backup.sources"))); + } +} + +TEST(UserDataBackup, FindRecentUsableSkipsIncompleteNewest) +{ + QTemporaryDir sourceDir; + QTemporaryDir backupRoot; + ASSERT_TRUE(sourceDir.isValid()); + ASSERT_TRUE(backupRoot.isValid()); + QFile deck(QDir(sourceDir.path()).absoluteFilePath("mydeck.cod")); + ASSERT_TRUE(deck.open(QIODevice::WriteOnly | QIODevice::Text)); + deck.write("\n"); + deck.close(); + + QStringList locations; + QString oldSession; + QString newSession; + ASSERT_TRUE(UserDataBackup::backupAtRiskPaths({sourceDir.path()}, backupRoot.path(), &locations, &oldSession)); + ASSERT_TRUE(UserDataBackup::backupAtRiskPaths({sourceDir.path()}, backupRoot.path(), &locations, &newSession)); + ASSERT_NE(oldSession, newSession); + + // Newest session becomes incomplete: usable search must fall back to the older complete one. + ASSERT_TRUE(QFile::remove(QDir(newSession).absoluteFilePath("backup.complete"))); + QFile marker(QDir(newSession).absoluteFilePath("backup.incomplete")); + ASSERT_TRUE(marker.open(QIODevice::WriteOnly | QIODevice::Text)); + marker.write("incomplete\n"); + marker.close(); + + EXPECT_EQ(UserDataBackup::findRecentUsableBackupSession(backupRoot.path(), {sourceDir.path()}), oldSession); + // Covering search still finds the newest session even though it is incomplete. + EXPECT_EQ(UserDataBackup::findRecentCoveringBackupSession(backupRoot.path(), {sourceDir.path()}), newSession); +} + +TEST(UserDataBackup, FindRecentUsableRequiresCoveringSet) +{ + QTemporaryDir sourceA; + QTemporaryDir sourceB; + QTemporaryDir backupRoot; + ASSERT_TRUE(sourceA.isValid()); + ASSERT_TRUE(sourceB.isValid()); + ASSERT_TRUE(backupRoot.isValid()); + QFile fa(QDir(sourceA.path()).absoluteFilePath("a.cod")); + ASSERT_TRUE(fa.open(QIODevice::WriteOnly | QIODevice::Text)); + fa.write("a"); + fa.close(); + QFile fb(QDir(sourceB.path()).absoluteFilePath("b.cod")); + ASSERT_TRUE(fb.open(QIODevice::WriteOnly | QIODevice::Text)); + fb.write("b"); + fb.close(); + + QStringList locations; + QString oldSession; + QString newSession; + ASSERT_TRUE(UserDataBackup::backupAtRiskPaths({sourceA.path()}, backupRoot.path(), &locations, &oldSession)); + ASSERT_TRUE(UserDataBackup::backupAtRiskPaths({sourceB.path()}, backupRoot.path(), &locations, &newSession)); + + // Newest covers B only, so a query for A must reuse the older session. + EXPECT_EQ(UserDataBackup::findRecentUsableBackupSession(backupRoot.path(), {sourceA.path()}), oldSession); + EXPECT_EQ(UserDataBackup::findRecentUsableBackupSession(backupRoot.path(), {sourceB.path()}), newSession); + EXPECT_TRUE( + UserDataBackup::findRecentUsableBackupSession(backupRoot.path(), {sourceA.path(), sourceB.path()}).isEmpty()); + EXPECT_TRUE(UserDataBackup::findRecentUsableBackupSession(backupRoot.path(), {}).isEmpty()); +} + +TEST(UserDataBackup, CopyDirectoryDoesNotSilentlyEmptyDirSymlink) +{ + QTemporaryDir sourceDir; + QTemporaryDir destParent; + ASSERT_TRUE(sourceDir.isValid()); + ASSERT_TRUE(destParent.isValid()); + QDir source(sourceDir.path()); + ASSERT_TRUE(source.mkpath("real")); + QFile inner(source.absoluteFilePath("real/card.cod")); + ASSERT_TRUE(inner.open(QIODevice::WriteOnly | QIODevice::Text)); + inner.write("x"); + inner.close(); + if (!QFile::link(source.absoluteFilePath("real"), source.absoluteFilePath("link"))) { + GTEST_SKIP() << "Symlinks not permitted in this environment"; + } + + QString dest = QDir(destParent.path()).absoluteFilePath("copy"); + const bool ok = UserDataBackup::copyDirectoryRecursively(sourceDir.path(), dest); + const QFileInfo linkCopy(QDir(dest).absoluteFilePath("link")); + // Old behavior created an empty plain dir and reported success, silently dropping contents. + // New behavior copies the link best-effort; if that fails it must report failure. + if (ok) { + EXPECT_TRUE(linkCopy.exists() || linkCopy.isSymLink()); + if (linkCopy.isDir() && !linkCopy.isSymLink()) { + EXPECT_FALSE(QDir(linkCopy.absoluteFilePath()).isEmpty()); + } + } else { + EXPECT_TRUE(linkCopy.isSymLink() || !linkCopy.exists() || !QDir(linkCopy.absoluteFilePath()).isEmpty()); + } +} + +TEST(UserDataBackup, PluralBackupClearsOutParamsOnTotalFailure) +{ + QTemporaryDir backupRoot; + ASSERT_TRUE(backupRoot.isValid()); + QStringList locations = {QStringLiteral("stale")}; + QString sessionDir = QStringLiteral("stale"); + const QString missing = QDir(backupRoot.path()).absoluteFilePath("does-not-exist.cod"); + EXPECT_FALSE(UserDataBackup::backupAtRiskPaths({missing}, backupRoot.path(), &locations, &sessionDir)); + EXPECT_TRUE(locations.isEmpty()); + EXPECT_TRUE(sessionDir.isEmpty()); +} + +} // namespace + +int main(int argc, char **argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/tools/installer-holder-probe/CMakeLists.txt b/tools/installer-holder-probe/CMakeLists.txt new file mode 100644 index 000000000..99e7d5045 --- /dev/null +++ b/tools/installer-holder-probe/CMakeLists.txt @@ -0,0 +1,36 @@ +# Windows-only. The installer runs this before anything is replaced (the running-instance +# guard in .onInit) and on the abort path of a failed purge, to name the applications and +# services holding the payload open (Restart Manager); see the GuardAppsNotRunning and +# LogProcessesUsingInstallDir functions in cmake/NSIS.template.in. It ships inside the +# installer and the uninstaller and is extracted to $PLUGINSDIR there, never installed, so +# it links nothing from this project: no Qt, and a static C runtime because the installed +# runtime is exactly what the purge may have failed to replace. +add_executable( + installer-holder-probe + close_holders.cpp + holder_line.cpp + installer_close_name.cpp + installer_close_request.cpp + main.cpp + payload_locks.cpp + payload_paths.cpp + restart_manager_holders.cpp +) + +target_link_libraries(installer-holder-probe PRIVATE rstrtmgr) + +# The close command is a header-only constant shared with the client. The probe still links +# nothing from this project: the header is plain C++ and pulls in no library. +target_include_directories(installer-holder-probe PRIVATE ${CMAKE_SOURCE_DIR}/libcockatrice_utility) + +# The NSIS template embeds the built file by the same relative path that resolves +# NSIS.definitions.nsh, and that path points at the build root. +set_target_properties( + installer-holder-probe + PROPERTIES MSVC_RUNTIME_LIBRARY "MultiThreaded" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY_DEBUG "${CMAKE_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL "${CMAKE_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY_RELEASE "${CMAKE_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${CMAKE_BINARY_DIR}" +) diff --git a/tools/installer-holder-probe/close_holders.cpp b/tools/installer-holder-probe/close_holders.cpp new file mode 100644 index 000000000..24fe7f0e7 --- /dev/null +++ b/tools/installer-holder-probe/close_holders.cpp @@ -0,0 +1,417 @@ +#include "close_holders.h" + +#include "installer_close_request.h" +#include "payload_paths.h" +#include "restart_manager_holders.h" + +// clang-format off +// windows.h has to come before anything that pulls in a Windows type, as everywhere in +// this tool. +#include +// clang-format on + +#include +#include +#include +#include + +namespace +{ +/// How long one poll of the asked processes takes. One WaitForSingleObject per process +/// and no enumeration, so polling costs nothing next to the seconds this is called on. +constexpr unsigned long WAIT_POLL_MILLISECONDS = 200; + +/// How long the report line may get before it is cut. NSIS holds one string in 1024 +/// characters, and this one is written there verbatim; every holder is named, so normally +/// the line stays far below it. +constexpr std::size_t REPORT_CHAR_MAX = 900; + +/// The holder as the log has to name it to be actionable. Deliberately not +/// formatHolder(): that one is worded for the dialog, where ", running" says something +/// the line here continues after. +std::wstring processLabel(const Holder &holder) +{ + std::wstring label = holder.imageName.empty() ? holder.appName : holder.imageName; + if (label.empty()) { + label = L"an unnamed process"; + } + label += L" (pid "; + label += std::to_wstring(holder.processId); + label += L")"; + return label; +} + +/// Joins the per-holder lines into the one line the installer logs and cuts it to what one +/// NSIS string holds. +std::wstring joinReport(const std::vector &lines) +{ + std::wstring report; + for (const std::wstring &line : lines) { + if (!report.empty()) { + report += L"; "; + } + report += line; + } + if (report.size() > REPORT_CHAR_MAX) { + report.erase(REPORT_CHAR_MAX); + report += L"..."; + } + return report; +} + +struct WindowSearch +{ + DWORD processId = 0; + std::vector windows; ///< every top-level window of the process, in Z-order +}; + +BOOL CALLBACK collectProcessWindow(HWND window, LPARAM parameter) +{ + auto &search = *reinterpret_cast(parameter); + DWORD processId = 0; + GetWindowThreadProcessId(window, &processId); + if (processId == search.processId) { + search.windows.push_back(window); + } + return TRUE; +} + +/// Whether a window is one the user could be asked to answer. +/// +/// Only a framed, non-empty window qualifies. Qt keeps hidden top-level windows of its own +/// next to the application's: the tray icon creates one to receive the TaskbarCreated +/// message, styled WS_OVERLAPPED - which is no frame at all - and titled +/// QTrayIconMessageWindow. Showing one of those puts an empty white rectangle on the +/// taskbar that nothing ever closes again: it has no window procedure of its own to hide it, +/// and the process is busy in the loop this is meant to break, so it stays until the process +/// dies. Posting a close to one is worse than useless too: Qt's tray window answers WM_CLOSE +/// by asking the application to terminate, without going through the confirmation the close +/// is supposed to reach. +bool isAnswerableWindow(HWND window) +{ + if ((GetWindowLongPtr(window, GWL_EXSTYLE) & WS_EX_TOOLWINDOW) != 0) { + return false; + } + if ((GetWindowLongPtr(window, GWL_STYLE) & WS_CAPTION) == 0) { + return false; + } + RECT bounds{}; + if (GetWindowRect(window, &bounds) == FALSE) { + return false; + } + return bounds.right > bounds.left && bounds.bottom > bounds.top; +} + +/// Picks the window to post the close to and the one the user should end up looking at. +struct CloseTarget +{ + HWND main = nullptr; ///< first answerable window without an owner: the application's own + HWND toRaise = nullptr; ///< topmost visible window: a dialog when one is up + bool hidden = false; ///< the window being closed is not on screen (notification area) +}; + +/// Only the windows the user could be asked to answer are candidates - see isAnswerableWindow. +/// +/// A hidden window is a candidate like a visible one: WM_CLOSE does not need a window on +/// screen, so an instance that is only in the notification area answers it the way it answers +/// its own close button, and the confirmation it raises then is a window Qt shows itself - +/// painted, unlike one restored from outside. Nothing is shown from here. Qt's own bookkeeping +/// windows stay excluded, because a close posted to the notification-area one terminates the +/// instance without the confirmation and that is worse than no close at all. +/// +/// @param windows every top-level window the process owns, in Z-order +/// @param askHidden when false, only a window that is on screen is a target: the launcher +/// mode reads "no window" as "past its own prompts and stuck", which stops being true +/// the moment a hidden window is asked to close +CloseTarget chooseCloseTarget(const std::vector &windows, bool askHidden) +{ + CloseTarget target; + for (const HWND window : windows) { + if (!isAnswerableWindow(window)) { + continue; + } + const bool visible = IsWindowVisible(window) != FALSE; + if (visible && target.toRaise == nullptr) { + target.toRaise = window; + } + if (target.main == nullptr && GetWindow(window, GW_OWNER) == nullptr && (visible || askHidden)) { + target.main = window; + target.hidden = !visible; + } + } + return target; +} + +/// What is left of the window the close was posted to, so a log line tells an instance that +/// answered by hiding from one that never answered at all. A process that hid its window and +/// stayed alive is the one the guard's end answer is for. +/// +/// @param window the window the close was posted to, or null when there was none +/// @param wasHidden whether that window was already off screen before the close +std::wstring describeWindowState(HWND window, bool wasHidden) +{ + if (window == nullptr || IsWindow(window) == FALSE) { + return L", its window is gone"; + } + // WM_NULL is the cheapest way to ask whether the thread behind the window still dispatches. + // A hung instance does not answer at all, which is a different problem from one that + // answered the close and stayed open, and the two want different words in the prompt. + DWORD_PTR ignored = 0; + const bool responding = + SendMessageTimeoutW(window, WM_NULL, 0, 0, SMTO_ABORTIFHUNG | SMTO_BLOCK, 1000, &ignored) != 0; + std::wstring state; + if (IsWindowVisible(window) != FALSE) { + state = L", window still open"; + } else { + state = wasHidden ? L", window still hidden" : L", window hidden again"; + } + if (!responding) { + state += L" and not responding"; + } + return state; +} + +/// Restores a minimized window and takes the foreground, so a confirmation Cockatrice +/// answers the close with is in front of the installer instead of behind it. +/// +/// A process in the background may not take the foreground on its own. Attaching to the +/// thread that owns the foreground window lifts that restriction for the duration of the +/// call; detaching afterwards leaves the two input queues as they were. +bool bringForward(HWND window) +{ + if (IsIconic(window) != FALSE) { + ShowWindow(window, SW_RESTORE); + } else { + ShowWindow(window, SW_SHOW); + } + + const HWND foreground = GetForegroundWindow(); + const DWORD foregroundThread = foreground != nullptr ? GetWindowThreadProcessId(foreground, nullptr) : 0; + const DWORD currentThread = GetCurrentThreadId(); + const bool attached = foregroundThread != 0 && foregroundThread != currentThread && + AttachThreadInput(currentThread, foregroundThread, TRUE) != FALSE; + BringWindowToTop(window); + SetForegroundWindow(window); + if (attached) { + AttachThreadInput(currentThread, foregroundThread, FALSE); + } + return GetForegroundWindow() == window; +} + +/// Waits for @p processes to signal, up to @p waitMilliseconds. Returns whether they all +/// exited and how long the wait took. +bool waitForExit(const std::vector &processes, unsigned long waitMilliseconds, unsigned long long &elapsed) +{ + const ULONGLONG start = GetTickCount64(); + const ULONGLONG deadline = start + waitMilliseconds; + for (;;) { + bool allExited = true; + for (const HANDLE process : processes) { + if (WaitForSingleObject(process, 0) != WAIT_OBJECT_0) { + allExited = false; + break; + } + } + elapsed = GetTickCount64() - start; + if (allExited || GetTickCount64() >= deadline) { + return allExited; + } + Sleep(WAIT_POLL_MILLISECONDS); + } +} +} // namespace + +bool isCockatriceImage(const std::wstring &imageName) +{ + constexpr wchar_t COCKATRICE_IMAGE[] = L"cockatrice.exe"; + return CompareStringOrdinal(imageName.c_str(), -1, COCKATRICE_IMAGE, -1, TRUE) == CSTR_EQUAL; +} + +CloseHoldersResult +closeCockatriceHolders(const std::vector &holders, unsigned long waitMilliseconds, bool askHiddenWindows) +{ + CloseHoldersResult result; + std::vector lines; + std::vector asked; + std::vector askedLines; + std::vector askedWindows; + std::vector askedWasHidden; + + for (const Holder &holder : holders) { + if (holder.isService || !isCockatriceImage(holder.imageName)) { + // Oracle and Servatrice are named by the prompt and close by hand: Oracle + // writes the card database in place. A service has no window to ask either. + lines.push_back(processLabel(holder) + L" left to you"); + continue; + } + + WindowSearch search; + search.processId = holder.processId; + EnumWindows(collectProcessWindow, reinterpret_cast(&search)); + CloseTarget target = chooseCloseTarget(search.windows, askHiddenWindows); + if (target.main == nullptr && !askHiddenWindows) { + // A launcher with no window on screen has already answered its own close and is + // stuck in shutdown (see the header): asking it again cannot reach a user who is + // not there, so it is named and the installer's wait ends it. + ++result.withoutWindow; + lines.push_back(processLabel(holder) + L" has no window of its own to close"); + continue; + } + + std::wstring line = processLabel(holder); + if (target.main == nullptr) { + // No window of the application's own at all: every window left is one of Qt's + // bookkeeping windows, which must never be asked to close. The installer socket + // below is the only thing that can reach it. + } else if (target.hidden) { + line += L" is hidden in the notification area"; + } else { + if (target.toRaise == nullptr) { + target.toRaise = target.main; + } + line += bringForward(target.toRaise) ? L" brought forward" : L" could not be brought forward"; + } + + // Kept open across the wait so a pid reused after the process exits cannot be + // mistaken for the instance that was asked to close. + const HANDLE process = OpenProcess(SYNCHRONIZE, FALSE, holder.processId); + const DWORD openError = process == nullptr ? GetLastError() : ERROR_SUCCESS; + + // Ask over the installer socket first. It is the same request as the window's close + // button - the application still answers its own confirmation - but it reaches an + // instance whose window is hidden in the notification area or is sitting on that + // confirmation, the two states a posted close cannot get through. A client from + // before the socket answers with a failed connect, so the posted close below stays + // the fallback and older versions keep behaving exactly as they did. + if (requestInstallerClose(holder.processId)) { + // "and" only when the line already says something about the instance's window. + line += target.main == nullptr ? L" asked to close over its installer socket" + : L" and asked to close over its installer socket"; + } else if (target.main == nullptr) { + // Nothing of the application's own to post a close to, and no socket to ask: the + // instance is named and the guard's end answer is what can free its files. + if (process != nullptr) { + CloseHandle(process); + } + ++result.withoutWindow; + lines.push_back(line + L" has no window of its own to close"); + continue; + } else if (PostMessageW(target.main, WM_CLOSE, 0, 0) == FALSE) { + // Posted to the instance's own window even when a question of its own is up. The + // close is what that window's close button does, and the application is the one + // that answers it: a current client refuses the close while its confirmation is + // pending, leaving the question in front, and only a client from before that guard + // accepts it and hides the window with the question. Withholding the request + // therefore protected nobody - it left the installer asking the user to close a + // program it had never asked - while the guard's end answer is what frees the files + // when the close cannot. + // + // Read the error before CloseHandle, which is free to reset it. + const DWORD postError = GetLastError(); + if (process != nullptr) { + CloseHandle(process); + } + lines.push_back(line + L", and the close request could not be sent (win32 " + std::to_wstring(postError) + + L")"); + continue; + } else { + line += L" and asked to close"; + } + ++result.askedToClose; + if (process == nullptr) { + line += L", but its exit could not be watched (win32 " + std::to_wstring(openError) + L")"; + } else { + askedLines.push_back(lines.size()); + asked.push_back(process); + askedWindows.push_back(target.main); + askedWasHidden.push_back(target.hidden); + } + lines.push_back(std::move(line)); + } + + if (!asked.empty() && waitMilliseconds > 0) { + unsigned long long elapsed = 0; + const bool allExited = waitForExit(asked, waitMilliseconds, elapsed); + for (std::size_t index = 0; index < asked.size(); ++index) { + const bool exited = allExited || WaitForSingleObject(asked[index], 0) == WAIT_OBJECT_0; + if (exited) { + lines[askedLines[index]] += L", exited after " + std::to_wstring(elapsed) + L" ms"; + } else if (askedWindows[index] == nullptr) { + // Asked over the socket with no window of its own: there is no window state + // to describe, only that it is still there. + lines[askedLines[index]] += L", still running after " + std::to_wstring(elapsed) + L" ms"; + } else { + lines[askedLines[index]] += L", still running after " + std::to_wstring(elapsed) + L" ms" + + describeWindowState(askedWindows[index], askedWasHidden[index]); + } + } + } + for (const HANDLE process : asked) { + CloseHandle(process); + } + + if (lines.empty()) { + result.report = L"nothing is holding the files"; + return result; + } + result.report = joinReport(lines); + return result; +} + +EndHoldersResult endHolders(const std::vector &holders, unsigned long waitMilliseconds) +{ + EndHoldersResult result; + std::vector lines; + std::vector ended; + std::vector endedLines; + + for (const Holder &holder : holders) { + if (!isPayloadProgram(holder.imageName)) { + // Not one of the programs the payload installs: a scanner that has a DLL open, or + // a program the user keeps in the install directory. It is named and left to the + // user, because ending it would destroy something this installer does not own. + lines.push_back(processLabel(holder) + L" left to you"); + continue; + } + + // Services included: the installer asked before getting here, and the Service + // Control Manager reports the terminated process as a stopped service afterwards. + const HANDLE process = OpenProcess(PROCESS_TERMINATE | SYNCHRONIZE, FALSE, holder.processId); + if (process == nullptr) { + lines.push_back(processLabel(holder) + L" could not be ended (win32 " + std::to_wstring(GetLastError()) + + L")"); + continue; + } + if (TerminateProcess(process, 1) == FALSE) { + // Read the error before CloseHandle, which is free to reset it. + const DWORD error = GetLastError(); + CloseHandle(process); + lines.push_back(processLabel(holder) + L" could not be ended (win32 " + std::to_wstring(error) + L")"); + continue; + } + ++result.ended; + endedLines.push_back(lines.size()); + lines.push_back(processLabel(holder) + L" ended"); + ended.push_back(process); + } + + if (!ended.empty() && waitMilliseconds > 0) { + unsigned long long elapsed = 0; + const bool allExited = waitForExit(ended, waitMilliseconds, elapsed); + for (std::size_t index = 0; index < ended.size(); ++index) { + const bool exited = allExited || WaitForSingleObject(ended[index], 0) == WAIT_OBJECT_0; + lines[endedLines[index]] += exited ? L", exited after " + std::to_wstring(elapsed) + L" ms" + : L", still running after " + std::to_wstring(elapsed) + L" ms"; + } + } + for (const HANDLE process : ended) { + CloseHandle(process); + } + + if (lines.empty()) { + result.report = L"nothing is holding the files"; + return result; + } + result.report = joinReport(lines); + return result; +} diff --git a/tools/installer-holder-probe/close_holders.h b/tools/installer-holder-probe/close_holders.h new file mode 100644 index 000000000..3f6c889fd --- /dev/null +++ b/tools/installer-holder-probe/close_holders.h @@ -0,0 +1,113 @@ +#ifndef CLOSE_HOLDERS_H +#define CLOSE_HOLDERS_H + +#include "holder_line.h" + +#include +#include + +/** + * @brief What asking the holders to close did, in the form the installer logs. + * + * @c report is one line naming every holder, including the ones this deliberately + * leaves alone, because the installer repeats it in its log. + * @c withoutWindow counts the holders that had no window of their own to ask: they were + * named and left alone, and the launcher mode reads that count as "nothing to ask" rather + * than as a window the user still has to answer. + */ +struct CloseHoldersResult +{ + unsigned int askedToClose = 0; ///< holders asked to close, over the socket or with WM_CLOSE + unsigned int withoutWindow = 0; ///< holders with no window of their own to ask + std::wstring report; ///< one line, narrowed to ASCII when written +}; + +/** + * @brief Whether @p imageName is the client the update belongs to, matched without case. + * + * The close mode asks only this image to close, and the launcher mode is only allowed to + * report it as safe to end, so both read the answer from here. + * + * @param imageName the file name of a process image, empty when it could not be read + */ +bool isCockatriceImage(const std::wstring &imageName); + +/** + * @brief Brings every running Cockatrice holding an installer file forward and asks it + * to close. + * + * The set is the Restart Manager answer, so it is exactly the processes holding a file of + * the previous version: nothing here has to guess at an executable path, and an + * application that merely shares the image name is never touched. Only cockatrice.exe is + * asked to close. Oracle and Servatrice are left to the user - Oracle writes the card + * database in place - and so is every service. + * + * One thing happens per instance: it is asked to close over the local socket it listens on, + * which is the same request as clicking its close button and is the only way to reach one + * whose window is hidden in the notification area or is sitting on its own confirmation. A + * client from before that socket is asked with WM_CLOSE on its own window instead, whether + * the window is on screen or only in the notification area, since the request does not need + * the window to be visible and the confirmation Cockatrice answers it with is a window Qt + * shows itself, painted, unlike one restored from outside. A window that is on screen is + * brought forward first, because that confirmation would otherwise open behind the + * installer's prompt; nothing is shown from here. Qt's own bookkeeping windows are never the + * target - posting a close to the notification-area one terminates the instance without its + * confirmation - so a process without a window of its own that does not answer the socket is + * named and left to the guard, which offers to end it. Stopping the process is never + * attempted here. + * + * Once the requests are out, this waits up to @c waitMilliseconds for the processes to + * exit, so the caller can measure the purge immediately afterwards instead of sleeping + * for a fixed time or re-asking a question this call already answered. A process that is + * still running is described further: whether its window is gone, still open, still + * hidden or hidden again, and whether it answers at all, so the log can tell an instance + * that refused the close from one that never received it. + * + * @param holders the Restart Manager answer, one entry per application or service + * @param waitMilliseconds how long to wait for the asked instances to exit, 0 to not wait + * @param askHiddenWindows whether a window that is not on screen is asked to close too. + * The guard wants that: an instance in the notification area is the one this update + * has no other way to reach. The launcher mode does not - a launcher without a + * visible window has already answered its own close and is stuck in shutdown, and + * the installer ends it instead of putting a question to a user who is not there. + */ +CloseHoldersResult +closeCockatriceHolders(const std::vector &holders, unsigned long waitMilliseconds, bool askHiddenWindows); + +/** + * @brief What ending the holders did, in the form the installer logs. + * + * @c report is one line naming every holder, including the ones this deliberately leaves + * alone, because the installer repeats it in its log. + */ +struct EndHoldersResult +{ + unsigned int ended = 0; ///< holders TerminateProcess was accepted for + std::wstring report; ///< one line, narrowed to ASCII when written +}; + +/** + * @brief Ends every running Cockatrice, Oracle and Servatrice the probe named. + * + * This is the last resort behind the installer's "end it now" answer, never a first move: a + * client that is still running may have an unsaved deck, an open game or a settings write in + * flight, Oracle may be part-way through writing the card database, and Servatrice may be + * serving a game, so the caller only gets here after the user picked it from a prompt that + * says so. + * + * Every program the payload installs is ended, services included. A service is terminated + * like a process rather than stopped through the Service Control Manager: the installer has + * asked the user first, and the SCM notices the process exit and marks the service stopped + * afterwards, so it can be started again normally. Anything else the Restart Manager named - + * a scanner holding a DLL, a program the user keeps in the install directory - is left to + * the user, because ending it would destroy something this installer does not own. + * + * Waits up to @c waitMilliseconds for what it ended to be gone, so the caller can measure the + * purge immediately afterwards instead of racing a process that is still exiting. + * + * @param holders what the probe named, one entry per application or service + * @param waitMilliseconds how long to wait for the ended instances to be gone, 0 to not wait + */ +EndHoldersResult endHolders(const std::vector &holders, unsigned long waitMilliseconds); + +#endif diff --git a/tools/installer-holder-probe/holder_line.cpp b/tools/installer-holder-probe/holder_line.cpp new file mode 100644 index 000000000..8d438ca17 --- /dev/null +++ b/tools/installer-holder-probe/holder_line.cpp @@ -0,0 +1,64 @@ +#include "holder_line.h" + +#include + +namespace +{ +std::wstring displayNameOf(const Holder &holder) +{ + // A service's application name is its long name, which is what the user can + // search for; its image name is usually a shared host process (svchost.exe) and + // would name the wrong thing. + if (holder.isService && !holder.appName.empty()) { + return holder.appName; + } + if (!holder.imageName.empty()) { + return holder.imageName; + } + if (!holder.appName.empty()) { + return holder.appName; + } + return L"an unnamed process"; +} +} // namespace + +std::wstring formatHolder(const Holder &holder) +{ + std::wstring line = displayNameOf(holder); + line += L" (pid "; + line += std::to_wstring(holder.processId); + if (holder.isService) { + line += L", service "; + line += holder.serviceShortName.empty() ? L"without a short name" : holder.serviceShortName; + } else { + line += L", running"; + } + line += L")"; + return line; +} + +std::wstring formatHolders(const std::vector &holders) +{ + const std::size_t wanted = std::min(holders.size(), HOLDER_LINE_MAX); + std::wstring line; + std::size_t listed = 0; + for (; listed < wanted; ++listed) { + const std::wstring entry = formatHolder(holders[listed]); + const std::size_t separator = line.empty() ? 0 : 2; + // The first holder is always named, however long its name: it is the answer the + // dialog exists to show, and Windows bounds one entry below the whole budget. + if (!line.empty() && line.size() + separator + entry.size() > HOLDER_LINE_CHAR_MAX) { + break; + } + if (!line.empty()) { + line += L"; "; + } + line += entry; + } + if (holders.size() > listed) { + line += L"; and "; + line += std::to_wstring(holders.size() - listed); + line += L" more"; + } + return line; +} diff --git a/tools/installer-holder-probe/holder_line.h b/tools/installer-holder-probe/holder_line.h new file mode 100644 index 000000000..675d79fc6 --- /dev/null +++ b/tools/installer-holder-probe/holder_line.h @@ -0,0 +1,62 @@ +#ifndef HOLDER_LINE_H +#define HOLDER_LINE_H + +#include +#include +#include + +/** + * @brief One application or service that has an installer file open or mapped. + * + * Deliberately free of Windows types: the wording is what the abort dialog shows, + * and it is the only part of the probe that can be exercised off Windows. + */ +struct Holder +{ + std::wstring imageName; ///< "cockatrice.exe", empty when the image could not be read + std::wstring appName; ///< Restart Manager's name for the application or service + std::wstring serviceShortName; ///< service short name, empty when this is not a service + unsigned long processId = 0; + bool isService = false; +}; + +/// How many holders the single-line summary names before it says how many were left out. +inline constexpr std::size_t HOLDER_LINE_MAX = 5; + +/** + * @brief How many characters the summary may spend on the holders it names. + * + * The line is not the whole string: every prompt puts it after a fixed paragraph and before + * the answers, and one NSIS string holds 1024 characters. Those paragraphs grew when the + * guard started spelling out what ending the programs costs and what is left to do by hand, + * so this budget is what keeps the longest message - the one with the longest listing, plus + * the "; and N more" the summary appends after the budget - complete, including the sentence + * that offers the other answer at its end. Windows caps one application or service name well + * below this, so it bounds the list without ever cutting a single holder short. + */ +inline constexpr std::size_t HOLDER_LINE_CHAR_MAX = 320; + +/** + * @brief Renders one holder the way the user has to recognise it, e.g. + * "cockatrice.exe (pid 24340, running)". + * + * The image name is preferred over the application name because it is what Task + * Manager shows, and the application name is what remains for a process whose + * image cannot be opened (another account, a protected process). A service is + * named by Restart Manager's long name and identified by its short name, because + * both are how the user finds it again in services.msc. + */ +std::wstring formatHolder(const Holder &holder); + +/** + * @brief Renders every holder as a single "; "-joined line. + * + * One line because it is written to the installer log as one and repeated in a + * message box, where nsExec hands the text back as one string. Only the first + * HOLDER_LINE_MAX are named, and only while they fit in HOLDER_LINE_CHAR_MAX + * characters; the rest are counted, so a machine with a dozen open handles cannot + * push the real answer off the dialog. + */ +std::wstring formatHolders(const std::vector &holders); + +#endif diff --git a/tools/installer-holder-probe/installer_close_name.cpp b/tools/installer-holder-probe/installer_close_name.cpp new file mode 100644 index 000000000..74659e151 --- /dev/null +++ b/tools/installer-holder-probe/installer_close_name.cpp @@ -0,0 +1,12 @@ +#include "installer_close_name.h" + +#include + +std::wstring installerCloseSocketName(unsigned long processId, const std::wstring &userName) +{ + std::wstring name = L"CockatriceInstallerClose-"; + name += std::to_wstring(processId); + name += L"-"; + name += userName; + return name; +} diff --git a/tools/installer-holder-probe/installer_close_name.h b/tools/installer-holder-probe/installer_close_name.h new file mode 100644 index 000000000..b659f8b8f --- /dev/null +++ b/tools/installer-holder-probe/installer_close_name.h @@ -0,0 +1,24 @@ +#ifndef INSTALLER_CLOSE_NAME_H +#define INSTALLER_CLOSE_NAME_H + +#include + +/** + * @brief The local-socket name a running instance listens on for the installer's close + * request. + * + * The name carries the process id, so every instance of the payload has its own socket - a + * second Cockatrice that was started independently, an Oracle and a Servatrice - and the + * installer can ask each one instead of guessing which is the primary. It also carries the + * account name, so one user's installer cannot reach another user's session. + * + * This is the same string the client builds with scopedLocalServerName() in + * libcockatrice_utility/local_server_name.h: `CockatriceInstallerClose--`. It is + * plain C++ so the probe and the client can be tested against each other off Windows. + * + * @param processId the id of the process that listens on the socket + * @param userName the account name scopedLocalServerName() appends + */ +std::wstring installerCloseSocketName(unsigned long processId, const std::wstring &userName); + +#endif // INSTALLER_CLOSE_NAME_H diff --git a/tools/installer-holder-probe/installer_close_request.cpp b/tools/installer-holder-probe/installer_close_request.cpp new file mode 100644 index 000000000..0475aedf3 --- /dev/null +++ b/tools/installer-holder-probe/installer_close_request.cpp @@ -0,0 +1,96 @@ +#include "installer_close_request.h" + +#include "installer_close_name.h" + +// clang-format off +// windows.h has to come before anything that pulls in a Windows type, as everywhere in +// this tool. +#include +// clang-format on + +#include +#include +#include +#include + +namespace +{ +/// How long a busy pipe may take before this gives up. The client answers in milliseconds; +/// the wait only covers a machine under load. +constexpr DWORD PIPE_WAIT_MILLISECONDS = 1000; + +std::wstring environmentVariable(const wchar_t *name) +{ + for (DWORD size = MAX_PATH; size <= 32768U; size *= 2) { + std::vector buffer(size); + const DWORD length = GetEnvironmentVariableW(name, buffer.data(), size); + if (length == 0) { + return {}; + } + if (length < size) { + return std::wstring(buffer.data(), length); + } + } + return {}; +} + +/// The account name scopedLocalServerName() appends: USER, then USERNAME, then the last +/// component of the profile path - the same order and the same fallbacks the client uses. +std::wstring currentUserName() +{ + for (const wchar_t *name : {L"USER", L"USERNAME"}) { + const std::wstring value = environmentVariable(name); + if (!value.empty()) { + return value; + } + } + const std::wstring profile = environmentVariable(L"USERPROFILE"); + const std::size_t separator = profile.find_last_of(L"\\/"); + if (separator == std::wstring::npos) { + return profile; + } + return profile.substr(separator + 1); +} + +/// The named pipe QLocalServer creates for the client's socket, or empty when the account +/// name could not be read and there is nothing to connect to. +std::wstring pipePath(unsigned long processId) +{ + const std::wstring userName = currentUserName(); + if (userName.empty()) { + return {}; + } + return L"\\\\.\\pipe\\" + installerCloseSocketName(processId, userName); +} +} // namespace + +bool requestInstallerClose(unsigned long processId) +{ + const std::wstring path = pipePath(processId); + if (path.empty()) { + return false; + } + // A client that is still serving the previous connection answers ERROR_PIPE_BUSY; one + // retry covers that, while a client that is not listening at all (an older version, or + // one that has already exited) is answered with a plain failure and the caller falls + // back to WM_CLOSE. + for (int attempt = 0; attempt < 2; ++attempt) { + const HANDLE pipe = CreateFileW(path.c_str(), GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); + if (pipe == INVALID_HANDLE_VALUE) { + if (GetLastError() == ERROR_PIPE_BUSY && WaitNamedPipeW(path.c_str(), PIPE_WAIT_MILLISECONDS) != FALSE) { + continue; + } + return false; + } + DWORD written = 0; + const BOOL ok = + WriteFile(pipe, INSTALLER_CLOSE_COMMAND, sizeof(INSTALLER_CLOSE_COMMAND) - 1, &written, nullptr); + CloseHandle(pipe); + // The request is in the pipe buffer, so the client reads it even though this handle + // is closed immediately. No acknowledgment is waited for: the caller only needs to + // know whether there was a listening instance to ask, and a hung client must not + // stall the guard. + return ok != FALSE && written == sizeof(INSTALLER_CLOSE_COMMAND) - 1; + } + return false; +} diff --git a/tools/installer-holder-probe/installer_close_request.h b/tools/installer-holder-probe/installer_close_request.h new file mode 100644 index 000000000..f30c49b2c --- /dev/null +++ b/tools/installer-holder-probe/installer_close_request.h @@ -0,0 +1,21 @@ +#ifndef INSTALLER_CLOSE_REQUEST_H +#define INSTALLER_CLOSE_REQUEST_H + +/** + * @brief Asks one running payload instance to close over its installer socket. + * + * A 3.1.3 client (and later) listens on a local socket named by + * installerCloseSocketName(). Connecting to it and writing one line is the same request as + * the window's close button, but it reaches an instance whose window is hidden in the + * notification area or is sitting on its own confirmation - the two states WM_CLOSE cannot + * get through. Older clients have no socket, so the caller still falls back to WM_CLOSE. + * + * Best effort: a failed connect or write is not an error. The caller has the window close + * and the guard's force-close answer left. + * + * @param processId the process to ask, as the Restart Manager named it + * @return true when the request was written to a listening instance + */ +bool requestInstallerClose(unsigned long processId); + +#endif // INSTALLER_CLOSE_REQUEST_H diff --git a/tools/installer-holder-probe/main.cpp b/tools/installer-holder-probe/main.cpp new file mode 100644 index 000000000..31901a0ec --- /dev/null +++ b/tools/installer-holder-probe/main.cpp @@ -0,0 +1,300 @@ +#include "close_holders.h" +#include "holder_line.h" +#include "payload_locks.h" +#include "payload_paths.h" +#include "restart_manager_holders.h" + +#include +#include +#include +#include +#include + +namespace +{ +/// The installer sets this, the same variable the running-instance guard uses, instead +/// of passing the directory on the command line: a path with spaces, quotes or a +/// trailing backslash then never has to survive NSIS quoting or a command line. +constexpr wchar_t INSTALL_DIR_VARIABLE[] = L"COCKATRICE_GUARD_DIR"; + +/// The installer sets this to the process it waits on (/PID=), so the launcher mode reads +/// the same process id the wait does instead of one that has to survive NSIS quoting. +constexpr wchar_t LAUNCHER_PID_VARIABLE[] = L"COCKATRICE_LAUNCHER_PID"; + +/// How long the close mode waits for the instance it asked to exit before it reports the +/// process as still running. The installer measures the purge right after this call, so +/// the wait belongs here: it is the only place that holds a handle on the process whose +/// exit it is waiting for. +constexpr unsigned long CLOSE_WAIT_MILLISECONDS = 30000; + +/// How long the end mode waits for what it terminated to be gone. TerminateProcess is +/// asynchronous, and the installer measures the purge right after this returns, so a process +/// that is still tearing down would otherwise be reported as a holder for one more round. +constexpr unsigned long END_WAIT_MILLISECONDS = 5000; + +/// Exit codes the installer reads. "Something was found" is deliberately not 0, so an +/// answer of "nothing holds the files" stays distinguishable from a probe that failed. +/// In the close mode 1 means a close was actually sent and 0 that every holder was left +/// to the user, which is what the report line next to the code spells out. 3 is for an +/// answer that could not name what it found: a lookup that failed and named nobody, or the +/// lookups naming nobody while a payload program refuses to be replaced, which is proof one +/// runs from the directory. 4 is only the launcher mode: the process the installer waits on +/// has no window a user could answer, which is the one answer the installer acts on by +/// itself instead of asking the user. +enum ExitCode +{ + EXIT_NO_HOLDERS = 0, + EXIT_HOLDERS_FOUND = 1, + EXIT_NOT_CONFIGURED = 2, + EXIT_PROBE_FAILED = 3, + EXIT_NO_WINDOW = 4, +}; + +/// The probe has four modes, selected by the first argument: +/// +/// (none) name what holds the payload, one line, for the guard prompt and the +/// abort dialog/log +/// --close-holders bring the running Cockatrice forward, ask it to close, and wait for +/// it to exit, for the offer to close after a failed purge +/// --end-holders end the running Cockatrice, Oracle and Servatrice and wait for them to +/// be gone, for the last resort the guard prompt offers when a close did +/// not free the files +/// --close-launcher ask the one process the installer waits on (/PID=) to close and say +/// whether there was a window to ask at all, because an instance with +/// no window has answered its own close and is stuck in shutdown +/// +/// The guard's three modes answer from three questions that cover each other's blind spots: +/// the Restart Manager names whoever has a payload file open, every payload program running +/// from the directory itself is named as well, because a running instance holds its own image +/// and the modules it loaded as mappings rather than open files and the Restart Manager does +/// not always report those, and a payload executable that refuses to be written is running +/// even when neither lookup could name it (see payloadExecutablesInUse). No one of them is +/// the guard's authority: an instance the process lookups do not name, while its executable +/// is locked, is exactly the case that left a half-replaced install behind, so it is reported +/// as a check that could not name what it found instead of as a free payload. +/// A program in a folder below the directory is not named by the second lookup: it is not an +/// instance of this payload, and the guard could neither close nor end it. +enum class Mode +{ + Name, ///< name the holders + Close, ///< ask the holders to close + End, ///< end the holders + CloseLauncher, ///< ask the one process the installer waits on to close +}; + +Mode requestedMode(int argumentCount, char **arguments) +{ + if (argumentCount > 1) { + const std::string mode(arguments[1]); + if (mode == "--close-holders") { + return Mode::Close; + } + if (mode == "--end-holders") { + return Mode::End; + } + if (mode == "--close-launcher") { + return Mode::CloseLauncher; + } + } + return Mode::Name; +} + +/// Writes one line to the pipe nsExec reads. Narrowed to ASCII on purpose: the bytes +/// come back to the installer as an ANSI string, so a non-ASCII process name would be +/// mojibake in the log; a '?' at least stays honest. Control characters are dropped +/// too, so a name can never forge a second log line. +void writeLine(const std::wstring &text) +{ + std::string narrow; + narrow.reserve(text.size() + 2); + for (const wchar_t character : text) { + narrow.push_back(character >= 0x20 && character < 0x7F ? static_cast(character) : '?'); + } + narrow += "\r\n"; + DWORD written = 0; + WriteFile(GetStdHandle(STD_OUTPUT_HANDLE), narrow.data(), static_cast(narrow.size()), &written, nullptr); +} + +/// The line for a payload executable that is in use while no lookup could name the program +/// holding it: the answer the guard shows, so it names the file the user has to free and +/// says that the program doing it is still to be closed by hand. +std::wstring inUseLine(const std::vector &files) +{ + std::wstring names; + for (const std::wstring &file : files) { + if (!names.empty()) { + names += L", "; + } + names += fileNameOf(file); + } + if (files.size() == 1) { + return names + L" is in use and the program holding it could not be identified"; + } + return names + L" are in use and the programs holding them could not be identified"; +} + +std::wstring environmentVariable(const wchar_t *name) +{ + for (DWORD size = MAX_PATH; size <= 32768U; size *= 2) { + std::vector buffer(size); + const DWORD length = GetEnvironmentVariableW(name, buffer.data(), size); + if (length == 0) { + return {}; + } + if (length < size) { + return std::wstring(buffer.data(), length); + } + } + return {}; +} + +std::wstring installDirFromEnvironment() +{ + return environmentVariable(INSTALL_DIR_VARIABLE); +} + +/// The process id the installer waits on, or false when it was not passed or is not a +/// number. A pid of 0 is refused because the installer never passes one. +bool launcherProcessIdFromEnvironment(unsigned long &processId) +{ + const std::wstring text = environmentVariable(LAUNCHER_PID_VARIABLE); + if (text.empty()) { + return false; + } + wchar_t *end = nullptr; + const unsigned long parsed = std::wcstoul(text.c_str(), &end, 10); + if (parsed == 0 || end == nullptr || *end != L'\0') { + return false; + } + processId = parsed; + return true; +} + +/// Asks the one process the installer waits on to close and answers what it could do. +/// +/// The guard closes a set and reports every holder; here there is exactly one process, and +/// the installer needs an answer it can act on rather than a sentence: a launcher with no +/// visible window has already answered its own close and is stuck in shutdown, so no answer +/// the installer offers would ever reach it. A window that was asked - including one the +/// instance is showing a question in - is still the user's decision. +int closeLauncher(const std::wstring &installDir) +{ + unsigned long processId = 0; + if (!launcherProcessIdFromEnvironment(processId)) { + writeLine(L"the probe was not given the launcher process id"); + return EXIT_NOT_CONFIGURED; + } + + // Only an instance that still runs from the directory being replaced is asked: a bare + // process id names anything once it is reused, while a process running from the + // directory is the one the installer started this update from. + for (const Holder &holder : findProcessesRunningFrom(installDir)) { + if (holder.processId != processId) { + continue; + } + if (!isCockatriceImage(holder.imageName)) { + writeLine(L"the launcher is not cockatrice.exe, left to you"); + return EXIT_PROBE_FAILED; + } + const CloseHoldersResult result = closeCockatriceHolders({holder}, CLOSE_WAIT_MILLISECONDS, false); + writeLine(result.report); + if (result.askedToClose > 0) { + return EXIT_HOLDERS_FOUND; + } + return result.withoutWindow > 0 ? EXIT_NO_WINDOW : EXIT_HOLDERS_FOUND; + } + // Not an answer: the lookup may have failed or refused the image, and the installer + // holds a live handle on that process, so "not found" must not read as "gone". The + // installer shows its dialog for this code instead of acting on it. + writeLine(L"the launcher is not running from the directory being replaced, left to you"); + return EXIT_PROBE_FAILED; +} +} // namespace + +int main(int argumentCount, char **arguments) +{ + const std::wstring installDir = installDirFromEnvironment(); + if (installDir.empty()) { + writeLine(L"the probe was not given the install directory"); + return EXIT_NOT_CONFIGURED; + } + + const Mode mode = requestedMode(argumentCount, arguments); + // The launcher mode answers about exactly one process, so the holder set below is what it + // does not need - and the Restart Manager call behind it is the slowest thing this tool + // does, which must not delay a wait that started with a ten-second grace. + if (mode == Mode::CloseLauncher) { + return closeLauncher(installDir); + } + + // The payload programs running from the directory first: they are the instance the user + // is asked to close, and the Restart Manager may not name any of them (see + // findProcessesRunningFrom). The file holders follow, deduplicated by process, so an + // instance that both runs from the directory and holds a payload file is named once. + // Neither lookup may name the probe or the process that started it, whichever one found + // it: an installer or uninstaller running from the directory it is replacing would + // otherwise be named as a holder, and closing it is a request nobody can answer. + std::vector holders = findProcessesRunningFrom(installDir); + const std::vector asking = askingProcessIds(); + std::vector fileHolders; + const unsigned long error = findProcessesUsingFiles(collectPayloadFiles(installDir), fileHolders); + for (const Holder &holder : fileHolders) { + if (std::find(asking.begin(), asking.end(), holder.processId) != asking.end()) { + continue; + } + const bool alreadyNamed = std::any_of(holders.begin(), holders.end(), [&holder](const Holder &named) { + return named.processId == holder.processId; + }); + if (!alreadyNamed) { + holders.push_back(holder); + } + } + + // Nobody was named. That is not yet the answer that nothing holds the payload: a lookup + // that failed only makes the probe unable to answer when it also named nobody, and a + // payload executable that refuses to be replaced is running even when neither lookup + // could name it. + if (holders.empty()) { + // Neither process lookup is proof that the payload is free: the Restart Manager does + // not report every mapping, and a program running under another account cannot be + // read from here, so "nobody was named" is not the answer "nothing is running". The + // file cannot be missed - a running image refuses the write the replacement needs - + // and an answer of "nothing is holding them" over that locked file is what sent + // updates into an uninstall that could only fail halfway. Reported as a check that + // could not name what it found, so the guard asks the user to close it and retry + // instead of replacing a payload that is still running. + const std::vector inUse = payloadExecutablesInUse(installDir); + if (!inUse.empty()) { + writeLine(inUseLine(inUse)); + return EXIT_PROBE_FAILED; + } + + if (error != ERROR_SUCCESS) { + writeLine(L"the Restart Manager lookup failed with win32 error " + std::to_wstring(error)); + return EXIT_PROBE_FAILED; + } + + // No output in the naming mode: the installer words the "nothing is holding them" + // case, and it owns the message either way. The acting modes say it themselves, + // because their line is logged verbatim next to what they were asked to do. + if (mode != Mode::Name) { + writeLine(L"nothing is holding the files"); + } + return EXIT_NO_HOLDERS; + } + if (mode == Mode::Name) { + writeLine(formatHolders(holders)); + return EXIT_HOLDERS_FOUND; + } + if (mode == Mode::End) { + const EndHoldersResult result = endHolders(holders, END_WAIT_MILLISECONDS); + writeLine(result.report); + return result.ended > 0 ? EXIT_HOLDERS_FOUND : EXIT_NO_HOLDERS; + } + + // Hidden windows are asked to close here: an instance in the notification area is the one + // this update has no other way to reach, and the guard's end answer is behind that attempt. + const CloseHoldersResult result = closeCockatriceHolders(holders, CLOSE_WAIT_MILLISECONDS, true); + writeLine(result.report); + return result.askedToClose > 0 ? EXIT_HOLDERS_FOUND : EXIT_NO_HOLDERS; +} diff --git a/tools/installer-holder-probe/payload_locks.cpp b/tools/installer-holder-probe/payload_locks.cpp new file mode 100644 index 000000000..83b77ac58 --- /dev/null +++ b/tools/installer-holder-probe/payload_locks.cpp @@ -0,0 +1,67 @@ +#include "payload_locks.h" + +#include "payload_paths.h" + +#include + +namespace +{ +/// How long the second look waits before it re-tests the files that refused the write open. +/// Only those are re-tested, so the one answer costs this at most once: a scanner, or a +/// program that is only starting, holds a file without write sharing for a moment, and that +/// must not turn a free payload into a run the user has to answer. +constexpr DWORD RECHECK_DELAY_MILLISECONDS = 300; + +/// Whether @p path refuses an open that asks to write it. +/// +/// Windows opens a program's image, and every module loaded into it, with read and delete +/// sharing but not write sharing, so this is refused for as long as the program runs and by +/// nothing else that merely reads the file. A read-only file is skipped: it refuses a write +/// open for its own reason and would answer yes for a payload nobody is running, and the +/// purge clears that attribute before it deletes anything. Any other error is no answer at +/// all - access this probe does not have says nothing about whether the file is in use, and +/// still answering "in use" would stop installs that could have finished. +bool refusesWrite(const std::wstring &path) +{ + const DWORD attributes = GetFileAttributesW(path.c_str()); + if (attributes == INVALID_FILE_ATTRIBUTES || + (attributes & (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_READONLY)) != 0) { + return false; + } + const HANDLE file = CreateFileW(path.c_str(), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (file == INVALID_HANDLE_VALUE) { + return GetLastError() == ERROR_SHARING_VIOLATION; + } + // Nothing was written: the open is the whole test, and closing the handle leaves the + // file exactly as it was. + CloseHandle(file); + return false; +} +} // namespace + +std::vector payloadExecutablesInUse(const std::wstring &installDir) +{ + std::vector refused; + for (const wchar_t *name : PAYLOAD_PROGRAMS) { + const std::wstring path = installDir + L"\\" + name; + if (refusesWrite(path)) { + refused.push_back(path); + } + } + if (refused.empty()) { + return refused; + } + + // One re-test of the files that refused, and only those: the answer is shown to a user + // who then has to close a program and run the installer again, so it has to be the + // settled state rather than a moment in which something happened to hold the file. + Sleep(RECHECK_DELAY_MILLISECONDS); + std::vector settled; + for (const std::wstring &path : refused) { + if (refusesWrite(path)) { + settled.push_back(path); + } + } + return settled; +} diff --git a/tools/installer-holder-probe/payload_locks.h b/tools/installer-holder-probe/payload_locks.h new file mode 100644 index 000000000..1d23648a2 --- /dev/null +++ b/tools/installer-holder-probe/payload_locks.h @@ -0,0 +1,36 @@ +#ifndef PAYLOAD_LOCKS_H +#define PAYLOAD_LOCKS_H + +#include +#include + +/** + * @brief The payload executables in @p installDir that refuse to be written, as full paths: + * the programs that are running from it right now. + * + * The guard asks two questions and both can come back empty for a running instance: the + * Restart Manager does not reliably report a program's own image or the modules it mapped, + * and the image path of a process running under another account cannot be read from here, + * so the process is not named either. The file itself is the evidence neither question + * depends on: a program's image is opened without write sharing for as long as it runs, so + * the write access replacing it needs is refused for exactly that long, while a file that is + * merely being read still accepts it. + * + * An installer that answers "nothing is holding the payload" from two lookups that both + * missed is what sent updates into an uninstall that could only fail halfway, leaving the + * previous version half removed. This answer is what keeps that from being said: a payload + * executable that refuses the write is running, whether or not this process may name it. + * + * Only the programs the payload installs are tested, not every file the purge removes. A + * payload executable refusing the write proves that it is running; a mapped module belongs + * to a running executable, which is tested. A DLL or qt.conf on its own can be held by the + * program that is asking - the uninstaller runs in place from the directory it empties - and + * naming that as a holder is the loop the guard already had. + * + * @return one full path per executable that still refused after the re-test, in the order + * PAYLOAD_PROGRAMS names them; empty when every one of them can be written, or none + * is installed. + */ +std::vector payloadExecutablesInUse(const std::wstring &installDir); + +#endif diff --git a/tools/installer-holder-probe/payload_paths.cpp b/tools/installer-holder-probe/payload_paths.cpp new file mode 100644 index 000000000..8d6274e91 --- /dev/null +++ b/tools/installer-holder-probe/payload_paths.cpp @@ -0,0 +1,48 @@ +#include "payload_paths.h" + +#include +#include +#include + +namespace +{ +/// Compares two file names the way the file system does, without a locale. +/// +/// Only the names above are ever compared against, and all of them are ASCII, so folding +/// the ASCII letters is the whole answer. The Windows ordinal comparison is not used here +/// on purpose: this file is compiled into a test that runs everywhere, and the question +/// "is this image a program the payload installs?" must be answerable without Windows. +bool equalsIgnoringAsciiCase(const std::wstring &left, const std::wstring &right) +{ + if (left.size() != right.size()) { + return false; + } + return std::equal(left.begin(), left.end(), right.begin(), [](wchar_t a, wchar_t b) { + const wchar_t foldedA = a >= L'A' && a <= L'Z' ? static_cast(a - L'A' + L'a') : a; + const wchar_t foldedB = b >= L'A' && b <= L'Z' ? static_cast(b - L'A' + L'a') : b; + return foldedA == foldedB; + }); +} +} // namespace + +std::wstring fileNameOf(const std::wstring &path) +{ + const std::size_t separator = path.find_last_of(L"\\/"); + return separator == std::wstring::npos ? path : path.substr(separator + 1); +} + +std::wstring parentDirectoryOf(const std::wstring &path) +{ + const std::size_t separator = path.find_last_of(L"\\/"); + return separator == std::wstring::npos ? std::wstring() : path.substr(0, separator); +} + +bool isPayloadProgram(const std::wstring &imageName) +{ + for (const wchar_t *program : PAYLOAD_PROGRAMS) { + if (equalsIgnoringAsciiCase(imageName, program)) { + return true; + } + } + return false; +} diff --git a/tools/installer-holder-probe/payload_paths.h b/tools/installer-holder-probe/payload_paths.h new file mode 100644 index 000000000..ea63732f5 --- /dev/null +++ b/tools/installer-holder-probe/payload_paths.h @@ -0,0 +1,51 @@ +#ifndef PAYLOAD_PATHS_H +#define PAYLOAD_PATHS_H + +#include + +/** + * @brief The file names of the programs the payload installs, in the order the guard names + * them. + * + * The single list behind isPayloadProgram(), so the guard acts on exactly the programs it + * names, and so a payload program added later is closed, ended and tested for a lock + * without a second list to update. A payload executable that refuses to be written is + * running, which is what makes this list the evidence the two process lookups cannot + * provide - see payloadExecutablesInUse(). + */ +inline constexpr const wchar_t *PAYLOAD_PROGRAMS[] = {L"cockatrice.exe", L"oracle.exe", L"servatrice.exe"}; + +/** + * @brief The last name in @p path: what follows its last separator, or @p path itself when + * it has none. + * + * Empty for an empty path and for one that ends in a separator, which has no name of its + * own - a caller that hands in a directory there gets no name rather than the directory's + * own last component. + */ +std::wstring fileNameOf(const std::wstring &path); + +/** + * @brief The directory @p path lies in, without a trailing separator. + * + * Empty when @p path has no directory part. Used to tell where a process was started from: + * an image running directly in the install directory has that directory as its parent, + * while one started from a folder below it has that folder, and the two are not the same + * answer to "does this process belong to the installation being replaced?". + */ +std::wstring parentDirectoryOf(const std::wstring &path); + +/** + * @brief Whether @p imageName is one of the programs the payload installs. + * + * The single place that answers which images are Cockatrice's own, so the guard acts on the + * same set it names, and so a program a user happens to keep in the install directory is + * never treated as an instance of the application. Matched on the image name alone, without + * case: the names are the payload's own, and the comparison is deliberately free of Windows + * API so it can be exercised off Windows. + * + * @param imageName the file name of a process image, empty when it could not be read + */ +bool isPayloadProgram(const std::wstring &imageName); + +#endif diff --git a/tools/installer-holder-probe/restart_manager_holders.cpp b/tools/installer-holder-probe/restart_manager_holders.cpp new file mode 100644 index 000000000..21ddd6988 --- /dev/null +++ b/tools/installer-holder-probe/restart_manager_holders.cpp @@ -0,0 +1,305 @@ +#include "restart_manager_holders.h" + +// clang-format off +// restartmanager.h uses the types from windows.h without including it, so the order +// here is a requirement; Regroup would sort them the other way round. +#include +#include +#include +// clang-format on + +#include +#include +#include + +namespace +{ +/// Stop registering after this many files. The install directory carries around 150 +/// files in practice; the cap only exists so a directory someone has filled with files +/// cannot turn the abort path into a long Restart Manager call. +constexpr std::size_t MAX_PROBED_FILES = 2000; + +/// How deep the Plugins walk goes. Its real shape is Plugins\\.dll. +constexpr int MAX_PLUGIN_DEPTH = 4; + +/// How many parents askingProcessIds() walks up before it stops on its own. A real chain +/// is the probe, the installer or uninstaller, and the shell that started it; the bound +/// only keeps a snapshot with a cycle in it from walking forever. +constexpr int MAX_ASKER_LEVELS = 8; + +void collectFilesOf(const std::wstring &directory, int depth, int maxDepth, std::vector &files) +{ + if (files.size() >= MAX_PROBED_FILES) { + return; + } + WIN32_FIND_DATAW entry{}; + const HANDLE find = FindFirstFileW((directory + L"\\*").c_str(), &entry); + if (find == INVALID_HANDLE_VALUE) { + return; + } + do { + if (files.size() >= MAX_PROBED_FILES) { + break; + } + const std::wstring name = entry.cFileName; + if (name == L"." || name == L".." || (entry.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { + continue; + } + const std::wstring path = directory + L"\\" + name; + if ((entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) { + if (depth < maxDepth) { + collectFilesOf(path, depth + 1, maxDepth, files); + } + } else { + files.push_back(path); + } + } while (FindNextFileW(find, &entry) != 0); + FindClose(find); +} + +std::wstring imagePathOf(unsigned long processId) +{ + // PROCESS_QUERY_LIMITED_INFORMATION is the right to ask, not to debug: it reads the + // path of a process this one does not own, which is what keeps a SYSTEM-owned holder + // from turning the whole list into an error. One that refuses even this is still + // named, by whatever Restart Manager called it. + const HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, processId); + if (process == nullptr) { + return {}; + } + wchar_t path[MAX_PATH * 2] = {}; + DWORD length = static_cast(std::size(path)); + std::wstring imagePath; + if (QueryFullProcessImageNameW(process, 0, path, &length) != 0) { + imagePath.assign(path, length); + } + CloseHandle(process); + return imagePath; +} + +/// @p directory with exactly one trailing separator, so that two spellings of the same +/// directory compare equal and a sibling whose name merely begins with the same letters +/// ("Cockatrice-old" next to "Cockatrice") cannot pass for it. +std::wstring directoryPrefixOf(const std::wstring &directory) +{ + std::wstring prefix = directory; + while (!prefix.empty() && (prefix.back() == L'\\' || prefix.back() == L'/')) { + prefix.pop_back(); + } + prefix += L'\\'; + return prefix; +} + +/// Whether @p left and @p right name the same directory, compared the way the file system +/// does: a difference in case is not a difference in directory, and a trailing separator +/// changes nothing. +bool isSameDirectory(const std::wstring &left, const std::wstring &right) +{ + if (left.empty() || right.empty()) { + return false; + } + const std::wstring leftPrefix = directoryPrefixOf(left); + const std::wstring rightPrefix = directoryPrefixOf(right); + return CompareStringOrdinal(leftPrefix.c_str(), -1, rightPrefix.c_str(), -1, TRUE) == CSTR_EQUAL; +} + +/// Copies a fixed-size, NUL-terminated WCHAR array the API filled in. +template std::wstring fixedString(const wchar_t (&text)[N]) +{ + return std::wstring(text, std::find(text, text + N, L'\0')); +} + +Holder holderOf(const RM_PROCESS_INFO &info) +{ + Holder holder; + holder.processId = info.Process.dwProcessId; + holder.isService = info.ApplicationType == RmService; + holder.appName = fixedString(info.strAppName); + if (holder.isService) { + holder.serviceShortName = fixedString(info.strServiceShortName); + } + holder.imageName = fileNameOf(imagePathOf(holder.processId)); + return holder; +} +} // namespace + +std::vector collectPayloadFiles(const std::wstring &installDir) +{ + std::vector files; + // The programs the payload installs, by name, plus the configuration file next to + // them: what the purge deletes by name, and therefore what an instance can have + // mapped. + std::vector replaced(std::begin(PAYLOAD_PROGRAMS), std::end(PAYLOAD_PROGRAMS)); + replaced.emplace_back(L"qt.conf"); + for (const std::wstring &name : replaced) { + const std::wstring path = installDir + L"\\" + name; + const DWORD attributes = GetFileAttributesW(path.c_str()); + if (attributes != INVALID_FILE_ATTRIBUTES && (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0) { + files.push_back(path); + } + } + + // Every top-level DLL: the set the purge deletes and therefore the set a running + // instance can have mapped. Matched by mask rather than by a list of expected names, + // so a transitive dependency nobody thought of is covered as well. + WIN32_FIND_DATAW entry{}; + const HANDLE dlls = FindFirstFileW((installDir + L"\\*.dll").c_str(), &entry); + if (dlls != INVALID_HANDLE_VALUE) { + do { + if (files.size() >= MAX_PROBED_FILES) { + break; + } + if ((entry.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0 || + (entry.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) { + continue; + } + files.push_back(installDir + L"\\" + entry.cFileName); + } while (FindNextFileW(dlls, &entry) != 0); + FindClose(dlls); + } + + // Plugins is the other tree the purge removes. Its data directory is not: it holds + // downloaded pictures, which the purge does not touch and which would turn this into + // a walk over thousands of files. + if (files.size() < MAX_PROBED_FILES) { + collectFilesOf(installDir + L"\\Plugins", 0, MAX_PLUGIN_DEPTH, files); + } + return files; +} + +unsigned long findProcessesUsingFiles(const std::vector &files, std::vector &holders) +{ + holders.clear(); + if (files.empty()) { + return ERROR_SUCCESS; + } + + DWORD session = 0; + wchar_t sessionKey[CCH_RM_SESSION_KEY + 1] = {}; + DWORD error = RmStartSession(&session, 0, sessionKey); + if (error != ERROR_SUCCESS) { + return error; + } + + // One call for the whole set, as the API asks: RmRegisterResources does registry + // work per file and is documented as too expensive to call once per file. + std::vector names; + names.reserve(files.size()); + for (const std::wstring &file : files) { + names.push_back(file.c_str()); + } + error = RmRegisterResources(session, static_cast(names.size()), names.data(), 0, nullptr, 0, nullptr); + + if (error == ERROR_SUCCESS) { + UINT needed = 0; + UINT count = 0; + DWORD rebootReasons = 0; + // The first call asks how large the answer is; Restart Manager reports + // ERROR_MORE_DATA rather than truncating a list that does not fit. + error = RmGetList(session, &needed, &count, nullptr, &rebootReasons); + // Bounded: each retry more than covers the list growing between the two calls, + // and an abort path cannot chase a process that keeps opening files. Giving up + // reports the code, which is a truthful answer instead of a truncated list. + for (int attempt = 0; attempt < 3 && error == ERROR_MORE_DATA; ++attempt) { + count = needed; + std::vector infos(count); + error = RmGetList(session, &needed, &count, infos.data(), &rebootReasons); + if (error == ERROR_SUCCESS) { + holders.reserve(count); + for (UINT index = 0; index < count; ++index) { + holders.push_back(holderOf(infos[index])); + } + } + } + } + + RmEndSession(session); + return error; +} + +std::vector askingProcessIds() +{ + std::vector asking; + asking.push_back(GetCurrentProcessId()); + + // One snapshot for the whole walk: the parent ids come from it, so a level costs no + // second lookup. + std::vector processes; + const HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snapshot == INVALID_HANDLE_VALUE) { + return asking; + } + PROCESSENTRY32W entry{}; + entry.dwSize = sizeof(entry); + if (Process32FirstW(snapshot, &entry) != FALSE) { + do { + processes.push_back(entry); + } while (Process32NextW(snapshot, &entry) != FALSE); + } + CloseHandle(snapshot); + + unsigned long current = asking.back(); + for (int level = 0; level < MAX_ASKER_LEVELS; ++level) { + const auto named = std::find_if(processes.begin(), processes.end(), [current](const PROCESSENTRY32W &process) { + return process.th32ProcessID == current; + }); + if (named == processes.end() || named->th32ParentProcessID == 0) { + break; + } + const unsigned long parent = named->th32ParentProcessID; + if (isPayloadProgram(fileNameOf(imagePathOf(parent)))) { + break; + } + asking.push_back(parent); + current = parent; + } + return asking; +} + +std::vector findProcessesRunningFrom(const std::wstring &installDir) +{ + std::vector holders; + if (installDir.empty()) { + return holders; + } + + // Never the probe, and never the installer or uninstaller that started it: either can + // run from the directory being replaced, and naming one asks the user to close the + // program that is asking (see askingProcessIds). + const std::vector asking = askingProcessIds(); + + const HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (snapshot == INVALID_HANDLE_VALUE) { + return holders; + } + PROCESSENTRY32W entry{}; + entry.dwSize = sizeof(entry); + if (Process32FirstW(snapshot, &entry) != FALSE) { + do { + if (std::find(asking.begin(), asking.end(), entry.th32ProcessID) != asking.end()) { + continue; + } + // One process at a time, because the path is per process; a process that + // refuses to be read is not named here and is still named by Restart + // Manager when it holds a handle. The idle process has no image at all. + const std::wstring imagePath = imagePathOf(entry.th32ProcessID); + const std::wstring imageName = fileNameOf(imagePath); + // Only the programs the payload installs, and only those started from the + // directory itself: a folder a user installs into can hold other applications, + // each in its own subfolder, and one of those is not an instance of this + // payload. Naming it would ask for a close the probe cannot send and an end it + // would refuse to perform, so the guard could only ever abort. A program below + // the directory that really does hold a payload file is named by + // findProcessesUsingFiles, which is the lookup for that question. + if (!isPayloadProgram(imageName) || !isSameDirectory(parentDirectoryOf(imagePath), installDir)) { + continue; + } + Holder holder; + holder.processId = entry.th32ProcessID; + holder.imageName = imageName; + holders.push_back(std::move(holder)); + } while (Process32NextW(snapshot, &entry) != FALSE); + } + CloseHandle(snapshot); + return holders; +} diff --git a/tools/installer-holder-probe/restart_manager_holders.h b/tools/installer-holder-probe/restart_manager_holders.h new file mode 100644 index 000000000..e966b70b8 --- /dev/null +++ b/tools/installer-holder-probe/restart_manager_holders.h @@ -0,0 +1,88 @@ +#ifndef RESTART_MANAGER_HOLDERS_H +#define RESTART_MANAGER_HOLDERS_H + +#include "holder_line.h" +#include "payload_paths.h" + +#include +#include + +/** + * @brief Lists the payload files, as full paths. + * + * The set is what the installer replaces and the purge removes: the three executables, + * qt.conf, every top-level DLL and everything below Plugins. A document, a log or a + * screenshot the user happens to have open in the install directory is not part of it, + * so it cannot be mistaken for a running instance and block an upgrade. + * + * Restart Manager refuses a directory with ERROR_ACCESS_DENIED, which is why the probe + * registers files and this has to name them first. Missing files and unreadable + * directories are skipped rather than reported, and the list is capped: this runs before + * the first page and on the abort path, and must not walk a tree of downloaded pictures. + */ +std::vector collectPayloadFiles(const std::wstring &installDir); + +/** + * @brief Names the applications and services that have one of @p files open or mapped. + * + * Restart Manager reports both a process that loaded one of the files as a module and + * one that merely holds a handle to it, and it reports services, which is the whole + * reason it is used instead of walking every process's module list. + * + * @param holders replaced with one entry per holder, in the order Windows reported them. + * @return 0 when the lookup ran, even if nothing is holding the files; otherwise the + * Win32 code of the Restart Manager call that failed. + */ +unsigned long findProcessesUsingFiles(const std::vector &files, std::vector &holders); + +/** + * @brief Lists every payload program whose image lies in @p installDir itself. + * + * Restart Manager answers the question "who has this file open", and a running instance + * does not necessarily have one open: its own image and the modules it loaded are mapped + * into it, not opened by it, and RmGetList does not reliably report a mapping. That is + * the state the failed updates this probe exists for were in - every DLL still on disk, + * an instance running from the directory, and "nothing is holding them any more" as the + * answer - so an answer built on file handles alone cannot be the guard's authority. + * + * Only the programs the payload installs are named, and only when the image lies directly + * in @p installDir. A folder a user installs into can hold other applications, each in its + * own subfolder, and those are not instances of this one: they hold none of the payload, + * neither closing nor ending them would free a file the purge removes, and naming them + * leaves the guard with an answer that can never be carried out. A program below the + * directory that does hold a payload file is still named, by findProcessesUsingFiles. + * + * Reading the image path needs PROCESS_QUERY_LIMITED_INFORMATION and + * QueryFullProcessImageNameW, neither of which depends on the bitness of either process, + * so a 64-bit instance is named from this 32-bit stub. The comparison is on the directory, + * not on an image name alone, so a program that merely shares a name with a payload program + * is never named. + * + * @param installDir the directory being replaced, as a full path. + * @return one entry per payload program running from it, in snapshot order, without the + * processes named by askingProcessIds(); empty when the directory is empty or no + * payload program runs from it. + */ +std::vector findProcessesRunningFrom(const std::wstring &installDir); + +/** + * @brief The processes that must never be named as holders: the probe and the ones that + * started it. + * + * The installer or uninstaller doing the asking can legitimately run from the directory it + * is replacing - the old uninstaller is started with _?= so it stays in $INSTDIR, and an + * installer can be downloaded into the folder - and naming it asks the user to close the + * program that is asking. That cannot be answered: the close skips it, the end offer skips + * it, and the guard loops until the user cancels, which is what a run that named itself + * did. + * + * The walk up the parent chain stops at the first program the payload installs, because in + * an in-app update the installer's own parent is the Cockatrice instance being replaced - + * the one the guard exists to name. + * + * @return process ids, the probe first, then its ancestors while they are not payload + * programs; only the probe itself when the snapshot fails. + */ +std::vector askingProcessIds(); + +#endif diff --git a/vcpkg.json b/vcpkg.json index f12227024..8c9c64161 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,6 +1,6 @@ { "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json", - "builtin-baseline": "9e593bb18ea69cc5095e012465dcd675a822ed0d", + "builtin-baseline": "9eec002df1cd109f8f104cd966a763daf9751489", "dependencies": [ "gtest", "liblzma",