From 75c70096239b760086331ccc863db5f96f6d2757 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 30 Jun 2025 20:00:32 -0500 Subject: [PATCH 01/17] removed clan error logs --- internal/logic/snapshots.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/internal/logic/snapshots.go b/internal/logic/snapshots.go index 0e569df0..1a02ebab 100644 --- a/internal/logic/snapshots.go +++ b/internal/logic/snapshots.go @@ -163,10 +163,7 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl go func() { defer group.Done() // clans are optional-ish - data, err := wgClient.BatchAccountClan(ctx, realm, accountsNeedAnUpdate) - if err != nil && err.Error() != "SOURCE_NOT_AVAILABLE" { - log.Err(err).Msg("failed to get batch account clans") - } + data, _ := wgClient.BatchAccountClan(ctx, realm, accountsNeedAnUpdate) clans = data }() From e084abfd4654144244d1a4509d1718ea083ef7fe Mon Sep 17 00:00:00 2001 From: Vovko Date: Sat, 26 Jul 2025 09:35:06 -0500 Subject: [PATCH 02/17] updated interaction expiry --- cmd/core/tasks/cleanup.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/core/tasks/cleanup.go b/cmd/core/tasks/cleanup.go index f22b5bda..acb2e220 100644 --- a/cmd/core/tasks/cleanup.go +++ b/cmd/core/tasks/cleanup.go @@ -58,9 +58,9 @@ func CreateCleanupTasks(client core.Client) error { ReferenceID: "database_cleanup", Type: models.TaskTypeDatabaseCleanup, Data: map[string]string{ - "expiration_interactions": now.Add(-1 * time.Hour * 24 * 7).Format(time.RFC3339), // 7 days - "expiration_snapshots": now.Add(-1 * time.Hour * 24 * 90).Format(time.RFC3339), // 90 days - "expiration_tasks": now.Add(-1 * time.Hour * 24 * 7).Format(time.RFC3339), // 7 days + "expiration_interactions": now.Add(-1 * time.Hour * 24 * 45).Format(time.RFC3339), // 45 days + "expiration_snapshots": now.Add(-1 * time.Hour * 24 * 90).Format(time.RFC3339), // 90 days + "expiration_tasks": now.Add(-1 * time.Hour * 24 * 7).Format(time.RFC3339), // 7 days }, } From 6aae144eed4862a63bf9f40d8e0b5b36addcfb58 Mon Sep 17 00:00:00 2001 From: Vovko Date: Mon, 28 Jul 2025 07:29:15 -0500 Subject: [PATCH 03/17] decreased cta freq --- cmd/discord/cta/middleware.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/discord/cta/middleware.go b/cmd/discord/cta/middleware.go index 059a11a7..55b58046 100644 --- a/cmd/discord/cta/middleware.go +++ b/cmd/discord/cta/middleware.go @@ -14,8 +14,8 @@ import ( "github.com/guregu/null/v6" ) -var DefaultCooldownDM = time.Hour * 24 -var DefaultCooldownServer = time.Hour * 6 +var DefaultCooldownDM = time.Hour * 24 * 7 +var DefaultCooldownServer = time.Hour * 24 * 3 var followUpResponseDelay = time.Second * 15 From ab45dbfe0230cd378a28e2d04aa03a1c2728d564 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Nov 2025 08:38:31 -0600 Subject: [PATCH 04/17] added backup image --- .github/workflows/build-packages.yml | 31 +++++++++++++++ Dockerfile.backup | 12 ++++++ database-backup.sh | 46 ++++++++++++++++++++++ database-restore.sh | 57 ++++++++++++++++++++++++++++ docker-compose.base.yaml | 14 ++++++- docker-compose.dokploy.yaml | 22 +++++++++++ 6 files changed, 181 insertions(+), 1 deletion(-) create mode 100644 Dockerfile.backup create mode 100644 database-backup.sh create mode 100644 database-restore.sh diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml index b729ca25..b76365b1 100644 --- a/.github/workflows/build-packages.yml +++ b/.github/workflows/build-packages.yml @@ -49,6 +49,20 @@ jobs: type=semver,pattern={{major}} type=raw,latest type=sha + - name: Extract backup metadata (tags, labels) for Docker + id: meta-backup + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-backup + tags: | + type=schedule + type=ref,event=pr + type=ref,event=branch + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + type=raw,latest + type=sha - name: Extract service metadata (tags, labels) for Docker id: meta-service uses: docker/metadata-action@v5 @@ -89,6 +103,17 @@ jobs: tags: ${{ steps.meta-migrations.outputs.tags }} labels: ${{ steps.meta-migrations.outputs.labels }} file: Dockerfile.migrate + - name: Build and push backup Docker image + id: push-backup + uses: docker/build-push-action@v6 + with: + push: true + context: . + platforms: linux/amd64 + tags: ${{ steps.meta-backup.outputs.tags }} + labels: ${{ steps.meta-backup.outputs.labels }} + file: Dockerfile.backup + # This step generates an artifact attestation for the image, which is an unforgeable statement about where and how it was built. It increases supply chain security for people who consume the image. For more information, see "[AUTOTITLE](/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)." - name: Generate migrations artifact attestation uses: actions/attest-build-provenance@v1 @@ -96,6 +121,12 @@ jobs: subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}-migrations subject-digest: ${{ steps.push-migrations.outputs.digest }} push-to-registry: true + - name: Generate backup artifact attestation + uses: actions/attest-build-provenance@v1 + with: + subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}-backup + subject-digest: ${{ steps.push-backup.outputs.digest }} + push-to-registry: true - name: Generate service artifact attestation uses: actions/attest-build-provenance@v1 with: diff --git a/Dockerfile.backup b/Dockerfile.backup new file mode 100644 index 00000000..00badc19 --- /dev/null +++ b/Dockerfile.backup @@ -0,0 +1,12 @@ +FROM postgres:17 + +# add AWS cli +RUN apt-get update; \ + apt-get install -y --no-install-recommends \ + awscli + +# add the backup script +COPY ./database-backup.sh /backup.sh +RUN chmod +x /backup.sh + +ENTRYPOINT [ "/backup.sh" ] \ No newline at end of file diff --git a/database-backup.sh b/database-backup.sh new file mode 100644 index 00000000..7c2e33f2 --- /dev/null +++ b/database-backup.sh @@ -0,0 +1,46 @@ +#!/bin/bash +set -e + +# Configuration +TIMESTAMP=$(date +"%Y-%m-%d_%H-%M-%S") +BACKUP_FILE="backup_${TIMESTAMP}.sql" +S3_PATH="s3://${S3_BUCKET}/${S3_PREFIX}${BACKUP_FILE}" + +# Determine connection source +if [ -n "$DATABASE_URL" ]; then + CONNECTION_ARG="$DATABASE_URL" + echo "Using DATABASE_URL for connection." +elif [ -n "$PGDATABASE" ]; then + # Fallback to standard libpq env vars (PGHOST, PGUSER, etc.) + CONNECTION_ARG="$PGDATABASE" + echo "Using PGDATABASE and standard PG env vars." +else + echo "Error: Neither DATABASE_URL nor PGDATABASE provided." + exit 1 +fi + +if [ -z "$S3_BUCKET" ]; then + echo "Error: S3_BUCKET is missing." + exit 1 +fi + +# Parse TARGET_TABLES (e.g. "users,orders") into flags ("-t users -t orders") +TABLE_FLAGS="" +if [ ! -z "$TARGET_TABLES" ]; then + for table in $(echo $TARGET_TABLES | tr "," "\n"); do + TABLE_FLAGS="${TABLE_FLAGS} -t ${table}" + done +fi + +echo "Starting backup to $BACKUP_FILE..." + +# pg_dump accepts a URI as the dbname argument +pg_dump "$CONNECTION_ARG" $TABLE_FLAGS -Fc -a -f "/tmp/$BACKUP_FILE" + +echo "Backup created. Uploading to $S3_PATH..." + +aws s3 cp "/tmp/$BACKUP_FILE" "$S3_PATH" + +rm "/tmp/$BACKUP_FILE" + +echo "Backup complete." \ No newline at end of file diff --git a/database-restore.sh b/database-restore.sh new file mode 100644 index 00000000..3675f8e5 --- /dev/null +++ b/database-restore.sh @@ -0,0 +1,57 @@ +#!/bin/bash +set -e + +INPUT_ARG="$1" + +if [ -z "$INPUT_ARG" ]; then + echo "Error: No input provided." + echo "Usage: ./restore.sh OR " + exit 1 +fi + +# Check connection vars +if [ -n "$DATABASE_URL" ]; then + CONNECTION_ARG="$DATABASE_URL" +elif [ -n "$PGDATABASE" ]; then + CONNECTION_ARG="$PGDATABASE" +else + echo "Error: Neither DATABASE_URL nor PGDATABASE provided." + exit 1 +fi + +# Determine if input is a local file or S3 key +if [ -f "$INPUT_ARG" ]; then + echo "Local file found: $INPUT_ARG" + RESTORE_FILE="$INPUT_ARG" + SHOULD_CLEANUP=false +else + # Assume S3 key + if [ -z "$S3_BUCKET" ]; then + echo "Error: Input file not found locally and S3_BUCKET env var is missing." + exit 1 + fi + + S3_FULL_PATH="s3://${S3_BUCKET}/${S3_PREFIX}${INPUT_ARG}" + RESTORE_FILE="/tmp/restore_temp.dump" + SHOULD_CLEANUP=true + + echo "Local file '$INPUT_ARG' not found. Attempting download from $S3_FULL_PATH..." + aws s3 cp "$S3_FULL_PATH" "$RESTORE_FILE" +fi + +echo "Restoring data to database..." + +# pg_restore flags: +# -a : Data only (no schema) +# -v : Verbose +# -1 : Single transaction +# -d : Connection string/dbname +# --disable-triggers : Disable triggers during data load (speeds up restore, avoids FK issues) +pg_restore -d "$CONNECTION_ARG" -a -v -1 --disable-triggers "$RESTORE_FILE" + +if [ "$SHOULD_CLEANUP" = true ]; then + echo "Cleaning up temp file..." + rm "$RESTORE_FILE" +fi + +echo "Restore complete." \ No newline at end of file diff --git a/docker-compose.base.yaml b/docker-compose.base.yaml index 09e86cd7..f88453ef 100644 --- a/docker-compose.base.yaml +++ b/docker-compose.base.yaml @@ -40,7 +40,19 @@ services: memory: 2048m limits: memory: 2048m - + + aftermath-backup-base: + image: ghcr.io/cufee/aftermath-backup:${ENVIRONMENT} + build: + context: . + dockerfile: Dockerfile.backup + deploy: + resources: + reservations: + memory: 2048m + limits: + memory: 2048m + aftermath-service-base: pull_policy: always image: ghcr.io/cufee/aftermath:${ENVIRONMENT} diff --git a/docker-compose.dokploy.yaml b/docker-compose.dokploy.yaml index 07515b94..5c0dbe42 100644 --- a/docker-compose.dokploy.yaml +++ b/docker-compose.dokploy.yaml @@ -36,6 +36,28 @@ services: dokploy-network: aliases: - migrate-${ENVIRONMENT} + database-backup: + hostname: database-backup-${ENVIRONMENT} + extends: + file: docker-compose.base.yaml + service: aftermath-backup-base + restart: never + environment: + # relations will not be scanned when making a backup + - TARGET_TABLES="account,clan,app_configuration,application_command,user,user_connection,user_subscription,user_content,user_restriction,moderation_request,widget_settings" + - DATABASE_URL="postgresql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}/${DATABASE_NAME}?sslmode=disable" + - S3_BUCKET=${DATABASE_BACKUP_S3_BUCKET} + - AWS_ACCESS_KEY_ID=${DATABASE_BACKUP_AWS_ACCESS_KEY_ID} + - AWS_SECRET_ACCESS_KEY=${DATABASE_BACKUP_AWS_SECRET_ACCESS_KEY} + depends_on: + migrate: + condition: service_completed_successfully + database: + condition: service_healthy + networks: + dokploy-network: + aliases: + - database-backup-${ENVIRONMENT} backend: extends: From 5f23033434460fe0fabec9f324b5f5c2c0a1e39b Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Nov 2025 08:40:06 -0600 Subject: [PATCH 05/17] fixed policy --- docker-compose.dokploy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.dokploy.yaml b/docker-compose.dokploy.yaml index 5c0dbe42..2417b1c0 100644 --- a/docker-compose.dokploy.yaml +++ b/docker-compose.dokploy.yaml @@ -41,7 +41,7 @@ services: extends: file: docker-compose.base.yaml service: aftermath-backup-base - restart: never + restart: no environment: # relations will not be scanned when making a backup - TARGET_TABLES="account,clan,app_configuration,application_command,user,user_connection,user_subscription,user_content,user_restriction,moderation_request,widget_settings" From 1b4dcaf8e185c5c8fdde4c14c4b8636625a772be Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Nov 2025 08:47:41 -0600 Subject: [PATCH 06/17] added endpoint urls --- database-backup.sh | 7 ++++++- database-restore.sh | 6 +++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/database-backup.sh b/database-backup.sh index 7c2e33f2..fd362c39 100644 --- a/database-backup.sh +++ b/database-backup.sh @@ -19,6 +19,11 @@ else exit 1 fi +if [ -z "$ENDPOINT_URL" ]; then + echo "Error: ENDPOINT_URL is missing." + exit 1 +fi + if [ -z "$S3_BUCKET" ]; then echo "Error: S3_BUCKET is missing." exit 1 @@ -39,7 +44,7 @@ pg_dump "$CONNECTION_ARG" $TABLE_FLAGS -Fc -a -f "/tmp/$BACKUP_FILE" echo "Backup created. Uploading to $S3_PATH..." -aws s3 cp "/tmp/$BACKUP_FILE" "$S3_PATH" +aws s3 cp "/tmp/$BACKUP_FILE" "$S3_PATH" --endpoint-url "$ENDPOINT_URL" rm "/tmp/$BACKUP_FILE" diff --git a/database-restore.sh b/database-restore.sh index 3675f8e5..7831d03b 100644 --- a/database-restore.sh +++ b/database-restore.sh @@ -30,13 +30,17 @@ else echo "Error: Input file not found locally and S3_BUCKET env var is missing." exit 1 fi + if [ -z "$ENDPOINT_URL" ]; then + echo "Error: ENDPOINT_URL is missing." + exit 1 + fi S3_FULL_PATH="s3://${S3_BUCKET}/${S3_PREFIX}${INPUT_ARG}" RESTORE_FILE="/tmp/restore_temp.dump" SHOULD_CLEANUP=true echo "Local file '$INPUT_ARG' not found. Attempting download from $S3_FULL_PATH..." - aws s3 cp "$S3_FULL_PATH" "$RESTORE_FILE" + aws s3 cp "$S3_FULL_PATH" "$RESTORE_FILE" --endpoint-url "$ENDPOINT_URL" fi echo "Restoring data to database..." From 61648e4f74cdcf80d0d42ebfa335f3f84aa7f357 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Nov 2025 08:48:16 -0600 Subject: [PATCH 07/17] added endpoint url --- docker-compose.dokploy.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.dokploy.yaml b/docker-compose.dokploy.yaml index 2417b1c0..29b5934b 100644 --- a/docker-compose.dokploy.yaml +++ b/docker-compose.dokploy.yaml @@ -47,6 +47,7 @@ services: - TARGET_TABLES="account,clan,app_configuration,application_command,user,user_connection,user_subscription,user_content,user_restriction,moderation_request,widget_settings" - DATABASE_URL="postgresql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}/${DATABASE_NAME}?sslmode=disable" - S3_BUCKET=${DATABASE_BACKUP_S3_BUCKET} + - ENDPOINT_URL=${DATABASE_BACKUP_ENDPOINT_URL} - AWS_ACCESS_KEY_ID=${DATABASE_BACKUP_AWS_ACCESS_KEY_ID} - AWS_SECRET_ACCESS_KEY=${DATABASE_BACKUP_AWS_SECRET_ACCESS_KEY} depends_on: From d7805c493ca602990167654132886de762d7f6bd Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Nov 2025 08:50:20 -0600 Subject: [PATCH 08/17] remove quotes --- docker-compose.dokploy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.dokploy.yaml b/docker-compose.dokploy.yaml index 29b5934b..4c98c6d8 100644 --- a/docker-compose.dokploy.yaml +++ b/docker-compose.dokploy.yaml @@ -45,7 +45,7 @@ services: environment: # relations will not be scanned when making a backup - TARGET_TABLES="account,clan,app_configuration,application_command,user,user_connection,user_subscription,user_content,user_restriction,moderation_request,widget_settings" - - DATABASE_URL="postgresql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}/${DATABASE_NAME}?sslmode=disable" + - DATABASE_URL=postgresql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}/${DATABASE_NAME}?sslmode=disable - S3_BUCKET=${DATABASE_BACKUP_S3_BUCKET} - ENDPOINT_URL=${DATABASE_BACKUP_ENDPOINT_URL} - AWS_ACCESS_KEY_ID=${DATABASE_BACKUP_AWS_ACCESS_KEY_ID} From 5c42983821a4a2859f00548eec6bd3e14ab26784 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Nov 2025 08:50:30 -0600 Subject: [PATCH 09/17] remove more quotes --- docker-compose.dokploy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.dokploy.yaml b/docker-compose.dokploy.yaml index 4c98c6d8..4f9ea53d 100644 --- a/docker-compose.dokploy.yaml +++ b/docker-compose.dokploy.yaml @@ -44,7 +44,7 @@ services: restart: no environment: # relations will not be scanned when making a backup - - TARGET_TABLES="account,clan,app_configuration,application_command,user,user_connection,user_subscription,user_content,user_restriction,moderation_request,widget_settings" + - TARGET_TABLES=account,clan,app_configuration,application_command,user,user_connection,user_subscription,user_content,user_restriction,moderation_request,widget_settings - DATABASE_URL=postgresql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}/${DATABASE_NAME}?sslmode=disable - S3_BUCKET=${DATABASE_BACKUP_S3_BUCKET} - ENDPOINT_URL=${DATABASE_BACKUP_ENDPOINT_URL} From 7f83f257182b187a88ee821d962f47d4e5b00cda Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Nov 2025 15:31:49 -0600 Subject: [PATCH 10/17] disable remote localization for build --- Dockerfile | 18 +- static/.gitignore | 2 +- static/localization/en/cta.yaml | 50 +++++ static/localization/en/discord.yaml | 241 +++++++++++++++++++++++++ static/localization/en/stats.yaml | 48 +++++ static/localization/pl/cta.yaml | 50 +++++ static/localization/pl/discord.yaml | 241 +++++++++++++++++++++++++ static/localization/pl/stats.yaml | 48 +++++ static/localization/pt-BR/cta.yaml | 50 +++++ static/localization/pt-BR/discord.yaml | 241 +++++++++++++++++++++++++ static/localization/pt-BR/stats.yaml | 48 +++++ 11 files changed, 1027 insertions(+), 10 deletions(-) create mode 100644 static/localization/en/cta.yaml create mode 100644 static/localization/en/discord.yaml create mode 100644 static/localization/en/stats.yaml create mode 100644 static/localization/pl/cta.yaml create mode 100644 static/localization/pl/discord.yaml create mode 100644 static/localization/pl/stats.yaml create mode 100644 static/localization/pt-BR/cta.yaml create mode 100644 static/localization/pt-BR/discord.yaml create mode 100644 static/localization/pt-BR/stats.yaml diff --git a/Dockerfile b/Dockerfile index 0edc4f83..a923ebd2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,16 @@ -# Download localizations -FROM node:23 AS builder-node +# # Download localizations +# FROM node:23 AS builder-node -ARG LOCALIZE_API_KEY -ENV LOCALIZE_API_KEY $LOCALIZE_API_KEY +# ARG LOCALIZE_API_KEY +# ENV LOCALIZE_API_KEY $LOCALIZE_API_KEY -WORKDIR /workspace +# WORKDIR /workspace -RUN npm install @tolgee/cli +# RUN npm install @tolgee/cli -COPY ./.tolgeerc ./ +# COPY ./.tolgeerc ./ -RUN npx tolgee pull --api-key "${LOCALIZE_API_KEY}" --states REVIEWED +# RUN npx tolgee pull --api-key "${LOCALIZE_API_KEY}" --states REVIEWED # Build app FROM golang:1.24.3 AS builder-go @@ -26,7 +26,7 @@ RUN --mount=type=cache,target=$GOPATH/pkg/mod go mod download COPY ./ ./ # load localizations -COPY --from=builder-node /workspace/static/localization/ ./static/localization/ +# COPY --from=builder-node /workspace/static/localization/ ./static/localization/ # generate static assets RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate ./internal/assets diff --git a/static/.gitignore b/static/.gitignore index 07bf85a9..bbdff85d 100644 --- a/static/.gitignore +++ b/static/.gitignore @@ -1 +1 @@ -localization +# localization diff --git a/static/localization/en/cta.yaml b/static/localization/en/cta.yaml new file mode 100644 index 00000000..8a6fd64b --- /dev/null +++ b/static/localization/en/cta.yaml @@ -0,0 +1,50 @@ +cta_abandoned_negative_growth_body: |- + It looks like you haven't used Aftermath in a while, but I can be really helpful for players looking to improve their stats. + + Here is your recent session, try using `/session` if you want to dive deeper into your stats! +cta_abandoned_negative_growth_head: "We miss you :cry:" +cta_abandoned_neutral_growth_body: |- + It looks like you haven't used Aftermath in a while. + + Check out your recent stats, you can also dive deeper using the `/session` command! +cta_abandoned_neutral_growth_head: "We miss you :cry:" +cta_abandoned_positive_growth_body: |- + It looks like you haven't used Aftermath in a while, but your growth is impressive! + + Here is your recent session, try using `/session` if you want to dive deeper into your stats. +cta_abandoned_positive_growth_head: "We miss you :cry:" +cta_command_help_body: |- + Looking for more information on all the awesome features? + + Check `/help` for an overview of every supported command! +cta_command_help_button: "Have a question?" +cta_command_help_head: "Learn about Aftermath!" +cta_command_links_add_body: |- + You can link your Blitz account to Aftermath to make checking stats even easier! + + Give it a try with `/links add` +cta_command_links_add_head: "Link your Blitz account!" +cta_command_links_verify_body: |- + You can verify your Blitz account using `/link verify`. + + This will make your custom background images visible to other users and unlock the `/my` command! +cta_command_links_verify_head: "Verify your Blitz account!" +cta_command_replay_body: |- + You can upload a replay using `/replay` to get an overview of your battle with stats for every player! + + Replays are always kept private, never stored, and never uploaded to any third party service - your strats are safe with us! +cta_command_replay_head: "Aftermath can also process your replays!" +cta_guild_install_body: |- + Just share this link with a server owner! + https://amth.one/install +cta_guild_install_button: "Share Aftermath" +cta_guild_install_head: "Want to add Aftermath to your favorite server?" +cta_personal_install_body: |- + Install Aftermath as a personal App and use it across all servers and Direct Message groups! + + Just select **Add to My Apps** when installing Aftermath +cta_personal_install_button: "Install Aftermath" +cta_personal_install_head: "You can use Aftermath across all of your servers!" +cta_report_issues_body: "You can report any errors and submit feature requests on the Aftermath Official Discord server!" +cta_report_issues_button: "Join Aftermath Official" +cta_report_issues_head: "Spotted an error or want to share some feedback?" diff --git a/static/localization/en/discord.yaml b/static/localization/en/discord.yaml new file mode 100644 index 00000000..e67754bd --- /dev/null +++ b/static/localization/en/discord.yaml @@ -0,0 +1,241 @@ +background_error_invalid_attachment_image: "The image you have provided is invalid. Please make sure it is a PNG or JPEG image." +background_error_invalid_attachment_vague: "The file attached is not a valid image." +background_error_missing_attachment: "You need to attach an image or provide a link in order to upload a custom background." +background_error_payment_required: |- + This feature of Aftermath is only available for users with an active subscription. + + You can subscribe by using the `/subscribe` command or pick a background using `/fancy` instead. +blitz_stars_error_service_down: "Some of your career stats rely on BlitzStars, which is currently unavailable. However, most other commands, including sessions, are still working." +buttons_add_aftermath_to_your_server: "Install Aftermath" +buttons_have_a_question_question: "Have a Question?" +buttons_join_primary_guild: "Join Aftermath Official" +buttons_need_help_question: "Need Help?" +buttons_submit: "Submit" +command_auction_description: "View the current Blitz Auction" +command_auction_name: "auction" +command_auction_result_fmt: "### Updated " +command_career_desc: "Get an overview of your career stats" +command_career_help_message: |- + ## View Career Stats for Your Account! + To get started, include a nickname and server, mention another user, or link an account using `/links add`! +command_career_name: "career" +command_fancy_description: "Manage your custom background images" +command_fancy_help_message: |- + ## Upload a Custom Background! + To get started, attach an image or provide a link to an image when sending the command! +command_fancy_name: "fancy" +command_help_description: "Get some helpful information about the bot" +command_help_name: "help" +command_links_buttons_manage: "Manage Accounts" +command_links_description: "Manage your linked Blitz accounts" +command_links_linked_successfully_fmt: |- + Your account has been linked! Aftermath will now default to **%s** on **%s** when checking stats. + You can also verify your account with `/links verify` +command_links_name: "links" +command_links_set_default_successfully_fmt: "Your default account has been updated! Aftermath will now default to **%s** on **%s** when checking stats." +command_links_unlinked_successfully: "Your linked account was removed." +command_links_verify_cta: "You can also verify your account with `/links verify`" +command_links_verify_response_fmt: |- + ## Here is your verification link! + ### You will need to log in using the Discord account you are currently using in order to link your Blitz account. + _This link is specific to %s. It does not contain any personal information._ + :link: %s +command_links_view_content_fmt: |- + ## Here is a list of your linked Blitz accounts! + %s +   + You can manage and verify them on our website! +command_my_career_description: "View career stats for your linked accounts" +command_my_career_name: "career" +command_my_description: "View stats for your linked accounts" +command_my_name: "my" +command_my_session_description: "View session stats for your linked account" +command_my_session_name: "session" +command_option_fancy_file_description: "A JPEG or PNG image file" +command_option_fancy_file_name: "file" +command_option_fancy_link_description: "A link to a JPEG or PNG image" +command_option_fancy_link_name: "link" +command_option_fancy_remove_desc: "Remove a custom background you have previously uploaded" +command_option_fancy_remove_name: "remove" +command_option_fancy_upload_desc: "Upload a new custom background for your stats" +command_option_fancy_upload_name: "upload" +command_option_links_add_desc: "Link a new Blitz account" +command_option_links_add_name: "add" +command_option_links_fav_desc: "Mark your linked account as default for Aftermath to check" +command_option_links_fav_name: "favorite" +command_option_links_remove_desc: "Remove your linked Blitz account" +command_option_links_remove_name: "remove" +command_option_links_verify_desc: "Link and verify your Blitz account to unlock more features!" +command_option_links_verify_name: "verify" +command_option_links_view_desc: "View your linked Blitz accounts" +command_option_links_view_name: "view" +command_option_my_account_description: "Select one of your linked account" +command_option_my_account_name: "account" +command_option_replay_file_description: "A Blitz replay file" +command_option_replay_file_name: "file" +command_option_replay_link_description: "A link to a Blitz replay file" +command_option_replay_link_name: "link" +command_option_widget_account_description: "Select one of your linked account" +command_option_widget_account_name: "account" +command_replay_help_message: |- + ## Analyze Your Replay and View Stats for All Players! + To get started, attach a replay file or provide a link to a replay file when sending the command! +command_session_description: "Get detailed stats for your recent session" +command_session_help_message: |- + ## View Session Stats for Your Account! + To get started, include a nickname and server, mention another user, or link an account using `/links add`! +command_session_name: "session" +commands_help_message_fmt: |- + ## :chart_with_upwards_trend: Track your progress + ### ○ /session + Get an image with your current session stats. You can also mention another user to view their session. + ### Aftermath sessions will be reset at the following times: + %s + ### ○ /career + Get an image with your career stats. You can also mention another user to view their stats. + ## :film_frames: Dive deeper into every battle + ### ○ /replay + Upload your replay and get an overview of the battle you have played. + ## :link: Tell Aftermath your Blitz nickname + ### ○ /links + You can set a default account by using the _/links favorite_ command to use _/session_ and _/career_ without providing a nickname and server. + ### ○ /links verify + Verify your Wargaming account to unlock additional features. + ## :frame_photo: Add a splash of style + ### ○ /fancy + Upload a custom background image for your session stats. Your unique style will be visible to everyone who views your stats. + ## :desktop: Show off on your stream + ### ○ /widget + Get a link to a streaming widget for your stream overlay. + This widget will automatically update with your latest session stats. + + _Found a translation error? Let us know on Aftermath Official_ + ## :heart: Share the love! +commands_help_refresh_times_fmt: |- + North America: () + Europe: () + Asia: () +commands_replay_description: "Analyze your replay and view stats for every single player" +commands_replay_name: "replay" +commands_subscribe_action_button: "Subscribe" +commands_widget_description: "Get a live streaming widget for your account" +commands_widget_message_fmt: |- + ## [Here is your widget link!](%s) + _Add it as a Browser Source in OBS_ [_(learn more)_](https://obsproject.com/kb/browser-source). +commands_widget_name: "widget" +common_error_command_expired_fmt: "This is a seasonal command that was disabled on and will be removed soon." +common_error_command_missing_permissions: "You don't have permission to use this command." +common_error_discord_outage: "It looks like Discord is having some temporary issues. Please try again in a few seconds." +common_error_missing_permissions_dm: "Aftermath is not able to send you a direct message." +common_error_missing_permissions_dm_mention_fmt: "Hey %s, Aftermath is not able to send you a direct message." +common_error_payment_required: |- + This feature of Aftermath is only available for users with an active subscription. + You can subscribe by using the `/subscribe` command. +common_error_service_outage: "Aftermath is currently undergoing maintenance and is temporarily unavailable. Please try again in a few moments." +common_error_unhandled_not_reported: |- + Something unexpected happened and your command failed. + *You can reach out to our team on Aftermath Official if you would like to report this error* +common_error_unhandled_reported: |- + Something unexpected happened and your command failed. + *This error was reported automatically, you can also reach out to our team on Aftermath Official* +common_error_user_restricted_vague_fmt: "### You are banned from using some or all features of Aftermath until " +common_error_user_spam_detected: "Hey there! It seems like you're using commands too quickly. To ensure a smooth experience for everyone, please refrain from spamming." +common_label_realm_as: "Asia" +common_label_realm_eu: "Europe" +common_label_realm_na: "North America" +common_label_realm_ru: "CIS" +common_label_tier_1: "Tier I" +common_label_tier_10: "Tier X" +common_label_tier_2: "Tier II" +common_label_tier_3: "Tier III" +common_label_tier_4: "Tier IV" +common_label_tier_5: "Tier V" +common_label_tier_6: "Tier VI" +common_label_tier_7: "Tier VII" +common_label_tier_8: "Tier VIII" +common_label_tier_9: "Tier IX" +common_option_stats_days_description: "How far back should the session go?" +common_option_stats_days_name: "days" +common_option_stats_nickname_description: "Blitz player name" +common_option_stats_nickname_name: "nickname" +common_option_stats_realm_description: "What server is this account on?" +common_option_stats_realm_name: "server" +common_option_stats_tank_description: "Filter vehicles by name" +common_option_stats_tank_tier_description: "Filter vehicles by tier" +common_option_stats_user_description: "Select another user to view their stats" +common_option_stats_user_name: "user" +discord_error_invalid_interaction_fmt: | + -# <@%s> used a command + **It looks like Discord is having some issues at the moment.** + Aftermath replies might be delayed, but we will try our best to make things work! +error_command_auction_still_refreshing: "Aftermath is still working on refreshing the current auction offers, please try again in a couple minutes." +errors_help_missing_dm_permissions_fmt: "Hey %s, Aftermath is not able to send you a direct message. Try using `/help` instead." +fancy_error_too_many_options_non_blocking: "When using this command, you only need to provide either a link or a file. The image file will override the link if both are provided." +fancy_errors_invalid_format: "The image you have provided is not in a JPEG or PNG format. This command does not support other file formats." +fancy_errors_invalid_image: |- + The image you have provided may be broken or is too large. + _The maximum recommended image size is 1000px x 1000px._ +fancy_errors_invalid_link: "The image you have provided is invalid, Aftermath was not able to view it." +fancy_errors_missing_attachment: "You need to provide a link or attach an image file." +fancy_errors_pending_request_exists: "You have a pending moderation request. Please wait until your previously submitted image is approved or rejected." +fancy_errors_preview_expired: "This preview has expired. Please create a new one to submit it for moderation." +fancy_errors_upload_timeout_fmt: |- + It looks like you have uploaded a custom background image recently. + You will be able to use this command again . +fancy_hint_image_transformation: "Images uploaded to Aftermath via this command will be scaled down and blurred. They will also require manual moderation." +fancy_moderation_request_approved: |- + ## :tada: **Your Custom Background is set!** :tada: + The image you have submitted was approved by a moderator and is now set as your stats background. Other users will only see this background if your default linked account is verified. +fancy_moderation_request_declined: |- + **Your recent image moderation request was declined** + It looks like the image you have uploaded did not pass out safety standards. You background will not be updated. +fancy_moderation_request_declined_and_banned_fmt: |- + **Your recent image moderation request was declined** + It looks like you have tried to upload an image that is against Aftermath Terms Of Service. As a result, your account will be restricted from uploading custom content until . This decision is final and cannot be appealed. +fancy_preview_msg: |- + ## Here is a preview of your image! + Use the buttons below to submit it for moderation. +fancy_remove_completed: "Your custom background image was removed." +fancy_remove_not_found: |- + ## You don't have a custom background image uploaded + To get started, use `/fancy upload`. +fancy_submitted_msg: |- + **Your request was submitted** + You will get a Direct Message when it is reviewed - no further action is required. + + _Please make sure your Direct Messages are open._ +links_error_connection_not_found: "Your linked account is no longer available. Try adding a new one with `/links add`." +links_error_connection_not_found_selected: "The account you have selected is no longer available" +links_error_no_account_selected: "It looks like you did not select an account from the options provided. Please pick one of the accounts shown as you type." +links_error_no_accounts_linked: "You don't have any linked accounts." +links_error_too_many_connections: |- + It looks like you have reached the limit for the number of Blitz accounts linked. + Try removing an existing account with `/links remove` before adding a new one. +my_error_no_account_linked: "It looks like you don't have a default Blitz account set. You can manage your accounts with `/links`." +nickname_autocomplete_invalid_input: "This does not look like a valid player name, make sure you are using your in-game name" +nickname_autocomplete_not_enough_length: "Continue typing to search for accounts..." +nickname_autocomplete_not_found: "Nothing found. Please select a nickname from the autocompletion list" +replay_error_too_many_options_non_blocking: "When using this command, you only need to provide either a link or a replay file. The replay file will override the link if both are provided." +replay_errors_all_attachments_invalid: "None of the files you have attached are valid WoT Blitz replays." +replay_errors_invalid_attachment: "The file you have attached is not a valid WoT Blitz replay." +replay_errors_missing_attachment: "You need to provide a link or attach a WoT Blitz replay file." +replay_errors_missing_permissions_vague: "Aftermath is missing some required permissions in this channel. Try using the `/replay` command instead." +replay_errors_some_attachments_invalid: "Some of the files you have attached are valid WoT Blitz replays and were skipped." +replay_errors_too_many_files: "Aftermath can process up to 5 replays at once. Please use the `/replay` command to upload any additional files." +session_error_account_was_not_tracked: "Aftermath just started tracking this account. Please play a few battles before using this command again." +session_error_no_session_for_period: "Aftermath does not yet have a session for this period. Try changing the number of days or using `/career` instead." +stats_account_not_found: "It looks like there are no Blitz accounts matching your search, please select an option from the list of accounts that is shown as you type in the nickname." +stats_autocomplete_not_enough_length: "Continue typing to search for available tanks..." +stats_autocomplete_not_found: "Nothing found. Please select a tank from the autocompletion list" +stats_bad_nickname_input_hint: "Wrong account? When searching for a Blitz account by nickname, try selecting a specific account from the results shown as you type" +stats_error_connection_not_found_personal: "Looks like you don't have a Blitz account linked yet. Give the `/links add` command a shot." +stats_error_connection_not_found_vague: "The user you mentioned does not have a Blitz account linked." +stats_error_connection_not_verified: "Looks like you haven't verified your Blitz account yet. Give the `/links verify` command a shot." +stats_error_mentioned_self_non_blocking: "You don't need to @mention yourself when checking stats, just type the command without any options to check your own account." +stats_error_nickname_invalid: "This does not look like a valid player name, make sure you are using your in-game name." +stats_error_too_many_arguments_non_blocking: "When using this command, you only need to mention a user or provide a nickname. The mentioned user will override the nickname if both are provided." +stats_multiple_accounts_found: "### It looks like there are multiple Blitz accounts matching your search, please select one using the buttons below." +stats_refresh_interaction_error_expired: "This refresh button has expired. Please use the command instead." +wargaming_error_outage: "It looks like Wargaming are having some temporary issues. Please try again in a few seconds." +wargaming_error_outage_short: "It looks like Wargaming are having some issues. Please try again" +wargaming_error_private_account: "This account is marked private by Wargaming and no stats are available for it at this time." diff --git a/static/localization/en/stats.yaml b/static/localization/en/stats.yaml new file mode 100644 index 00000000..e9dca1f6 --- /dev/null +++ b/static/localization/en/stats.yaml @@ -0,0 +1,48 @@ +game_mode_lunar: "Lunar" +game_mode_training: "Training Room" +game_mode_tutorial: "Tutorial" +game_mode_unknown: "Unknown" +label_accuracy: "Accuracy" +label_assisted: "Assisted" +label_assisted_combined: "Assisted+" +label_avg_damage: "Damage" +label_avg_tier: "Tier" +label_battle_type_regular: "Encounter" +label_battle_type_supremacy: "Supremacy" +label_battle_type_unknown: "Unknown" +label_battles: "Battles" +label_blocked: "Blocked" +label_damage_assisted: "Assisted" +label_damage_blocked: "Blocked" +label_damage_dealt: "Damage" +label_damage_ratio: "Dmg. Ratio" +label_damage_taken: "Damage Received" +label_defeat: "Defeat" +label_frags: "Kills" +label_game_mode_burning_games: "Burning Games" +label_game_mode_gravity: "Gravity" +label_game_mode_mad_games: "Mad Games" +label_game_mode_quick_tournament: "Quick Tournament" +label_game_mode_rating: "Rating Battle" +label_game_mode_realistic: "Realistic Battle" +label_game_mode_regular: "Regular Battle" +label_game_mode_skirmish: "Skirmish" +label_game_mode_tournament: "Tournament" +label_game_mode_training: "Training Room" +label_game_mode_unknown: "Unknown" +label_game_mode_uprising: "Uprising" +label_highlight_avg_damage: "Best Performance" +label_highlight_battles: "Favorite Tank" +label_highlight_recent_battle: "Last Battle" +label_highlight_wn8: "Biggest Impact" +label_map_name_unknown: "Unknown" +label_overview_rating: "Rating Battles" +label_overview_unrated: "Regular Battles" +label_ranked_rating: "Rating" +label_survival_percent: "Survived" +label_survival_ratio: "Survival" +label_victory: "Victory" +label_winrate: "Winrate" +label_wn8: "WN8" +tag_quick_tournament: "Quick Tournament" +tag_tournament: "Tournament" diff --git a/static/localization/pl/cta.yaml b/static/localization/pl/cta.yaml new file mode 100644 index 00000000..0a11f90f --- /dev/null +++ b/static/localization/pl/cta.yaml @@ -0,0 +1,50 @@ +cta_abandoned_negative_growth_body: |- + Wygląda na to, że nie używałeś Aftermath od jakiegoś czasu, ale mogę być naprawdę pomocny dla graczy, którzy chcą poprawić swoje statystyki. + + Oto twoja ostatnia sesja, spróbuj użyć `/session`, jeśli chcesz zagłębić się w swoje statystyki! +cta_abandoned_negative_growth_head: "Tęsknimy za Tobą :cry:" +cta_abandoned_neutral_growth_body: |- + Wygląda na to, że nie używałeś Aftermath od jakiegoś czasu. + + Sprawdź swoje ostatnie statystyki, możesz też zanurkować głębiej używając polecenia `/session`! +cta_abandoned_neutral_growth_head: "Tęsknimy za Tobą :cry:" +cta_abandoned_positive_growth_body: |- + Wygląda na to, że nie używałeś Aftermath od jakiegoś czasu, ale twój rozwój jest imponujący! + + Oto twoja ostatnia sesja, spróbuj użyć `/session`, jeśli chcesz zagłębić się w swoje statystyki. +cta_abandoned_positive_growth_head: "Tęsknimy za Tobą :cry:" +cta_command_help_body: |- + Szukasz więcej informacji o wszystkich niesamowitych funkcjach? + + Sprawdź `/help`, aby uzyskać przegląd wszystkich obsługiwanych poleceń! +cta_command_help_button: "Masz pytanie?" +cta_command_help_head: "Dowiedz się więcej o Aftermath!" +cta_command_links_add_body: |- + Możesz połączyć swoje konto Blitz z Aftermath, aby jeszcze bardziej ułatwić sprawdzanie statystyk! + + Wypróbuj z `/links add` +cta_command_links_add_head: "Połącz swoje konto Blitz!" +cta_command_links_verify_body: |- + Możesz zweryfikować swoje konto Blitz za pomocą `/link verify`. + + To sprawi, że Twoje niestandardowe obrazy tła będą widoczne dla innych użytkowników i odblokuje polecenie `/my`! +cta_command_links_verify_head: "Zweryfikuj swoje konto Blitz!" +cta_command_replay_body: |- + Możesz przesłać powtórkę za pomocą `/replay`, aby uzyskać przegląd swojej bitwy ze statystykami każdego gracza! + + Powtórki są zawsze prywatne, nigdy nie są przechowywane i nigdy nie są przesyłane do żadnej usługi zewnętrznej - Twoje strategie są u nas bezpieczne! +cta_command_replay_head: "Aftermath może również przetwarzać Twoje powtórki!" +cta_guild_install_body: |- + Po prostu udostępnij ten link właścicielowi serwera! + https://amth.one/install +cta_guild_install_button: "Udostępnij Aftermath" +cta_guild_install_head: "Chcesz dodać Aftermath do swojego ulubionego serwera?" +cta_personal_install_body: |- + Zainstaluj Aftermath jako osobistą aplikację i używaj jej na wszystkich serwerach i grupach Direct Message! + + Po prostu wybierz **Dodaj do moich aplikacji** podczas instalowania Aftermath +cta_personal_install_button: "Zainstaluj Aftermath" +cta_personal_install_head: "Możesz używać Aftermath na wszystkich swoich serwerach!" +cta_report_issues_body: "Możesz zgłaszać wszelkie błędy i przesyłać prośby o dodanie funkcji na oficjalnym serwerze Aftermath w serwisie Discord!" +cta_report_issues_button: "Dołącz do Aftermath Official" +cta_report_issues_head: "Znalazłeś błąd lub chcesz podzielić się swoją opinią?" diff --git a/static/localization/pl/discord.yaml b/static/localization/pl/discord.yaml new file mode 100644 index 00000000..86eab17e --- /dev/null +++ b/static/localization/pl/discord.yaml @@ -0,0 +1,241 @@ +background_error_invalid_attachment_image: "Podany obraz jest nieprawidłowy. Upewnij się, że jest to obraz PNG lub JPEG." +background_error_invalid_attachment_vague: "Załączony plik nie jest prawidłowym obrazem." +background_error_missing_attachment: "Aby przesłać niestandardowe tło, musisz dołączyć obraz lub podać link." +background_error_payment_required: |- + Ta funkcja Aftermath jest dostępna tylko dla użytkowników z aktywną subskrypcją. + + Możesz subskrybować, używając polecenia `/subscribe` lub wybrać tło, używając polecenia `/fancy`. +blitz_stars_error_service_down: "Niektóre statystyki twojej kariery opierają się na BlitzStars, który jest obecnie niedostępny. Jednak większość innych poleceń, w tym sesje, nadal działa." +buttons_add_aftermath_to_your_server: "Zainstaluj Aftermath" +buttons_have_a_question_question: "Masz pytanie?" +buttons_join_primary_guild: "Dołącz do Aftermath Official" +buttons_need_help_question: "Potrzebujesz pomocy?" +buttons_submit: "Wyślij" +command_auction_description: "Zobacz aktualną aukcję Blitz" +command_auction_name: "aukcja" +command_auction_result_fmt: "### Zaktualizowano " +command_career_desc: "Uzyskaj przegląd statystyk swojej kariery" +command_career_help_message: |- + ## Zobacz statystyki kariery dla swojego konta! + Aby rozpocząć, podaj pseudonim i serwer, wspomnij o innym użytkowniku lub połącz konto za pomocą `/links add`! +command_career_name: "kariera" +command_fancy_description: "Zarządzaj swoimi niestandardowymi obrazami tła" +command_fancy_help_message: |- + ## Prześlij niestandardowe tło! + Aby rozpocząć, dołącz obraz lub podaj link do obrazu podczas wysyłania polecenia! +command_fancy_name: "wymyślny" +command_help_description: "Uzyskaj przydatne informacje o bocie" +command_help_name: "pomoc" +command_links_buttons_manage: "Zarządzaj kontami" +command_links_description: "Zarządzaj swoimi połączonymi kontami Blitz" +command_links_linked_successfully_fmt: |- + Twoje konto zostało połączone! Aftermath będzie teraz domyślnie używać **%s** na **%s** podczas sprawdzania statystyk. + Możesz również zweryfikować swoje konto za pomocą `/links verify` +command_links_name: "linki" +command_links_set_default_successfully_fmt: "Twoje domyślne konto zostało zaktualizowane! Aftermath będzie teraz domyślnie **%s** na **%s** podczas sprawdzania statystyk." +command_links_unlinked_successfully: "Twoje powiązane konto zostało usunięte." +command_links_verify_cta: "Możesz również zweryfikować swoje konto za pomocą `/links verify`" +command_links_verify_response_fmt: |- + ## Oto Twój link weryfikacyjny! + ### Musisz zalogować się przy użyciu konta Discord, którego aktualnie używasz, aby połączyć swoje konto Blitz. + _Ten link jest specyficzny dla %s. Nie zawiera żadnych danych osobowych._ + :link: %s +command_links_view_content_fmt: |- + ## Oto lista Twoich powiązanych kont Blitz! + %s +   + Możesz nimi zarządzać i weryfikować je na naszej stronie internetowej! +command_my_career_description: "Wyświetl statystyki kariery dla powiązanych kont" +command_my_career_name: "kariera" +command_my_description: "Wyświetl statystyki dla połączonych kont" +command_my_name: "mój" +command_my_session_description: "Wyświetl statystyki sesji dla połączonego konta" +command_my_session_name: "sesja" +command_option_fancy_file_description: "Plik obrazu JPEG lub PNG" +command_option_fancy_file_name: "plik" +command_option_fancy_link_description: "Link do obrazu JPEG lub PNG" +command_option_fancy_link_name: "link" +command_option_fancy_remove_desc: "Usuń niestandardowe tło, które wcześniej przesłałeś" +command_option_fancy_remove_name: "usuń" +command_option_fancy_upload_desc: "Prześlij nowe, niestandardowe tło dla swoich statystyk" +command_option_fancy_upload_name: "wgraj" +command_option_links_add_desc: "Połącz nowe konto Blitz" +command_option_links_add_name: "dodaj" +command_option_links_fav_desc: "Oznacz swoje połączone konto jako domyślne, aby Aftermath je sprawdził" +command_option_links_fav_name: "ulubione" +command_option_links_remove_desc: "Usuń powiązane konto Blitz" +command_option_links_remove_name: "usuń" +command_option_links_verify_desc: "Połącz i zweryfikuj swoje konto Blitz, aby odblokować więcej funkcji!" +command_option_links_verify_name: "zweryfikuj" +command_option_links_view_desc: "Wyświetl powiązane konta Blitz" +command_option_links_view_name: "pogląd" +command_option_my_account_description: "Wybierz jedno ze swoich powiązanych kont" +command_option_my_account_name: "konto" +command_option_replay_file_description: "Plik powtórki Blitz" +command_option_replay_file_name: "plik" +command_option_replay_link_description: "Link do pliku powtórki Blitza" +command_option_replay_link_name: "link" +command_option_widget_account_description: "Wybierz jedno ze swoich powiązanych kont" +command_option_widget_account_name: "konto" +command_replay_help_message: |- + ## Przeanalizuj swoją powtórkę i zobacz statystyki wszystkich graczy! + Aby rozpocząć, dołącz plik powtórki lub podaj link do pliku powtórki podczas wysyłania polecenia! +command_session_description: "Uzyskaj szczegółowe statystyki dotyczące ostatniej sesji" +command_session_help_message: |- + ## Wyświetl statystyki sesji dla swojego konta! + Aby rozpocząć, podaj pseudonim i serwer, wspomnij o innym użytkowniku lub połącz konto za pomocą `/links add`! +command_session_name: "sesja" +commands_help_message_fmt: |- + ## :chart_with_upwards_trend: Śledź swoje postępy + ### ○ /session + Uzyskaj obraz ze statystykami swojej bieżącej sesji. Możesz również wspomnieć o innym użytkowniku, aby zobaczyć jego sesję. + ### Sesje Aftermath zostaną zresetowane w następujących momentach: + %s + ### ○ /career + Uzyskaj obraz ze statystykami swojej kariery. Możesz również wspomnieć o innym użytkowniku, aby zobaczyć jego statystyki. + ## :film_frames: Zanurz się głębiej w każdej bitwie + ### ○ /replay + Prześlij swoją powtórkę i zobacz przegląd rozegranej bitwy. + ## :link: Podaj Aftermath swój pseudonim Blitz + ### ○ /links + Możesz ustawić domyślne konto, używając polecenia _/links favorite_, aby używać _/session_ i _/career_ bez podawania pseudonimu i serwera. + ### ○ /links verify + Zweryfikuj swoje konto Wargaming, aby odblokować dodatkowe funkcje. + ## :frame_photo: Dodaj odrobinę stylu + ### ○ /fancy + Prześlij niestandardowy obraz tła dla statystyk sesji. Twój unikalny styl będzie widoczny dla wszystkich, którzy oglądają Twoje statystyki. + ## :desktop: Pokaż się na swoim streamie + ### ○ /widget + Uzyskaj link do widżetu streamingu dla nakładki streamu. + Ten widżet automatycznie zaktualizuje się o najnowsze statystyki sesji. + + _Znalazłeś błąd w tłumaczeniu? Daj nam znać na Aftermath Official_ + ## :heart: Podziel się miłością! +commands_help_refresh_times_fmt: |- + Ameryka Północna: () + Europa: () + Azja: () +commands_replay_description: "Przeanalizuj powtórkę i zobacz statystyki każdego gracza" +commands_replay_name: "powtórka" +commands_subscribe_action_button: "Subskrybuj" +commands_widget_description: "Uzyskaj widget transmisji strumieniowej na żywo dla swojego konta" +commands_widget_message_fmt: |- + ## [Oto link do Twojego widżetu!](%s) + _Dodaj go jako źródło przeglądarki w OBS_ [_(dowiedz się więcej)_](https://obsproject.com/kb/browser-source). +commands_widget_name: "widżet" +common_error_command_expired_fmt: "Jest to polecenie sezonowe, które zostało wyłączone w i wkrótce zostanie usunięte." +common_error_command_missing_permissions: "Nie masz uprawnień do użycia tego polecenia." +common_error_discord_outage: "Wygląda na to, że Discord ma chwilowe problemy. Spróbuj ponownie za kilka sekund." +common_error_missing_permissions_dm: "Aftermath nie jest w stanie wysłać Ci bezpośredniej wiadomości." +common_error_missing_permissions_dm_mention_fmt: "Hej %s, Aftermath nie jest w stanie wysłać Ci bezpośredniej wiadomości." +common_error_payment_required: |- + Ta funkcja Aftermath jest dostępna tylko dla użytkowników z aktywną subskrypcją. + Możesz subskrybować, używając polecenia `/subscribe`. +common_error_service_outage: "Aftermath jest obecnie w trakcie konserwacji i jest tymczasowo niedostępny. Spróbuj ponownie za chwilę." +common_error_unhandled_not_reported: |- + Wydarzyło się coś nieoczekiwanego i Twoje polecenie się nie powiodło. + *Możesz skontaktować się z naszym zespołem na Aftermath Official, jeśli chcesz zgłosić ten błąd* +common_error_unhandled_reported: |- + Wydarzyło się coś nieoczekiwanego i Twoje polecenie nie powiodło się. + *Ten błąd został zgłoszony automatycznie, możesz również skontaktować się z naszym zespołem na Aftermath Official* +common_error_user_restricted_vague_fmt: "### Do czasu nie możesz korzystać z niektórych lub wszystkich funkcji Aftermath" +common_error_user_spam_detected: "Hej! Wygląda na to, że używasz poleceń zbyt szybko. Aby zapewnić wszystkim płynne działanie, powstrzymaj się od spamowania." +common_label_realm_as: "Azja" +common_label_realm_eu: "Europa" +common_label_realm_na: "Ameryka Północna" +common_label_realm_ru: "WNP" +common_label_tier_1: "Poziom I" +common_label_tier_10: "Poziom X" +common_label_tier_2: "Poziom II" +common_label_tier_3: "Poziom III" +common_label_tier_4: "Poziom IV" +common_label_tier_5: "Poziom V" +common_label_tier_6: "Poziom VI" +common_label_tier_7: "Poziom VII" +common_label_tier_8: "Poziom VIII" +common_label_tier_9: "Poziom IX" +common_option_stats_days_description: "Jak daleko wstecz powinna sięgać sesja?" +common_option_stats_days_name: "dni" +common_option_stats_nickname_description: "Nazwa gracza Blitza" +common_option_stats_nickname_name: "przydomek" +common_option_stats_realm_description: "Na jakim serwerze znajduje się to konto?" +common_option_stats_realm_name: "serwer" +common_option_stats_tank_description: "Filtruj pojazdy według nazwy" +common_option_stats_tank_tier_description: "Filtruj pojazdy według poziomu" +common_option_stats_user_description: "Wybierz innego użytkownika, aby wyświetlić jego statystyki" +common_option_stats_user_name: "użytkownik" +discord_error_invalid_interaction_fmt: | + -# <@%s> użył polecenia + **Wygląda na to, że Discord ma obecnie pewne problemy.** + Odpowiedzi na Aftermatch mogą być opóźnione, ale zrobimy co w naszej mocy, żeby wszystko działało! +error_command_auction_still_refreshing: "Aftermath nadal pracuje nad odświeżeniem bieżących ofert aukcyjnych. Prosimy spróbować ponownie za kilka minut." +errors_help_missing_dm_permissions_fmt: "Hej %s, Aftermath nie jest w stanie wysłać Ci bezpośredniej wiadomości. Spróbuj zamiast tego użyć `/help`." +fancy_error_too_many_options_non_blocking: "Podczas korzystania z tego polecenia, musisz podać tylko link lub plik. Plik obrazu zastąpi link, jeśli podano oba." +fancy_errors_invalid_format: "Obraz, który podałeś, nie jest w formacie JPEG lub PNG. To polecenie nie obsługuje innych formatów plików." +fancy_errors_invalid_image: |- + Dostarczony obraz może być uszkodzony lub jest za duży. + _Maksymalny zalecany rozmiar obrazu to 1000px x 1000px._ +fancy_errors_invalid_link: "Dostarczony obraz jest nieprawidłowy. Aftermath nie mógł go wyświetlić." +fancy_errors_missing_attachment: "Musisz podać link lub załączyć plik graficzny." +fancy_errors_pending_request_exists: "Masz oczekującą prośbę o moderację. Poczekaj, aż Twój poprzednio przesłany obraz zostanie zatwierdzony lub odrzucony." +fancy_errors_preview_expired: "Ten podgląd wygasł. Utwórz nowy, aby przesłać go do moderacji." +fancy_errors_upload_timeout_fmt: |- + Wygląda na to, że niedawno przesłałeś niestandardowy obraz tła. + Będziesz mógł ponownie użyć tego polecenia . +fancy_hint_image_transformation: "Obrazy przesłane do Aftermath za pomocą tego polecenia zostaną zmniejszone i rozmyte. Będą również wymagały ręcznej moderacji." +fancy_moderation_request_approved: |- + ## :tada: **Twoje niestandardowe tło jest ustawione!** :tada: + Przesłany obraz został zatwierdzony przez moderatora i jest teraz ustawiony jako tło statystyk. Inni użytkownicy zobaczą to tło tylko wtedy, gdy Twoje domyślne połączone konto zostanie zweryfikowane. +fancy_moderation_request_declined: |- + **Twoja ostatnia prośba o moderację obrazu została odrzucona** + Wygląda na to, że przesłany przez Ciebie obraz nie spełnia standardów bezpieczeństwa. Twoje tło nie zostanie zaktualizowane. +fancy_moderation_request_declined_and_banned_fmt: |- + **Twoja ostatnia prośba o moderację obrazu została odrzucona** + Wygląda na to, że próbowałeś przesłać obraz, który jest niezgodny z Warunkami korzystania z usługi Aftermath. W rezultacie Twoje konto zostanie ograniczone do przesyłania niestandardowych treści do . Ta decyzja jest ostateczna i nie można się od niej odwołać. +fancy_preview_msg: |- + ## Oto podgląd Twojego obrazu! + Użyj poniższych przycisków, aby przesłać go do moderacji. +fancy_remove_completed: "Twój niestandardowy obraz tła został usunięty." +fancy_remove_not_found: |- + ## Nie przesłano niestandardowego obrazu tła + Aby rozpocząć, użyj `/fancy upload`. +fancy_submitted_msg: |- + **Twoje żądanie zostało wysłane** + Po jego rozpatrzeniu otrzymasz wiadomość bezpośrednią — nie jest wymagane żadne dalsze działanie. + + _Upewnij się, że Twoje wiadomości bezpośrednie są otwarte._ +links_error_connection_not_found: "Twoje połączone konto nie jest już dostępne. Spróbuj dodać nowe za pomocą `/links add`." +links_error_connection_not_found_selected: "Wybrane przez Ciebie konto nie jest już dostępne" +links_error_no_account_selected: "Wygląda na to, że nie wybrałeś konta z podanych opcji. Wybierz jedno z kont wyświetlanych podczas pisania." +links_error_no_accounts_linked: "Nie masz żadnych powiązanych kont." +links_error_too_many_connections: |- + Wygląda na to, że osiągnąłeś limit liczby połączonych kont Blitz. + Spróbuj usunąć istniejące konto za pomocą `/links remove` przed dodaniem nowego. +my_error_no_account_linked: "Wygląda na to, że nie masz ustawionego domyślnego konta Blitz. Możesz zarządzać swoimi kontami za pomocą `/links`." +nickname_autocomplete_invalid_input: "To nie wygląda na prawidłową nazwę gracza. Upewnij się, że używasz swojej nazwy w grze." +nickname_autocomplete_not_enough_length: "Kontynuuj wpisywanie, aby wyszukać konta..." +nickname_autocomplete_not_found: "Nic nie znaleziono. Wybierz pseudonim z listy autouzupełniania" +replay_error_too_many_options_non_blocking: "Podczas korzystania z tego polecenia, musisz podać tylko link lub plik powtórki. Plik powtórki zastąpi link, jeśli podano oba." +replay_errors_all_attachments_invalid: "Żaden z załączonych plików nie jest prawidłową powtórką WoT Blitz." +replay_errors_invalid_attachment: "Załączony plik nie jest prawidłową powtórką WoT Blitz." +replay_errors_missing_attachment: "Musisz podać link lub załączyć plik powtórki WoT Blitz." +replay_errors_missing_permissions_vague: "Aftermathowi brakuje niektórych wymaganych uprawnień w tym kanale. Spróbuj zamiast tego użyć polecenia `/replay`." +replay_errors_some_attachments_invalid: "Niektóre z załączonych plików to prawidłowe powtórki WoT Blitz, więc zostały pominięte." +replay_errors_too_many_files: "Aftermath może przetworzyć do 5 powtórek na raz. Proszę użyć polecenia `/replay`, aby przesłać dodatkowe pliki." +session_error_account_was_not_tracked: "Aftermath właśnie zaczął śledzić to konto. Proszę rozegrać kilka bitew przed ponownym użyciem tej komendy." +session_error_no_session_for_period: "Aftermath nie ma jeszcze sesji na ten okres. Spróbuj zmienić liczbę dni lub zamiast tego użyj `/career`." +stats_account_not_found: "Wygląda na to, że nie znaleziono żadnych kont Blitz odpowiadających Twojemu wyszukiwaniu. Wybierz opcję z listy kont, która zostanie wyświetlona po wpisaniu pseudonimu." +stats_autocomplete_not_enough_length: "Kontynuuj wpisywanie, aby wyszukać dostępne czołgi..." +stats_autocomplete_not_found: "Nic nie znaleziono. Wybierz zbiornik z listy autouzupełniania" +stats_bad_nickname_input_hint: "Złe konto? Podczas wyszukiwania konta Blitz według pseudonimu spróbuj wybrać konkretne konto z wyników wyświetlanych podczas pisania" +stats_error_connection_not_found_personal: "Wygląda na to, że nie masz jeszcze połączonego konta Blitz. Wypróbuj polecenie `/links add`." +stats_error_connection_not_found_vague: "Użytkownik, o którym wspomniałeś, nie ma połączonego konta Blitz." +stats_error_connection_not_verified: "Wygląda na to, że jeszcze nie zweryfikowałeś swojego konta Blitz. Wypróbuj polecenie `/links verify`." +stats_error_mentioned_self_non_blocking: "Nie musisz @wspominać o sobie przy sprawdzaniu statystyk, po prostu wpisz polecenie bez żadnych opcji, aby sprawdzić własne konto." +stats_error_nickname_invalid: "To nie wygląda na prawidłową nazwę gracza. Upewnij się, że używasz swojej nazwy z gry." +stats_error_too_many_arguments_non_blocking: "Podczas korzystania z tego polecenia wystarczy wspomnieć użytkownika lub podać pseudonim. Wspomniany użytkownik zastąpi pseudonim, jeśli podano oba." +stats_multiple_accounts_found: "### Wygląda na to, że istnieje wiele kont Blitz odpowiadających Twojemu wyszukiwaniu. Wybierz jedno, korzystając z przycisków poniżej." +stats_refresh_interaction_error_expired: "Ten przycisk odświeżania wygasł. Proszę użyć zamiast tego polecenia." +wargaming_error_outage: "Wygląda na to, że Wargaming ma chwilowe problemy. Spróbuj ponownie za kilka sekund." +wargaming_error_outage_short: "Wygląda na to, że Wargaming ma pewne problemy. Spróbuj ponownie" +wargaming_error_private_account: "To konto zostało oznaczone przez Wargaming jako prywatne i w chwili obecnej nie są dla niego dostępne żadne statystyki." diff --git a/static/localization/pl/stats.yaml b/static/localization/pl/stats.yaml new file mode 100644 index 00000000..c7ac58d7 --- /dev/null +++ b/static/localization/pl/stats.yaml @@ -0,0 +1,48 @@ +game_mode_lunar: "Lunar" +game_mode_training: "Pokój treningowy" +game_mode_tutorial: "Samouczek" +game_mode_unknown: "Nieznany" +label_accuracy: "Dokładność" +label_assisted: "Asysty" +label_assisted_combined: "Asysty+" +label_avg_damage: "Obrażenia" +label_avg_tier: "Poziom" +label_battle_type_regular: "Spotkanie" +label_battle_type_supremacy: "Dominacja" +label_battle_type_unknown: "Nieznany" +label_battles: "Bitwy" +label_blocked: "Zablok." +label_damage_assisted: "Asysty" +label_damage_blocked: "Zablok." +label_damage_dealt: "Obraż." +label_damage_ratio: "Stos. obrażeń" +label_damage_taken: "Otrzymane obraż." +label_defeat: "Porażka" +label_frags: "Kille" +label_game_mode_burning_games: "Burning Games" +label_game_mode_gravity: "Grawitacja" +label_game_mode_mad_games: "Mad Games" +label_game_mode_quick_tournament: "Szybki turniej" +label_game_mode_rating: "Bitwa Rankingowa" +label_game_mode_realistic: "Realistyczna bitwa" +label_game_mode_regular: "Regularna bitwa" +label_game_mode_skirmish: "Potyczka" +label_game_mode_tournament: "Turniej" +label_game_mode_training: "Pokój treningowy" +label_game_mode_unknown: "Nieznany" +label_game_mode_uprising: "Uprising" +label_highlight_avg_damage: "Najlepsza wydajność" +label_highlight_battles: "Ulubiony czołg" +label_highlight_recent_battle: "Ostatnia bitwa" +label_highlight_wn8: "Największy wpływ" +label_map_name_unknown: "Nieznany" +label_overview_rating: "Bitwy Rankingowe" +label_overview_unrated: "Regularne bitwy" +label_ranked_rating: "Ranking" +label_survival_percent: "Przetrwano" +label_survival_ratio: "Przetrwanie" +label_victory: "Zwycięstwo" +label_winrate: "% Wygr." +label_wn8: "WN8" +tag_quick_tournament: "Szybki turniej" +tag_tournament: "Turniej" diff --git a/static/localization/pt-BR/cta.yaml b/static/localization/pt-BR/cta.yaml new file mode 100644 index 00000000..19cc8c77 --- /dev/null +++ b/static/localization/pt-BR/cta.yaml @@ -0,0 +1,50 @@ +cta_abandoned_negative_growth_body: |- + Parece que você não usou o Aftermath já faz um tempo, mas eu posso ser muito útil para jogadores que querem melhorar suas estatísticas. + + Esta é a sua Sessão mais recente, tente usar `/session` se você deseja se aprofundar mais! +cta_abandoned_negative_growth_head: "Estamos com saudades :cry:" +cta_abandoned_neutral_growth_body: |- + Parece que você não usou o Aftermath já faz um tempo. + + Dê uma olhada nas suas estatísticas mais recentes, use `/session` se você desejar se aprofundar mais! +cta_abandoned_neutral_growth_head: "Estamos com saudades :cry:" +cta_abandoned_positive_growth_body: |- + Parece que você não usou o Aftermath já faz um tempo, mas você melhorou muito! + + Esta é a sua sessão mais recente, tente usar `/session` se você deseja se aprofundar mais! +cta_abandoned_positive_growth_head: "Estamos com saudades :cry:" +cta_command_help_body: |- + Quer informações sobre todos os recursos incríveis? + + Use `/help` para receber uma lista com os comandos disponíveis! +cta_command_help_button: "Dúvidas?" +cta_command_help_head: "Aprenda mais sobre o Aftermath!" +cta_command_links_add_body: |- + Você pode vincular sua conta do Blitz ao Aftermath para faciliar o uso de comandos! + + Tente usando o comandos `/links add`. +cta_command_links_add_head: "Vincule a sua conta Blitz!" +cta_command_links_verify_body: |- + Você pode verificar sua conta do Blitz usando `/link verify`. + + Isso fará com que outros usuários possam ver o seu fundo e desbloqueará o comando `/my`! +cta_command_links_verify_head: "Verifique sua conta Blitz!" +cta_command_replay_body: |- + Você pode enviar um replay usando `/replay` para obter uma visão geral da sua batalha com estatísticas para cada jogador! + + Os replays são sempre mantidos em sigilo, nunca armazenados e nunca enviamos para nenhum serviço de terceiros - suas estratégias estão seguras conosco! +cta_command_replay_head: "O Aftermath também pode verificar os seus replays!" +cta_guild_install_body: |- + Compartilhe este link com o dono do servidor! + https://amth.one/install +cta_guild_install_button: "Compartilhe o Aftermath" +cta_guild_install_head: "Quer adicionar o Aftermath ao seu servidor favorito?" +cta_personal_install_body: |- + Instale o Aftermath como um aplicativo pessoal e use-o em todos os servidores e grupos de conversa! + + Basta selecionar **Adicionar aos Meus Aplicativos** ao instalar o Aftermath. +cta_personal_install_button: "Instale o Aftermath" +cta_personal_install_head: "Você pode usar o Aftermath em todos os seus servidores!" +cta_report_issues_body: "Você pode reportar quaisquer erros e enviar solicitações de recursos no servidor oficial do Aftermath no Discord!" +cta_report_issues_button: "Junte-se ao Aftermath Oficial" +cta_report_issues_head: "Viu algum erro ou quer compartilhar algum feedback?" diff --git a/static/localization/pt-BR/discord.yaml b/static/localization/pt-BR/discord.yaml new file mode 100644 index 00000000..ecf580a8 --- /dev/null +++ b/static/localization/pt-BR/discord.yaml @@ -0,0 +1,241 @@ +background_error_invalid_attachment_image: "A imagem que você enviou não é valida. Tenha certeza de que o arquivo é uma imagem em formato PNG ou JPEG." +background_error_invalid_attachment_vague: "O arquivo anexado não é uma imagem valida." +background_error_missing_attachment: "Você deve anexar uma imagem ou um link para ter um fundo personalizado." +background_error_payment_required: |- + Esse recurso é exclusivo de assinantes. + + Você pode ter uma assinatura pelo comando `/subscribe` ou usar `/fancy` para escolher um fundo. +blitz_stars_error_service_down: "Algumas estatísticas da sua carreira dependem do BlitzStars, que no momento está indisponível. Entretanto, a maioria dos outros comandos, incluindo o de sessões, ainda funcionam." +buttons_add_aftermath_to_your_server: "Instale o Aftermath" +buttons_have_a_question_question: "Tem Dúvidas?" +buttons_join_primary_guild: "Entre no Aftermath Oficial" +buttons_need_help_question: "Precisa de Ajuda?" +buttons_submit: "Enviar" +command_auction_description: "Veja o Leilão atual no Blitz" +command_auction_name: "leilão" +command_auction_result_fmt: "### Atualizado " +command_career_desc: "Tenha um resumo das estatísticas da sua carreira" +command_career_help_message: |- + ## Veja as Estatísticas da sua Conta! + Para começar, coloque o seu nome e um servidor, mencione outro usuário ou vincule uma conta usando `/links add`! +command_career_name: "carreira" +command_fancy_description: "Veja suas imagens de fundo personalizado" +command_fancy_help_message: |- + ## Tenha um fundo personalizado! + Para começar, anexe uma imagem ou um link para uma imagem ao enviar o comando! +command_fancy_name: "chique" +command_help_description: "Veja informações úteis sobre o bot" +command_help_name: "ajuda" +command_links_buttons_manage: "Gerenciar Contas" +command_links_description: "Gerencie as suas contas Blitz vinculadas" +command_links_linked_successfully_fmt: |- + Sua conta foi vinculada! O Aftermath agora exibirá como padrão **%s** em **%s** ao verificar as estatísticas. + Você também pode verificar sua conta com "/links verify". +command_links_name: "links" +command_links_set_default_successfully_fmt: "Sua conta padrão foi atualizada! O Aftermath agora exibirá como padrão **%s** em **%s** ao verificar as estatísticas." +command_links_unlinked_successfully: "Sua conta vinculada foi removida." +command_links_verify_cta: "Você também pode verificar sua conta com \"/links verify\"" +command_links_verify_response_fmt: |- + ## Aqui está o seu link de verificação! + ### Você precisará fazer login com a conta do Discord que está usando atualmente para vincular sua conta do Blitz. + _Este link é específico para %s. Ele não contém nenhuma informação pessoal._ + :link: %s +command_links_view_content_fmt: |- + ## Aqui está uma lista das suas contas Blitz vinculadas! + %s +   + Você pode gerenciá-las e verificá-las em nosso site! +command_my_career_description: "Veja estatísticas de carreira das suas contas vinculadas" +command_my_career_name: "carreira" +command_my_description: "Ver estatísticas das suas contas vinculadas" +command_my_name: "minha" +command_my_session_description: "Ver estatísticas de sessão para sua conta vinculada" +command_my_session_name: "sessão" +command_option_fancy_file_description: "Um arquivo de imagem JPEG ou PNG" +command_option_fancy_file_name: "arquivo" +command_option_fancy_link_description: "Link para uma imagem JPEG ou PNG" +command_option_fancy_link_name: "link" +command_option_fancy_remove_desc: "Remova um plano de fundo personalizado que você enviou anteriormente" +command_option_fancy_remove_name: "remover" +command_option_fancy_upload_desc: "Envie um novo fundo personalizado para suas estatísticas" +command_option_fancy_upload_name: "enviar" +command_option_links_add_desc: "Vincular uma nova conta Blitz" +command_option_links_add_name: "adicionar" +command_option_links_fav_desc: "Marque sua conta vinculada como principal para o Aftermath verificar" +command_option_links_fav_name: "favorito" +command_option_links_remove_desc: "Remover sua conta Blitz vinculada" +command_option_links_remove_name: "remover" +command_option_links_verify_desc: "Vincule e verifique sua conta Blitz para desbloquear mais recursos!" +command_option_links_verify_name: "verificar" +command_option_links_view_desc: "Veja suas contas Blitz vinculadas" +command_option_links_view_name: "visualizar" +command_option_my_account_description: "Selecione uma das suas contas vinculadas" +command_option_my_account_name: "conta" +command_option_replay_file_description: "Um arquivo de replay do Blitz" +command_option_replay_file_name: "arquivo" +command_option_replay_link_description: "Um link para uma replay do Blitz" +command_option_replay_link_name: "link" +command_option_widget_account_description: "Selecione uma das suas contas vinculadas" +command_option_widget_account_name: "conta" +command_replay_help_message: |- + ## Analise seu replay e veja estatística de todos os jogadores! + Para começar, anexe um arquivo de replay ou forneça um link do replay ao enviar o comando! +command_session_description: "Obtenha estatísticas detalhadas da sua sessão recente" +command_session_help_message: |- + ## Veja as estatísticas da sessão da sua conta! + Para começar, inclua um nome e um servidor, mencione outro usuário ou vincule uma conta usando `/links add`! +command_session_name: "sessão" +commands_help_message_fmt: |- + ## :chart_with_upwards_trend: Acompanhe seu progresso + ### ○ /session + Obtenha uma imagem com as estatísticas da sua sessão atual. Você também pode mencionar outro usuário para visualizar a sessão dele. + ### As sessões do Aftermath serão reiniciadas nos seguintes horários: + %s + ### ○ /career + Obtenha uma imagem com as estatísticas da sua carreira. Você também pode mencionar outro usuário para visualizar as estatísticas dele. + ## :film_frames: Mergulhe mais fundo em cada batalha + ### ○ /replay + Envie seu replay e tenha uma visão geral da batalha que você jogou. + ## :link: Informe seu nome do Blitz para o Aftermath + ### ○ /links + Você pode definir uma conta padrão usando o comando _/links favorite_ para usar _/session_ e _/career_ sem fornecer um nome e servidor. + ### ○ /links verify + Verifique sua conta Wargaming para desbloquear recursos adicionais. + ## :frame_photo: Adicione um toque de estilo + ### ○ /fancy + Envie uma imagem de fundo personalizada para as estatísticas da sua sessão. Seu estilo único ficará visível para todos que visualizarem suas estatísticas. + ## :desktop: Se exiba na transmissão + ### ○ /widget + Obtenha um link para um widget de transmissão para o overlay da sua transmissão. + Este widget será atualizado automaticamente com as estatísticas mais recentes da sua sessão. + + _Encontrou um erro de tradução? Avise-nos no Aftermath Official_ + ## :heart: Compartilhe amor! +commands_help_refresh_times_fmt: |- + América do Norte: () + Europa: () + Ásia: () +commands_replay_description: "Analise o seu replay e veja as estatísticas de cada jogador" +commands_replay_name: "replay" +commands_subscribe_action_button: "Inscrever-se" +commands_widget_description: "Obtenha um widget de transmissão para sua conta" +commands_widget_message_fmt: |- + ## [Aqui está o link do seu widget!](%s) + _Adicione-o como uma fonte do navegador no OBS_ [_(saiba mais)_](https://obsproject.com/kb/browser-source). +commands_widget_name: "widget" +common_error_command_expired_fmt: "Este é um comando sazonal que foi desabilitado em e será removido em breve." +common_error_command_missing_permissions: "Você não tem permissão para usar este comando." +common_error_discord_outage: "Parece que o Discord está com problemas temporários. Tente novamente em alguns segundos." +common_error_missing_permissions_dm: "O Aftermath não pode te enviar uma mensagem direta." +common_error_missing_permissions_dm_mention_fmt: "Olá %s, o Aftermath não pode lhe enviar uma mensagem direta." +common_error_payment_required: |- + Este recurso é exclusivo de assinantes. + Você pode ter uma assinatura pelo comando `/subscribe`. +common_error_service_outage: "O Aftermath está em manutenção e temporariamente indisponível. Tente novamente em alguns instantes." +common_error_unhandled_not_reported: |- + Algo inesperado aconteceu e seu comando falhou. + *Você pode entrar em contato com nossa equipe no Aftermath Oficial se desejar reportar este erro* +common_error_unhandled_reported: |- + Algo inesperado aconteceu e seu comando falhou. + *Este erro foi reportado automaticamente. Você também pode entrar em contato com nossa equipe no Aftermath Oficial* +common_error_user_restricted_vague_fmt: "### Você está proibido de usar alguns ou todos os recursos do Aftermath até " +common_error_user_spam_detected: "Ei! Parece que você está usando os comandos muito rápido. Para garantir uma boa experiência para todos, evite spam." +common_label_realm_as: "Ásia" +common_label_realm_eu: "Europa" +common_label_realm_na: "América do Norte" +common_label_realm_ru: "CEI" +common_label_tier_1: "Nível I" +common_label_tier_10: "Nível X" +common_label_tier_2: "Nível II" +common_label_tier_3: "Nível III" +common_label_tier_4: "Nível IV" +common_label_tier_5: "Nível V" +common_label_tier_6: "Nível VI" +common_label_tier_7: "Nível VII" +common_label_tier_8: "Nível VIII" +common_label_tier_9: "Nível IX" +common_option_stats_days_description: "Até quando a sessão deve ir?" +common_option_stats_days_name: "dias" +common_option_stats_nickname_description: "Nome do jogador Blitz" +common_option_stats_nickname_name: "nome" +common_option_stats_realm_description: "Em qual servidor esta conta está?" +common_option_stats_realm_name: "servidor" +common_option_stats_tank_description: "Filtrar veículos por nome" +common_option_stats_tank_tier_description: "Filtrar veículos por nível" +common_option_stats_user_description: "Selecione outro usuário para visualizar suas estatísticas" +common_option_stats_user_name: "usuário" +discord_error_invalid_interaction_fmt: | + -# <@%s> usou um comando + **Parece que o Discord está com alguns problemas no momento.** + As respostas do Aftermath podem demorar, mas faremos o possível para que tudo funcione! +error_command_auction_still_refreshing: "O Aftermath ainda está tentando atualizar as ofertas atuais do leilão. Tente novamente em alguns minutos." +errors_help_missing_dm_permissions_fmt: "Olá %s, o Aftermath não conseguiu enviar uma mensagem direta para você. Tente usar `/help`." +fancy_error_too_many_options_non_blocking: "Ao usar este comando, você só precisa fornecer uma imagem ou um link. A imagem terá prioridade se ambos forem fornecidos." +fancy_errors_invalid_format: "A imagem que você forneceu não está no formato JPEG ou PNG. Este comando não suporta outros tipos de formatos." +fancy_errors_invalid_image: |- + A imagem que você forneceu pode estar corrompida ou ser muito grande. + _O tamanho máximo de imagem recomendado é 1000px por 1000px._ +fancy_errors_invalid_link: "A imagem que você forneceu é inválida, o Aftermath não conseguiu visualizá-la." +fancy_errors_missing_attachment: "Você precisa fornecer um link ou anexar um arquivo de imagem." +fancy_errors_pending_request_exists: "Você tem uma solicitação de moderação pendente. Aguarde até que a imagem enviada anteriormente seja aprovada ou rejeitada." +fancy_errors_preview_expired: "Esta prévia expirou. Crie uma nova para envia-la à moderação." +fancy_errors_upload_timeout_fmt: |- + Parece que você enviou uma imagem de fundo personalizada recentemente. + Você poderá usar este comando novamente . +fancy_hint_image_transformation: "As imagens enviadas para o Aftermath por meio deste comando serão reduzidas e desfocadas. Elas também precisarão de moderação manual." +fancy_moderation_request_approved: |- + ## :tada: **Seu plano de fundo personalizado foi definido!** :tada: + A imagem que você enviou foi aprovada por um moderador e agora está definida como seu plano de fundo de estatísticas. Outros usuários só verão este plano de fundo se sua conta vinculada padrão for verificada. +fancy_moderation_request_declined: |- + **Sua solicitação recente de moderação de imagem foi recusada** + Parece que a imagem que você enviou não atendeu aos padrões de segurança. Seu plano de fundo não será atualizado. +fancy_moderation_request_declined_and_banned_fmt: |- + **Sua solicitação recente de moderação de imagem foi recusada** + Parece que você tentou enviar uma imagem que viola os Termos de Serviço do Aftermath. Como resultado, sua conta ficará suspensa de enviar conteúdo personalizado até . Esta decisão é final e não pode ser apelada. +fancy_preview_msg: |- + ## Aqui está uma prévia da sua imagem! + Use os botões abaixo para envia-la à moderação. +fancy_remove_completed: "Sua imagem de fundo personalizada foi removida." +fancy_remove_not_found: |- + ## Você não possui uma imagem de fundo personalizada enviada + Para começar, use `/fancy upload`. +fancy_submitted_msg: |- + **Sua solicitação foi enviada** + Você receberá uma Mensagem Direta quando ela for analisada - nenhuma outra ação é necessária. + + _Certifique-se de que suas Mensagens Diretas estejam abertas._ +links_error_connection_not_found: "Não consegui encontrar uma conta vinculada com este nome. Tente adicionar uma com `/links add`." +links_error_connection_not_found_selected: "Não consegui acessar a conta que você selecionou." +links_error_no_account_selected: "Parece que você não selecionou uma conta entre as opções fornecidas. Escolha uma das contas exibidas conforme você digita." +links_error_no_accounts_linked: "Você não tem nenhuma conta vinculada." +links_error_too_many_connections: |- + Parece que você atingiu o limite de contas vinculadas do Blitz. + Tente remover uma conta existente com `/links remove` antes de adicionar uma nova. +my_error_no_account_linked: "Parece que você não tem uma conta padrão do Blitz definida. Você pode gerenciar suas contas com `/links`." +nickname_autocomplete_invalid_input: "Este não parece ser um nome de jogador válido, certifique-se de que está usando seu nome no jogo" +nickname_autocomplete_not_enough_length: "Continue digitando para procurar contas..." +nickname_autocomplete_not_found: "Nada encontrado. Selecione um nome na lista de preenchimento automático." +replay_error_too_many_options_non_blocking: "Ao usar este comando, você só precisa fornecer um replay ou um link. O replay terá prioridade se ambos forem fornecidos." +replay_errors_all_attachments_invalid: "Nenhum dos arquivos que você anexou são replays válidos do WoT Blitz." +replay_errors_invalid_attachment: "O arquivo que você anexou não é um replay válido do WoT Blitz." +replay_errors_missing_attachment: "Você precisa fornecer um link ou anexar um arquivo de replay do WoT Blitz." +replay_errors_missing_permissions_vague: "O Aftermath não possui algumas permissões necessárias neste canal. Tente usar o comando `/replay`." +replay_errors_some_attachments_invalid: "Alguns dos arquivos que você anexou são replays válidos do WoT Blitz e foram pulados." +replay_errors_too_many_files: "O Aftermath pode processar até 5 replays simultaneamente. Use o comando `/replay` para enviar arquivos adicionais." +session_error_account_was_not_tracked: "O Aftermath começou a rastrear esta conta. Jogue algumas batalhas antes de usar este comando novamente." +session_error_no_session_for_period: "O Aftermath ainda não possui uma sessão para este período. Tente alterar o número de dias ou use `/career`." +stats_account_not_found: "Parece que não há contas do Blitz que correspondam à sua pesquisa. Selecione uma opção na lista de contas que é exibida conforme você digita o nome." +stats_autocomplete_not_enough_length: "Continue digitando para procurar por tanques disponíveis..." +stats_autocomplete_not_found: "Nada encontrado. Selecione um tanque na lista de preenchimento automático." +stats_bad_nickname_input_hint: "Conta errada? Ao procurar uma conta do Blitz pelo nome, tente selecionar uma conta específica nos resultados exibidos conforme você digita." +stats_error_connection_not_found_personal: "Parece que você ainda não tem uma conta Blitz vinculada. Experimente o comando `/links add`." +stats_error_connection_not_found_vague: "O usuário que você mencionou não tem uma conta Blitz vinculada." +stats_error_connection_not_verified: "Parece que você ainda não verificou sua conta do Blitz. Experimente o comando `/links verify`." +stats_error_mentioned_self_non_blocking: "Você não precisa @mencionar a si mesmo ao verificar as estatísticas, basta digitar o comando sem nenhuma opção para verificar sua própria conta." +stats_error_nickname_invalid: "Este não parece ser um nome de jogador válido. Certifique-se de que você está usando seu nome no jogo." +stats_error_too_many_arguments_non_blocking: "Ao usar este comando, você só precisa mencionar um usuário ou fornecer um nome. O nome terá prioridade se ambos forem fornecidos." +stats_multiple_accounts_found: "### Parece que há várias contas do Blitz que correspondem à sua pesquisa. Selecione uma usando os botões abaixo." +stats_refresh_interaction_error_expired: "Este botão de atualização expirou. Em vez disso, use o comando." +wargaming_error_outage: "Parece que a Wargaming está com problemas temporários. Tente novamente em alguns segundos." +wargaming_error_outage_short: "Parece que a Wargaming está com problemas. Tente novamente." +wargaming_error_private_account: "Esta conta foi marcada como privada pela Wargaming e não há estatísticas disponíveis para ela no momento." diff --git a/static/localization/pt-BR/stats.yaml b/static/localization/pt-BR/stats.yaml new file mode 100644 index 00000000..5581cc28 --- /dev/null +++ b/static/localization/pt-BR/stats.yaml @@ -0,0 +1,48 @@ +game_mode_lunar: "Lunar" +game_mode_training: "Sala de Treinamento" +game_mode_tutorial: "Tutorial" +game_mode_unknown: "Desconhecido" +label_accuracy: "Precisão" +label_assisted: "Auxiliado" +label_assisted_combined: "Auxiliado+" +label_avg_damage: "Dano" +label_avg_tier: "Nível" +label_battle_type_regular: "Encontro" +label_battle_type_supremacy: "Supremacia" +label_battle_type_unknown: "Desconhecido" +label_battles: "Batalhas" +label_blocked: "Bloqueado" +label_damage_assisted: "Auxiliado" +label_damage_blocked: "Bloqueado" +label_damage_dealt: "Dano" +label_damage_ratio: "Dano Médio" +label_damage_taken: "Dano Recebido" +label_defeat: "Derrota" +label_frags: "Destruído" +label_game_mode_burning_games: "Burning Games" +label_game_mode_gravity: "Gravidade" +label_game_mode_mad_games: "Mad Games" +label_game_mode_quick_tournament: "Torneio Rápido" +label_game_mode_rating: "Classificatória" +label_game_mode_realistic: "Batalha Realista" +label_game_mode_regular: "Regular" +label_game_mode_skirmish: "Skirmish" +label_game_mode_tournament: "Torneio" +label_game_mode_training: "Sala de Treinamento" +label_game_mode_unknown: "Desconhecido" +label_game_mode_uprising: "Renascimento" +label_highlight_avg_damage: "Melhor Desempenho" +label_highlight_battles: "Tanque Favorito" +label_highlight_recent_battle: "Última Batalha" +label_highlight_wn8: "Mais Impactamte" +label_map_name_unknown: "Desconhecido" +label_overview_rating: "Classificatória" +label_overview_unrated: "Regular" +label_ranked_rating: "Classificação" +label_survival_percent: "Sobreviveu" +label_survival_ratio: "Sobrevivência" +label_victory: "Vitória" +label_winrate: "Taxa de Vitórias" +label_wn8: "WN8" +tag_quick_tournament: "Torneio Rápido" +tag_tournament: "Torneio" From c4f6174ad7b6865c8d486bb80f2e35630b6cfcc2 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Nov 2025 19:22:39 -0600 Subject: [PATCH 11/17] added ofelia schedule --- docker-compose.dokploy.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docker-compose.dokploy.yaml b/docker-compose.dokploy.yaml index 4f9ea53d..6ace3838 100644 --- a/docker-compose.dokploy.yaml +++ b/docker-compose.dokploy.yaml @@ -41,7 +41,12 @@ services: extends: file: docker-compose.base.yaml service: aftermath-backup-base - restart: no + restart: always + command: sleep infinity + labels: + ofelia.enabled: "true" + ofelia.job-exec.backup-run.schedule: "@daily" + ofelia.job-exec.backup-run.command: "/backup.sh" environment: # relations will not be scanned when making a backup - TARGET_TABLES=account,clan,app_configuration,application_command,user,user_connection,user_subscription,user_content,user_restriction,moderation_request,widget_settings From 3fd6139516d234ad8fad9e6941976d0d9892088c Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Nov 2025 19:23:56 -0600 Subject: [PATCH 12/17] updated entrypoint --- docker-compose.dokploy.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.dokploy.yaml b/docker-compose.dokploy.yaml index 6ace3838..0e786b48 100644 --- a/docker-compose.dokploy.yaml +++ b/docker-compose.dokploy.yaml @@ -42,7 +42,7 @@ services: file: docker-compose.base.yaml service: aftermath-backup-base restart: always - command: sleep infinity + entrypoint: ["/bin/sh", "-c", "sleep infinity"] labels: ofelia.enabled: "true" ofelia.job-exec.backup-run.schedule: "@daily" From 6e77c2b2d0822b8a28fde95c5016bd29d534f979 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Nov 2025 19:39:08 -0600 Subject: [PATCH 13/17] update to use database url --- docker-compose.base.yaml | 2 +- docker-compose.dokploy.yaml | 8 ++++---- internal/constants/database.go | 4 +--- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/docker-compose.base.yaml b/docker-compose.base.yaml index f88453ef..9dc2d98a 100644 --- a/docker-compose.base.yaml +++ b/docker-compose.base.yaml @@ -31,7 +31,7 @@ services: build: context: . dockerfile: Dockerfile.migrate - command: migrate apply --dir "file:///migrations" --tx-mode all --url "postgresql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}/${DATABASE_NAME}?sslmode=disable" + command: migrate apply --dir "file:///migrations" --tx-mode all --url "${DATABASE_URL}" volumes: - /var/run/docker.sock:/var/run/docker.sock deploy: diff --git a/docker-compose.dokploy.yaml b/docker-compose.dokploy.yaml index 0e786b48..3575e4b7 100644 --- a/docker-compose.dokploy.yaml +++ b/docker-compose.dokploy.yaml @@ -6,7 +6,7 @@ services: restart: no environment: - COLLECTOR_BACKEND_URL=backend-${ENVIRONMENT}:${PRIVATE_SERVER_PORT} - - DATABASE_HOST=database-${ENVIRONMENT}:5432 + - DATABASE_URL=${DATABASE_URL} networks: dokploy-network: aliases: @@ -28,7 +28,7 @@ services: file: docker-compose.base.yaml service: aftermath-migrate-base environment: - - DATABASE_HOST=database-${ENVIRONMENT}:5432 + - DATABASE_URL=${DATABASE_URL} depends_on: database: condition: service_healthy @@ -50,7 +50,7 @@ services: environment: # relations will not be scanned when making a backup - TARGET_TABLES=account,clan,app_configuration,application_command,user,user_connection,user_subscription,user_content,user_restriction,moderation_request,widget_settings - - DATABASE_URL=postgresql://${DATABASE_USER}:${DATABASE_PASSWORD}@${DATABASE_HOST}/${DATABASE_NAME}?sslmode=disable + - DATABASE_URL=${DATABASE_URL} - S3_BUCKET=${DATABASE_BACKUP_S3_BUCKET} - ENDPOINT_URL=${DATABASE_BACKUP_ENDPOINT_URL} - AWS_ACCESS_KEY_ID=${DATABASE_BACKUP_AWS_ACCESS_KEY_ID} @@ -73,7 +73,7 @@ services: environment: # the rest is imported from .env, which is going to be created by Dokploy automatically - PORT=3000 # the port does not matter, but it needs to match Traefik labels. we set it here explicitly in order to avoid any issues - - DATABASE_HOST=database-${ENVIRONMENT}:5432 + - DATABASE_URL=${DATABASE_URL} depends_on: migrate: condition: service_completed_successfully diff --git a/internal/constants/database.go b/internal/constants/database.go index d7e999de..4a8da651 100644 --- a/internal/constants/database.go +++ b/internal/constants/database.go @@ -1,7 +1,5 @@ package constants -import "fmt" - var ( - DatabaseConnString = fmt.Sprintf("postgresql://%s:%s@%s/%s?sslmode=disable", MustGetEnv("DATABASE_USER"), MustGetEnv("DATABASE_PASSWORD"), MustGetEnv("DATABASE_HOST"), MustGetEnv("DATABASE_NAME")) + DatabaseConnString = MustGetEnv("DATABASE_URL") ) From 3b90ec84a0d6035bc5032d5ca6bdf146b35a6342 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Nov 2025 19:57:27 -0600 Subject: [PATCH 14/17] fixed incorrect tx use --- cmd/core/scheduler/workers.go | 12 +++------- cmd/core/server/handlers/private/accounts.go | 19 +--------------- cmd/frontend/handler/context.go | 2 +- internal/database/accounts.go | 9 ++++---- internal/database/accounts_test.go | 23 ++++---------------- internal/database/averages.go | 12 +++++----- internal/database/averages_test.go | 5 +---- internal/database/cleanup_test.go | 2 +- internal/database/client.go | 8 +++---- internal/database/game_modes.go | 12 +++++----- internal/database/snapshots_test.go | 4 ++-- internal/database/vehicles.go | 12 +++++----- internal/logic/snapshots.go | 9 ++------ internal/stats/fetch/v1/multisource.go | 12 +++------- tests/static_database.go | 16 +++++++------- 15 files changed, 49 insertions(+), 108 deletions(-) diff --git a/cmd/core/scheduler/workers.go b/cmd/core/scheduler/workers.go index 3c05b08e..5ab39b6c 100644 --- a/cmd/core/scheduler/workers.go +++ b/cmd/core/scheduler/workers.go @@ -89,18 +89,12 @@ func UpdateAveragesWorker(client core.Client) func() { return } - aErr, err := client.Database().UpsertVehicleAverages(ctx, averages) + err = client.Database().UpsertVehicleAverages(ctx, averages) if err != nil { log.Err(err).Msg("failed to update averages cache") return } - for id, err := range aErr { - if err != nil { - log.Err(err).Str("", id).Msg("failed to update some average cache") - } - } - log.Info().Msg("averages cache updated") } } @@ -148,7 +142,7 @@ func UpdateGlossaryWorker(client core.Client) func() { vctx, cancel := context.WithTimeout(context.Background(), time.Second*15) defer cancel() - _, err = client.Database().UpsertVehicles(vctx, vehicles) + err = client.Database().UpsertVehicles(vctx, vehicles) if err != nil { log.Err(err).Msg("failed to save vehicle glossary") return @@ -208,7 +202,7 @@ func UpdateGlossaryWorker(client core.Client) func() { gmctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - _, err = client.Database().UpsertGameModes(gmctx, withTags) + err = client.Database().UpsertGameModes(gmctx, withTags) if err != nil { log.Err(err).Msg("failed save game modes glossary") return diff --git a/cmd/core/server/handlers/private/accounts.go b/cmd/core/server/handlers/private/accounts.go index a8805b28..16008f6b 100644 --- a/cmd/core/server/handlers/private/accounts.go +++ b/cmd/core/server/handlers/private/accounts.go @@ -62,9 +62,6 @@ func LoadAccountsHandler(client core.Client) http.HandlerFunc { batchSize := 50 var wg sync.WaitGroup sem := semaphore.NewWeighted(5) - errors := make(map[string]error) - var errorsMx sync.Mutex - for realm, accounts := range accountsByRealm { for i := 0; i < len(accounts); i += batchSize { end := i + batchSize @@ -113,29 +110,15 @@ func LoadAccountsHandler(client core.Client) http.HandlerFunc { inserts = append(inserts, &update) } - accErr, err := client.Database().UpsertAccounts(ctx, inserts...) + err = client.Database().UpsertAccounts(ctx, inserts...) if err != nil { log.Err(err).Msg("failed to upsert accounts") } - if len(accErr) > 0 { - errorsMx.Lock() - for id, err := range accErr { - errors[id] = err - } - errorsMx.Unlock() - } - }(accounts[i:end], realm) } } wg.Wait() - for id, err := range errors { - if err != nil { - log.Err(err).Str("id", id).Msg("some account imports failed") - } - } - log.Debug().Int("count", len(accounts)-len(existing)).Msg("finished importing accounts") }(accounts, existing) } diff --git a/cmd/frontend/handler/context.go b/cmd/frontend/handler/context.go index 42fe6360..02d3b27e 100644 --- a/cmd/frontend/handler/context.go +++ b/cmd/frontend/handler/context.go @@ -304,7 +304,7 @@ func (ws WebSocket) Serve(ctx *Context) error { conn, err := u.Upgrade(ctx.w, ctx.r, nil) if err != nil { - return ctx.String(err.Error()) + return ctx.String("%v", err) } return handler(conn) } diff --git a/internal/database/accounts.go b/internal/database/accounts.go index d3e9aa2f..2e7dd1dd 100644 --- a/internal/database/accounts.go +++ b/internal/database/accounts.go @@ -73,13 +73,12 @@ func (c *client) GetAccounts(ctx context.Context, ids []string) ([]models.Accoun return accounts, nil } -func (c *client) UpsertAccounts(ctx context.Context, accounts ...*models.Account) (map[string]error, error) { +func (c *client) UpsertAccounts(ctx context.Context, accounts ...*models.Account) error { if len(accounts) < 1 { - return nil, nil + return nil } - errors := make(map[string]error) - return errors, c.withTx(ctx, func(tx *transaction) error { + return c.withTx(ctx, func(tx *transaction) error { for _, a := range accounts { stmt := t.Account. INSERT(t.Account.AllColumns). @@ -96,7 +95,7 @@ func (c *client) UpsertAccounts(ctx context.Context, accounts ...*models.Account ) _, err := tx.exec(ctx, stmt) if err != nil { - errors[a.ID] = err + return err } } return nil diff --git a/internal/database/accounts_test.go b/internal/database/accounts_test.go index c33e4729..29a01ecf 100644 --- a/internal/database/accounts_test.go +++ b/internal/database/accounts_test.go @@ -16,7 +16,7 @@ func TestAccounts(t *testing.T) { t.Run("upsert and check a new account", func(t *testing.T) { is := is.New(t) - errors, err := client.UpsertAccounts(context.Background(), &models.Account{ + err := client.UpsertAccounts(context.Background(), &models.Account{ ID: "id-1", Realm: "realm", Nickname: "nickname-1", @@ -26,15 +26,12 @@ func TestAccounts(t *testing.T) { LastBattleTime: time.Now(), }) is.NoErr(err) - for _, err := range errors { - is.NoErr(err) - } account, err := client.GetAccountByID(context.Background(), "id-1") is.NoErr(err) is.True(account.Nickname == "nickname-1") - errors, err = client.UpsertAccounts(context.Background(), &models.Account{ + err = client.UpsertAccounts(context.Background(), &models.Account{ ID: "id-1", Realm: "realm", Nickname: "nickname-2", @@ -44,9 +41,6 @@ func TestAccounts(t *testing.T) { LastBattleTime: time.Now(), }) is.NoErr(err) - for _, err := range errors { - is.NoErr(err) - } account, err = client.GetAccountByID(context.Background(), "id-1") is.NoErr(err) @@ -56,7 +50,7 @@ func TestAccounts(t *testing.T) { t.Run("get multiple accounts", func(t *testing.T) { is := is.New(t) - errors, err := client.UpsertAccounts(context.Background(), + err := client.UpsertAccounts(context.Background(), &models.Account{ ID: "id-21", Realm: "realm", @@ -76,9 +70,6 @@ func TestAccounts(t *testing.T) { LastBattleTime: time.Now(), }) is.NoErr(err) - for _, err := range errors { - is.NoErr(err) - } accounts, err := client.GetAccounts(context.Background(), []string{"id-21", "id-22"}) is.NoErr(err) @@ -91,7 +82,7 @@ func TestAccounts(t *testing.T) { t.Run("set account to private", func(t *testing.T) { is := is.New(t) - errors, err := client.UpsertAccounts(context.Background(), &models.Account{ + err := client.UpsertAccounts(context.Background(), &models.Account{ ID: "id-10", Realm: "realm", Nickname: "nickname-10", @@ -101,9 +92,6 @@ func TestAccounts(t *testing.T) { LastBattleTime: time.Now(), }) is.NoErr(err) - for _, err := range errors { - is.NoErr(err) - } account, err := client.GetAccountByID(context.Background(), "id-10") is.NoErr(err) @@ -111,9 +99,6 @@ func TestAccounts(t *testing.T) { err = client.AccountSetPrivate(context.Background(), "id-10", true) is.NoErr(err) - for _, err := range errors { - is.NoErr(err) - } account, err = client.GetAccountByID(context.Background(), "id-10") is.NoErr(err) diff --git a/internal/database/averages.go b/internal/database/averages.go index b894af73..3440d947 100644 --- a/internal/database/averages.go +++ b/internal/database/averages.go @@ -13,18 +13,16 @@ import ( s "github.com/go-jet/jet/v2/postgres" ) -func (c *client) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) (map[string]error, error) { +func (c *client) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error { if len(averages) < 1 { - return nil, nil + return nil } - errors := make(map[string]error) - return errors, c.withTx(ctx, func(tx *transaction) error { + return c.withTx(ctx, func(tx *transaction) error { for id, data := range averages { encoded, err := json.Marshal(data) if err != nil { - errors[id] = err - continue + return err } model := m.VehicleAverage{ @@ -44,7 +42,7 @@ func (c *client) UpsertVehicleAverages(ctx context.Context, averages map[string] )) _, err = tx.exec(ctx, stmt) if err != nil { - errors[id] = err + return err } } return nil diff --git a/internal/database/averages_test.go b/internal/database/averages_test.go index 6ac81e28..6822d238 100644 --- a/internal/database/averages_test.go +++ b/internal/database/averages_test.go @@ -24,11 +24,8 @@ func TestAverages(t *testing.T) { "id-2": {Battles: 228}, } - errors, err := client.UpsertVehicleAverages(context.Background(), averages) + err := client.UpsertVehicleAverages(context.Background(), averages) is.NoErr(err) - for _, err := range errors { - is.NoErr(err) - } found, err := client.GetVehicleAverages(context.Background(), []string{"id-1", "id-2"}) is.NoErr(err) diff --git a/internal/database/cleanup_test.go b/internal/database/cleanup_test.go index 485ef4a2..d5f9aa3d 100644 --- a/internal/database/cleanup_test.go +++ b/internal/database/cleanup_test.go @@ -24,7 +24,7 @@ func TestSnapshotCleanup(t *testing.T) { defer client.db.Exec(fmt.Sprintf("DELETE FROM %s;", table.AccountSnapshot.TableName())) accountID := "a-TestSnapshotCleanup" - _, err := client.UpsertAccounts(context.Background(), &models.Account{ID: accountID, Realm: "test", Nickname: "test_account"}) + err := client.UpsertAccounts(context.Background(), &models.Account{ID: accountID, Realm: "test", Nickname: "test_account"}) assert.NoError(t, err, "failed to upsert an account") defer client.db.Exec(fmt.Sprintf("DELETE FROM %s WHERE id = '%s';", table.Account.TableName(), accountID)) diff --git a/internal/database/client.go b/internal/database/client.go index 026da508..ac07e1b6 100644 --- a/internal/database/client.go +++ b/internal/database/client.go @@ -37,7 +37,7 @@ type AccountsClient interface { GetAccountByID(ctx context.Context, id string) (models.Account, error) GetRealmAccountIDs(ctx context.Context, realm string) ([]string, error) AccountSetPrivate(ctx context.Context, id string, value bool) error - UpsertAccounts(ctx context.Context, accounts ...*models.Account) (map[string]error, error) + UpsertAccounts(ctx context.Context, accounts ...*models.Account) error } type GlossaryClient interface { @@ -45,14 +45,14 @@ type GlossaryClient interface { GetVehicles(ctx context.Context, ids []string) (map[string]models.Vehicle, error) GetVehicleAverages(ctx context.Context, ids []string) (map[string]frame.StatsFrame, error) - UpsertVehicles(ctx context.Context, vehicles map[string]models.Vehicle) (map[string]error, error) - UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) (map[string]error, error) + UpsertVehicles(ctx context.Context, vehicles map[string]models.Vehicle) error + UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error GetMap(ctx context.Context, id string) (types.Map, error) UpsertMaps(ctx context.Context, maps map[string]types.Map) error GetGameModeNames(ctx context.Context, id string) (map[language.Tag]string, error) - UpsertGameModes(ctx context.Context, modes map[string]map[language.Tag]string) (map[string]error, error) + UpsertGameModes(ctx context.Context, modes map[string]map[language.Tag]string) error } type UsersClient interface { diff --git a/internal/database/game_modes.go b/internal/database/game_modes.go index 799dfee9..6b8245aa 100644 --- a/internal/database/game_modes.go +++ b/internal/database/game_modes.go @@ -12,18 +12,16 @@ import ( "golang.org/x/text/language" ) -func (c *client) UpsertGameModes(ctx context.Context, modes map[string]map[language.Tag]string) (map[string]error, error) { +func (c *client) UpsertGameModes(ctx context.Context, modes map[string]map[language.Tag]string) error { if len(modes) < 1 { - return nil, nil + return nil } - errors := make(map[string]error) - return errors, c.withTx(ctx, func(tx *transaction) error { + return c.withTx(ctx, func(tx *transaction) error { for id, locales := range modes { encoded, err := json.Marshal(locales) if err != nil { - errors[id] = err - continue + return err } model := m.GameMode{ @@ -43,7 +41,7 @@ func (c *client) UpsertGameModes(ctx context.Context, modes map[string]map[langu )) _, err = tx.exec(ctx, stmt) if err != nil { - errors[id] = err + return err } } return nil diff --git a/internal/database/snapshots_test.go b/internal/database/snapshots_test.go index 01c81305..a1c1d9a3 100644 --- a/internal/database/snapshots_test.go +++ b/internal/database/snapshots_test.go @@ -21,7 +21,7 @@ func TestVehicleSnapshots(t *testing.T) { defer client.db.Exec(fmt.Sprintf("DELETE FROM %s;", table.VehicleSnapshot.TableName())) accountID := "a-TestVehicleSnapshots" - _, err := client.UpsertAccounts(ctx, &models.Account{ID: accountID, Realm: "test", Nickname: "test_account"}) + err := client.UpsertAccounts(ctx, &models.Account{ID: accountID, Realm: "test", Nickname: "test_account"}) assert.NoError(t, err, "failed to upsert an account") defer client.db.Exec(fmt.Sprintf("DELETE FROM %s WHERE id = '%s';", table.Account.TableName(), accountID)) @@ -137,7 +137,7 @@ func TestAccountSnapshots(t *testing.T) { accountID := "a-TestAccountSnapshots" accountID2 := "a-TestAccountSnapshots-2" - _, err := client.UpsertAccounts(ctx, &models.Account{ID: accountID, Realm: "test", Nickname: "test_account"}, &models.Account{ID: accountID2, Realm: "test", Nickname: "test_account"}) + err := client.UpsertAccounts(ctx, &models.Account{ID: accountID, Realm: "test", Nickname: "test_account"}, &models.Account{ID: accountID2, Realm: "test", Nickname: "test_account"}) is.NoErr(err) defer client.db.Exec(fmt.Sprintf("DELETE FROM %s WHERE id IN ('%s', '%s');", table.Account.TableName(), accountID, accountID2)) diff --git a/internal/database/vehicles.go b/internal/database/vehicles.go index 1f47729b..95839f3a 100644 --- a/internal/database/vehicles.go +++ b/internal/database/vehicles.go @@ -12,13 +12,12 @@ import ( s "github.com/go-jet/jet/v2/postgres" ) -func (c *client) UpsertVehicles(ctx context.Context, vehicles map[string]models.Vehicle) (map[string]error, error) { +func (c *client) UpsertVehicles(ctx context.Context, vehicles map[string]models.Vehicle) error { if len(vehicles) < 1 { - return nil, nil + return nil } - errors := make(map[string]error) - return errors, c.withTx(ctx, func(tx *transaction) error { + return c.withTx(ctx, func(tx *transaction) error { for id, data := range vehicles { model := m.Vehicle{ ID: id, @@ -28,8 +27,7 @@ func (c *client) UpsertVehicles(ctx context.Context, vehicles map[string]models. } names, err := json.Marshal(data.LocalizedNames) if err != nil { - errors[id] = err - continue + return err } model.LocalizedNames = names @@ -45,7 +43,7 @@ func (c *client) UpsertVehicles(ctx context.Context, vehicles map[string]models. _, err = tx.exec(ctx, stmt) if err != nil { - errors[id] = err + return err } } return nil diff --git a/internal/logic/snapshots.go b/internal/logic/snapshots.go index 1a02ebab..2832005b 100644 --- a/internal/logic/snapshots.go +++ b/internal/logic/snapshots.go @@ -254,17 +254,12 @@ func RecordAccountSnapshots(ctx context.Context, wgClient wargaming.Client, dbCl } } -outer: for _, accountSnapshot := range accountSnapshots { // update account cache - aErr, err := dbClient.UpsertAccounts(ctx, accountUpdates[accountSnapshot.AccountID]) + err := dbClient.UpsertAccounts(ctx, accountUpdates[accountSnapshot.AccountID]) if err != nil { log.Err(err).Str("accountId", accountSnapshot.AccountID).Msg("failed to upsert account") - } - for _, err := range aErr { - log.Err(err).Str("accountId", accountSnapshot.AccountID).Msg("failed to upsert account") - accountErrors[accountSnapshot.AccountID] = err - continue outer + continue } // save account snapshot diff --git a/internal/stats/fetch/v1/multisource.go b/internal/stats/fetch/v1/multisource.go index f396563f..067c1f87 100644 --- a/internal/stats/fetch/v1/multisource.go +++ b/internal/stats/fetch/v1/multisource.go @@ -161,13 +161,10 @@ func (c *multiSourceClient) Account(ctx context.Context, id string) (models.Acco ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() - aErr, err := c.database.UpsertAccounts(ctx, &account) + err := c.database.UpsertAccounts(ctx, &account) if err != nil { log.Err(err).Msg("failed to update account cache") } - if err := aErr[account.ID]; err != nil { - log.Err(err).Msg("failed to update account cache") - } }(account) return account, nil @@ -227,7 +224,7 @@ func (c *multiSourceClient) CurrentStats(ctx context.Context, id string, opts .. } // manually filter vehicles for cases where the slice of ids was 100+ - if options.VehicleIDs != nil && len(options.VehicleIDs) >= 100 { + if len(options.VehicleIDs) >= 100 { var filtered []types.VehicleStatsFrame for _, v := range vehicles.Data { if !slices.Contains(options.VehicleIDs, fmt.Sprint(v.TankID)) { @@ -269,13 +266,10 @@ func (c *multiSourceClient) CurrentStats(ctx context.Context, id string, opts .. ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() - aErr, err := c.database.UpsertAccounts(ctx, &account) + err := c.database.UpsertAccounts(ctx, &account) if err != nil { log.Err(err).Msg("failed to update account cache") } - if err := aErr[account.ID]; err != nil { - log.Err(err).Msg("failed to update account cache") - } }(stats.Account) return stats, nil diff --git a/tests/static_database.go b/tests/static_database.go index 7c5e7f5a..4e553d5c 100644 --- a/tests/static_database.go +++ b/tests/static_database.go @@ -64,13 +64,13 @@ func (c *staticTestingDatabase) GetRealmAccountIDs(ctx context.Context, realm st func (c *staticTestingDatabase) AccountSetPrivate(ctx context.Context, id string, value bool) error { return errors.New("AccountSetPrivate not implemented") } -func (c *staticTestingDatabase) UpsertAccounts(ctx context.Context, accounts ...*models.Account) (map[string]error, error) { +func (c *staticTestingDatabase) UpsertAccounts(ctx context.Context, accounts ...*models.Account) error { for _, acc := range accounts { if _, ok := staticAccounts[acc.ID]; ok { staticAccounts[acc.ID] = *acc } } - return nil, nil + return nil } func (c *staticTestingDatabase) GetAllVehicles(ctx context.Context) (map[string]models.Vehicle, error) { @@ -95,11 +95,11 @@ func (c *staticTestingDatabase) GetVehicleAverages(ctx context.Context, ids []st } return averages, nil } -func (c *staticTestingDatabase) UpsertVehicles(ctx context.Context, vehicles map[string]models.Vehicle) (map[string]error, error) { - return nil, errors.New("UpsertVehicles not implemented") +func (c *staticTestingDatabase) UpsertVehicles(ctx context.Context, vehicles map[string]models.Vehicle) error { + return errors.New("UpsertVehicles not implemented") } -func (c *staticTestingDatabase) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) (map[string]error, error) { - return nil, errors.New("UpsertVehicleAverages not implemented") +func (c *staticTestingDatabase) UpsertVehicleAverages(ctx context.Context, averages map[string]frame.StatsFrame) error { + return errors.New("UpsertVehicleAverages not implemented") } func (c *staticTestingDatabase) GetUserByID(ctx context.Context, id string, opts ...database.UserQueryOption) (models.User, error) { @@ -270,8 +270,8 @@ func (c *staticTestingDatabase) CreateWidgetSettings(ctx context.Context, userID func (c *staticTestingDatabase) GetGameModeNames(ctx context.Context, id string) (map[language.Tag]string, error) { return map[language.Tag]string{}, nil } -func (c *staticTestingDatabase) UpsertGameModes(ctx context.Context, modes map[string]map[language.Tag]string) (map[string]error, error) { - return nil, nil +func (c *staticTestingDatabase) UpsertGameModes(ctx context.Context, modes map[string]map[language.Tag]string) error { + return nil } func (c *staticTestingDatabase) FindUserModerationRequests(ctx context.Context, userID string, referenceIDs []string, status []models.ModerationStatus, since time.Time) ([]models.ModerationRequest, error) { From 2c273570d5fd2bbecfa333b128b0d294640f81c1 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Nov 2025 20:17:03 -0600 Subject: [PATCH 15/17] update envs, migrations, tests --- .env.example | 5 ++++- Taskfile.yaml | 4 ++-- atlas.hcl | 20 ++++---------------- 3 files changed, 10 insertions(+), 19 deletions(-) diff --git a/.env.example b/.env.example index e905324f..e44445f2 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,8 @@ DATABASE_PATH="/absolute/path/" -DATABASE_NAME="aftermath.db" +DATABASE_URL="postgresql://aftermath:password@0.0.0.0:5432/aftermath-local?sslmode=disable" +DATABASE_NAME="aftermath-local" +DATABASE_USER="aftermath" +DATABASE_PASSWORD="password" # Init INIT_GLOBAL_ADMIN_USER="" # Discord user ID for a user who will be assigned permissions.GlobalAdmin on startup, can be left blank diff --git a/Taskfile.yaml b/Taskfile.yaml index f906886d..ffd078e5 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -9,10 +9,10 @@ tasks: - ".env.test" desc: runs tests cmds: - # - task: db-migrate-apply-test + - task: db-migrate-tests - | if [ -z "{{ .CLI_ARGS }}" ]; then - go test -timeout 30s --count=1 -v ./... + go test -timeout 30s --count=1 -v ./... | grep -v "\[no test files\]" else go test -timeout 30s --count=1 -v -run {{ .CLI_ARGS }} fi diff --git a/atlas.hcl b/atlas.hcl index 7ff4811c..0cf77fae 100644 --- a/atlas.hcl +++ b/atlas.hcl @@ -1,18 +1,6 @@ -variable "database_name" { +variable "database_url" { type = string - default = getenv("DATABASE_NAME") -} -variable "database_user" { - type = string - default = getenv("DATABASE_USER") -} -variable "database_password" { - type = string - default = getenv("DATABASE_PASSWORD") -} -variable "database_host" { - type = string - default = getenv("DATABASE_HOST") + default = getenv("DATABASE_URL") } variable "sources" { type = list(string) @@ -35,7 +23,7 @@ env "local" { dir = "file://internal/database/migrations" } - url = "postgresql://${var.database_user}:${var.database_password}@${var.database_host}/${var.database_name}?sslmode=disable" + url = var.database_url dev = "docker://postgres/17/dev?search_path=public" } @@ -55,5 +43,5 @@ env "migrate" { } tx-mode = "all" - url = "postgresql://${var.database_user}:${var.database_password}@${var.database_host}/${var.database_name}?sslmode=disable" + url = var.database_url } \ No newline at end of file From 77bb6eb240fb2c836f79ce75911e150756b593a6 Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Nov 2025 20:20:44 -0600 Subject: [PATCH 16/17] cleanup --- Dockerfile | 2 +- go.mod | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index a923ebd2..5d6bdbf6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,7 +33,7 @@ RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate ./internal/assets RUN --mount=type=cache,target=$GOPATH/pkg/mod go generate ./cmd/frontend/assets/generate # generate frontend -RUN go tool templ generate +RUN --mount=type=cache,target=$GOPATH/pkg/mod go tool templ generate # build a fully standalone binary with zero dependencies RUN --mount=type=cache,target=$GOPATH/pkg/mod CGO_ENABLED=0 GOOS=linux go build -ldflags='-s -w' -trimpath -o /bin/aftermath . diff --git a/go.mod b/go.mod index 78ba673f..3daa440b 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,6 @@ go 1.24.3 require github.com/go-jet/jet/v2 v2.13.0 -// replace github.com/cufee/facepaint => ../facepaint - require ( github.com/Kodeworks/golang-image-ico v0.0.0-20141118225523-73f0f4cfade9 github.com/PuerkitoBio/goquery v1.10.3 From 3c93c74af4b8665614648a986ebc645722a5a80a Mon Sep 17 00:00:00 2001 From: Vovko Date: Fri, 21 Nov 2025 20:21:59 -0600 Subject: [PATCH 17/17] fixed command name --- internal/assets/generate.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/assets/generate.go b/internal/assets/generate.go index ed93b3a8..b177447d 100644 --- a/internal/assets/generate.go +++ b/internal/assets/generate.go @@ -123,7 +123,7 @@ func generateDiscordHelpImage(printer func(string) string) { dctx.Fill() fontSize := 20.0 - commands := []string{"help", "links", "stats", "session"} + commands := []string{"help", "links", "career", "session"} for i, name := range commands { drawY := float64((padding + int(fontSize)) + i*(int(fontSize*2)+padding)) dctx.SetColor(color.White)