From e63549158b42480513580438fdf7f4c17b572643 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:27:11 -0600 Subject: [PATCH 01/90] feat: use glsl 3 --- package.json | 2 +- .../Armor/components/CoreArmorSceneComponent/index.tsx | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 729329ee7..e48ad6e1d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "blitzkrieg", - "version": "1.16.0", + "version": "1.16.2", "description": "🎉 Tools for everything World of Tanks Blitz", "main": "src/server.ts", "type": "module", diff --git a/src/components/Armor/components/CoreArmorSceneComponent/index.tsx b/src/components/Armor/components/CoreArmorSceneComponent/index.tsx index c646ee157..aa924cb77 100644 --- a/src/components/Armor/components/CoreArmorSceneComponent/index.tsx +++ b/src/components/Armor/components/CoreArmorSceneComponent/index.tsx @@ -1,6 +1,7 @@ import { useFrame } from '@react-three/fiber'; import { useEffect } from 'react'; import { + GLSL3, MeshBasicMaterial, Object3D, ShaderMaterial, @@ -38,6 +39,7 @@ export function CoreArmorSceneComponent({ thickness, }: CoreArmorSceneComponentProps) { const material = new ShaderMaterial({ + glslVersion: GLSL3, fragmentShader, vertexShader, From 035e257926c1c96155baf69975fd3baa0917882d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Fri, 1 Mar 2024 22:54:03 -0600 Subject: [PATCH 02/90] feat: add closedkrieg --- .gitmodules | 3 +++ submodules/closedkrieg | 1 + 2 files changed, 4 insertions(+) create mode 100644 .gitmodules create mode 160000 submodules/closedkrieg diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..6b4e7c7e0 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "submodules/closedkrieg"] + path = submodules/closedkrieg + url = https://github.com/tresabhi/closedkrieg diff --git a/submodules/closedkrieg b/submodules/closedkrieg new file mode 160000 index 000000000..ade51426f --- /dev/null +++ b/submodules/closedkrieg @@ -0,0 +1 @@ +Subproject commit ade51426fa8f4e653de48b25dc16ff6942fa034a From 66740bcb8d98daec5050fd458c99f40b3b349319 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 3 Mar 2024 15:58:21 -0600 Subject: [PATCH 03/90] feat: vocab --- .vscode/settings.json | 2 ++ submodules/closedkrieg | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index b395babe4..dfd14db10 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -40,8 +40,10 @@ "donotremove", "drei", "Droodles", + "dvpd", "dvpl", "DVPLCRC", + "dvpm", "dxgi", "Everytime", "FASTNAME", diff --git a/submodules/closedkrieg b/submodules/closedkrieg index ade51426f..ea6012c50 160000 --- a/submodules/closedkrieg +++ b/submodules/closedkrieg @@ -1 +1 @@ -Subproject commit ade51426fa8f4e653de48b25dc16ff6942fa034a +Subproject commit ea6012c50ba7a5d1c6673ac9a8067759a5ec5f05 From f6a418499b58edbeca7c7f1c84d5453395013d0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 3 Mar 2024 16:08:29 -0600 Subject: [PATCH 04/90] feat: direct depot --- .github/workflows/cd-assets.yml | 2 +- .vscode/settings.json | 1 + scripts/buildAssets/constants.ts | 11 ++++++----- src/core/blitzkrieg/secrets.ts | 1 + submodules/closedkrieg | 2 +- 5 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cd-assets.yml b/.github/workflows/cd-assets.yml index 70480c78a..7207bba0e 100644 --- a/.github/workflows/cd-assets.yml +++ b/.github/workflows/cd-assets.yml @@ -44,7 +44,7 @@ jobs: run: yarn setup:depotdownloader - name: Run DepotDownloader - run: ./temp/depotDownloader/DepotDownloader.exe -app 444200 -username ${{ secrets.DEPOT_DOWNLOADER_USERNAME }} -password "${{ secrets.DEPOT_DOWNLOADER_PASSWORD }}" + run: ./temp/depotDownloader/DepotDownloader.exe -app 444202 -username ${{ secrets.DEPOT_DOWNLOADER_USERNAME }} -password "${{ secrets.DEPOT_DOWNLOADER_PASSWORD }}" - name: Build assets run: | diff --git a/.vscode/settings.json b/.vscode/settings.json index dfd14db10..dca4e6fa9 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -140,6 +140,7 @@ "webp", "winrate", "wireframe", + "WOTB", "wotblitz", "xffn", "xkdkw", diff --git a/scripts/buildAssets/constants.ts b/scripts/buildAssets/constants.ts index 741fa1fb0..d9c07cac5 100644 --- a/scripts/buildAssets/constants.ts +++ b/scripts/buildAssets/constants.ts @@ -1,12 +1,13 @@ -import { readdir } from 'fs/promises'; import { argv } from 'process'; const isDepot = argv.includes('--depot'); +export const WOTB_WIN32_DEPOT = 444202; export const DATA = isDepot - ? await (async () => { - const [installationVersion] = await readdir('depots/444202'); - return `depots/444202/${installationVersion}/Data`; - })() + ? // ? await (async () => { + // const [installationVersion] = await readdir(`depots/${WOTB_WIN32_DEPOT}`); + // return `depots/${WOTB_WIN32_DEPOT}/${installationVersion}/Data`; + // })() + `depots/${WOTB_WIN32_DEPOT}/Data` : 'C:/Program Files (x86)/Steam/steamapps/common/World of Tanks Blitz/Data'; export const POI = { diff --git a/src/core/blitzkrieg/secrets.ts b/src/core/blitzkrieg/secrets.ts index 264382263..5cb7ae534 100644 --- a/src/core/blitzkrieg/secrets.ts +++ b/src/core/blitzkrieg/secrets.ts @@ -6,6 +6,7 @@ export const SECRET_KEYS = [ 'DISCORD_TOKEN', 'GH_TOKEN', 'DISCORD_BLITZ_DB', + 'WOTB_DLC_CDN', ] as const; export type SECRET = typeof SECRET_KEYS extends readonly (infer T)[] diff --git a/submodules/closedkrieg b/submodules/closedkrieg index ea6012c50..39fb1bbe9 160000 --- a/submodules/closedkrieg +++ b/submodules/closedkrieg @@ -1 +1 @@ -Subproject commit ea6012c50ba7a5d1c6673ac9a8067759a5ec5f05 +Subproject commit 39fb1bbe9c6f1690f0d085dc765c7c99aa01b11a From 9efe9860ae701112e477c554bcfb4e0d779e51db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 3 Mar 2024 17:42:53 -0600 Subject: [PATCH 05/90] feat: static grid --- package.json | 3 +- .../tankopedia/[id]/components/SceneProps.tsx | 34 +++------ src/components/InfiniteGridHelper.tsx | 76 ------------------- 3 files changed, 13 insertions(+), 100 deletions(-) delete mode 100644 src/components/InfiniteGridHelper.tsx diff --git a/package.json b/package.json index e48ad6e1d..b4f9ff3f3 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "lint:version": "vite-node --script scripts/checkVersionMissMatch", "knip": "knip", "update": "yarn upgrade --latest", - "setup:depotdownloader": "vite-node --script scripts/setupDepotDownloader" + "setup:depotdownloader": "vite-node --script scripts/setupDepotDownloader", + "tunnel": "cloudflared tunnel --url http://localhost:3000" }, "license": "MIT", "dependencies": { diff --git a/src/app/tools/tankopedia/[id]/components/SceneProps.tsx b/src/app/tools/tankopedia/[id]/components/SceneProps.tsx index 2be3155b5..1866a9973 100644 --- a/src/app/tools/tankopedia/[id]/components/SceneProps.tsx +++ b/src/app/tools/tankopedia/[id]/components/SceneProps.tsx @@ -1,32 +1,20 @@ -import { Color } from 'three'; -import InfiniteGridHelper from '../../../../../components/InfiniteGridHelper'; +import { useLoader } from '@react-three/fiber'; +import { TextureLoader } from 'three'; import { useTankopediaPersistent } from '../../../../../stores/tankopedia'; export function SceneProps() { - const showGrid = useTankopediaPersistent( - (state) => state.model.visual.showGrid, - ); - const showEnvironment = useTankopediaPersistent( - (state) => state.model.visual.showEnvironment, + const show = useTankopediaPersistent( + (state) => + state.model.visual.showGrid && !state.model.visual.showEnvironment, ); + const texture = useLoader(TextureLoader, 'https://i.imgur.com/6QjSn1e.png'); - if (!showGrid || showEnvironment) return null; + if (!show) return null; return ( - <> - - - + + + + ); } diff --git a/src/components/InfiniteGridHelper.tsx b/src/components/InfiniteGridHelper.tsx deleted file mode 100644 index f6b4dc298..000000000 --- a/src/components/InfiniteGridHelper.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { MeshProps } from '@react-three/fiber'; -import { forwardRef } from 'react'; -import { DoubleSide, Mesh } from 'three'; - -interface InfiniteGridHelperProps extends MeshProps { - size1?: number; - size2?: number; - color?: THREE.Color; - distance?: number; -} -/** - * Thanks, Fyrestar, for [THREE.InfiniteGridHelper](https://github.com/Fyrestar/THREE.InfiniteGridHelper/)! - */ -const InfiniteGridHelper = forwardRef( - ({ size1, size2, color, distance, ...props }, ref) => { - return ( - - - - - ); - }, -); -export default InfiniteGridHelper; From 90bff59af35bbb5cf4420fb56ae2f144138de382 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 3 Mar 2024 17:59:39 -0600 Subject: [PATCH 06/90] feat: more muted colors --- .../tankopedia/[id]/components/SceneProps.tsx | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/app/tools/tankopedia/[id]/components/SceneProps.tsx b/src/app/tools/tankopedia/[id]/components/SceneProps.tsx index 1866a9973..203d41d05 100644 --- a/src/app/tools/tankopedia/[id]/components/SceneProps.tsx +++ b/src/app/tools/tankopedia/[id]/components/SceneProps.tsx @@ -1,5 +1,7 @@ -import { useLoader } from '@react-three/fiber'; -import { TextureLoader } from 'three'; +import { useFrame, useLoader } from '@react-three/fiber'; +import { clamp } from 'lodash'; +import { useRef } from 'react'; +import { MeshStandardMaterial, TextureLoader } from 'three'; import { useTankopediaPersistent } from '../../../../../stores/tankopedia'; export function SceneProps() { @@ -7,14 +9,22 @@ export function SceneProps() { (state) => state.model.visual.showGrid && !state.model.visual.showEnvironment, ); - const texture = useLoader(TextureLoader, 'https://i.imgur.com/6QjSn1e.png'); + const texture = useLoader(TextureLoader, 'https://i.imgur.com/C28Z8nU.png'); + const material = useRef(null); + + useFrame(({ camera }) => { + if (!material.current) return; + material.current.opacity = clamp(0.1 * camera.position.y, 0, 1); + }); + + texture.anisotropy = 2; if (!show) return null; return ( - + ); } From d3a9c3bd95da28c9791dcea318b9acbd3d2fb242 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 3 Mar 2024 20:24:31 -0600 Subject: [PATCH 07/90] Create crowdin.yml --- crowdin.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 crowdin.yml diff --git a/crowdin.yml b/crowdin.yml new file mode 100644 index 000000000..3abba759a --- /dev/null +++ b/crowdin.yml @@ -0,0 +1,3 @@ +files: + - source: /src/lang/en-US.json + translation: /src/lang/%locale%.json From 8865a489362f466f5886296d9a3bd99465777d92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 3 Mar 2024 21:41:12 -0600 Subject: [PATCH 08/90] feat: base lang --- src/lang/en.json | 71 ++++++++++++++++++++++++++++++++++++++++++ submodules/closedkrieg | 2 +- 2 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 src/lang/en.json diff --git a/src/lang/en.json b/src/lang/en.json new file mode 100644 index 000000000..0c13dcf64 --- /dev/null +++ b/src/lang/en.json @@ -0,0 +1,71 @@ +{ + "bot": { + "common": { + "wn8": { + "$": "WN8", + "very_bad": "Very bad", + "bad": "Bad", + "below_average": "Below average", + "average": "Average", + "above_average": "Above average", + "good": "Good", + "very_good": "Very good", + "great": "Great", + "unicum": "Unicum", + "super_unicum": "Super unicum" + } + }, + + "commands": { + "about": { + "$": "about", + "description": "All the info you need about the bot", + "subcommands": { + "commands": { + "$": "commands", + "description": "List of all commands and their descriptions", + "body": "# Commands\n\nBlitzkrieg offers the following commands:" + }, + "embeds": { + "$": "embeds", + "description": "How embeds work" + }, + "introduction": { + "$": "introduction", + "description": "About the bot" + }, + "invite": { + "$": "invite", + "description": "How to invite the bot" + }, + "timezones": { + "$": "timezones", + "description": "How Blitzkrieg infers and uses your timezone" + }, + "creators": { + "$": "creators", + "description": "People who have worked on the bot" + } + } + }, + + "breakdown": { + "$": "breakdown", + "description": "A period's breakdown by tanks played", + + "body": { + "total": "Total", + "battles": "Battles", + "winrate": "Winrate", + "damage": "Damage" + }, + + "buttons": { + "refresh": "Refresh" + } + } + } + }, + + "website": {} +} diff --git a/submodules/closedkrieg b/submodules/closedkrieg index 39fb1bbe9..dad92d231 160000 --- a/submodules/closedkrieg +++ b/submodules/closedkrieg @@ -1 +1 @@ -Subproject commit 39fb1bbe9c6f1690f0d085dc765c7c99aa01b11a +Subproject commit dad92d2310d80c0c8b268d2b28765427584595e7 From ec628e4a0de08de53bf6952ce1a589d5fa9799e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 3 Mar 2024 21:42:08 -0600 Subject: [PATCH 09/90] fix: rename to en us --- src/lang/{en.json => en-US.json} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/lang/{en.json => en-US.json} (100%) diff --git a/src/lang/en.json b/src/lang/en-US.json similarity index 100% rename from src/lang/en.json rename to src/lang/en-US.json From e42ff03c4b733dc4205460ad82a60e39ef1556a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 3 Mar 2024 21:44:38 -0600 Subject: [PATCH 10/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 66 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/lang/es-ES.json diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json new file mode 100644 index 000000000..998a77718 --- /dev/null +++ b/src/lang/es-ES.json @@ -0,0 +1,66 @@ +{ + "bot": { + "common": { + "wn8": { + "$": "WN8", + "very_bad": "Very bad", + "bad": "Bad", + "below_average": "Below average", + "average": "Average", + "above_average": "Above average", + "good": "Good", + "very_good": "Very good", + "great": "Great", + "unicum": "Unicum", + "super_unicum": "Super unicum" + } + }, + "commands": { + "about": { + "$": "about", + "description": "All the info you need about the bot", + "subcommands": { + "commands": { + "$": "commands", + "description": "List of all commands and their descriptions", + "body": "# Commands\n\nBlitzkrieg offers the following commands:" + }, + "embeds": { + "$": "embeds", + "description": "How embeds work" + }, + "introduction": { + "$": "introduction", + "description": "About the bot" + }, + "invite": { + "$": "invite", + "description": "How to invite the bot" + }, + "timezones": { + "$": "timezones", + "description": "How Blitzkrieg infers and uses your timezone" + }, + "creators": { + "$": "creators", + "description": "People who have worked on the bot" + } + } + }, + "breakdown": { + "$": "breakdown", + "description": "A period's breakdown by tanks played", + "body": { + "total": "Total", + "battles": "Battles", + "winrate": "Winrate", + "damage": "Damage" + }, + "buttons": { + "refresh": "Refresh" + } + } + } + }, + "website": {} +} From bef48eca62d7dcffc587389381b90bad7bf49bfd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 3 Mar 2024 21:44:38 -0600 Subject: [PATCH 11/90] New translations en-us.json (Japanese) --- src/lang/ja-JP.json | 66 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/lang/ja-JP.json diff --git a/src/lang/ja-JP.json b/src/lang/ja-JP.json new file mode 100644 index 000000000..998a77718 --- /dev/null +++ b/src/lang/ja-JP.json @@ -0,0 +1,66 @@ +{ + "bot": { + "common": { + "wn8": { + "$": "WN8", + "very_bad": "Very bad", + "bad": "Bad", + "below_average": "Below average", + "average": "Average", + "above_average": "Above average", + "good": "Good", + "very_good": "Very good", + "great": "Great", + "unicum": "Unicum", + "super_unicum": "Super unicum" + } + }, + "commands": { + "about": { + "$": "about", + "description": "All the info you need about the bot", + "subcommands": { + "commands": { + "$": "commands", + "description": "List of all commands and their descriptions", + "body": "# Commands\n\nBlitzkrieg offers the following commands:" + }, + "embeds": { + "$": "embeds", + "description": "How embeds work" + }, + "introduction": { + "$": "introduction", + "description": "About the bot" + }, + "invite": { + "$": "invite", + "description": "How to invite the bot" + }, + "timezones": { + "$": "timezones", + "description": "How Blitzkrieg infers and uses your timezone" + }, + "creators": { + "$": "creators", + "description": "People who have worked on the bot" + } + } + }, + "breakdown": { + "$": "breakdown", + "description": "A period's breakdown by tanks played", + "body": { + "total": "Total", + "battles": "Battles", + "winrate": "Winrate", + "damage": "Damage" + }, + "buttons": { + "refresh": "Refresh" + } + } + } + }, + "website": {} +} From cba93b3d556c7a7cc0667de27066e98210a42d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 3 Mar 2024 22:13:22 -0600 Subject: [PATCH 12/90] feat: translate funcs --- .github/workflows/ci-version.yml | 32 ---------------------- src/core/localization/strings.ts | 17 ++++++++++++ src/core/localization/translator.ts | 42 +++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 32 deletions(-) delete mode 100644 .github/workflows/ci-version.yml create mode 100644 src/core/localization/strings.ts create mode 100644 src/core/localization/translator.ts diff --git a/.github/workflows/ci-version.yml b/.github/workflows/ci-version.yml deleted file mode 100644 index 3e567d42d..000000000 --- a/.github/workflows/ci-version.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: CI Version - -on: push - -jobs: - host: - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Use node - uses: actions/setup-node@v3 - with: - node-version: 18 - cache: 'npm' - - - name: Install Yarn - run: npm install --location=global yarn - - - name: Use node_modules cache - uses: actions/cache@v3 - with: - path: '**/node_modules' - key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }} - - - name: Install dependencies - run: yarn - - - name: Generate leaderboard - run: yarn lint:version diff --git a/src/core/localization/strings.ts b/src/core/localization/strings.ts new file mode 100644 index 000000000..f372d8a45 --- /dev/null +++ b/src/core/localization/strings.ts @@ -0,0 +1,17 @@ +const LANGUAGES = ['en-US', 'es-ES', 'ja-JP'] as const; + +export type Language = (typeof LANGUAGES)[number]; +export type TranslationFragmentTree = { + [key: string]: TranslationFragment; +} & { + $?: string; +}; +export type TranslationFragment = string | TranslationFragmentTree; + +export const translations = LANGUAGES.reduce( + (table, language) => ({ + ...table, + [language]: import(`../../lang/${language}.json`), + }), + {}, +) as Record>; diff --git a/src/core/localization/translator.ts b/src/core/localization/translator.ts new file mode 100644 index 000000000..3f3e204a4 --- /dev/null +++ b/src/core/localization/translator.ts @@ -0,0 +1,42 @@ +import { Language, translations } from './strings'; + +export async function translator(language: Language) { + const strings = await translations[language]; + + function translate(key: string) { + const path = key.split('.'); + let fragment = strings; + + for (const pathItem in path) { + if (typeof fragment === 'string') { + throw new SyntaxError( + `Attempted to access sub-string ("${pathItem}" in "${path}") from a string instead of a tree`, + ); + } else { + fragment = fragment[pathItem]; + + if (typeof fragment === 'undefined') { + throw new Error( + `Undefined translation for key "${key}" (in "${path}") in language "${language}"`, + ); + } + } + } + + if (typeof fragment === 'string') { + return fragment; + } else if (fragment.$) { + return fragment.$!; + } else { + throw new Error( + `Unresolved tree ending for path "${path}" in language "${language}"`, + ); + } + } + + function t(strings: TemplateStringsArray) { + return translate(strings[0]); + } + + return { translate, t }; +} From 11a0c246dc8528bd43d18549062b342435b8cf98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Sun, 3 Mar 2024 22:21:06 -0600 Subject: [PATCH 13/90] feat: error tolerant command registeration --- src/events/interactionCreate/index.ts | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/events/interactionCreate/index.ts b/src/events/interactionCreate/index.ts index 390055141..81e6b9452 100644 --- a/src/events/interactionCreate/index.ts +++ b/src/events/interactionCreate/index.ts @@ -112,19 +112,16 @@ export const COMMANDS_RAW: CommandRegistry[] = [ todayCommand, ]; -export const commands = Promise.all(COMMANDS_RAW).then((rawCommands) => - rawCommands.reduce>( - (accumulator, registry) => { - if (isDev()) registry.command.setDefaultMemberPermissions(0); - - return { - ...accumulator, - [registry.command.name]: registry, - }; +export const commands = Promise.allSettled(COMMANDS_RAW).then((rawCommands) => { + return rawCommands.reduce>( + (commands, registry) => { + if (registry.status === 'rejected') return commands; + if (isDev()) registry.value.command.setDefaultMemberPermissions(0); + return { ...commands, [registry.value.command.name]: registry.value }; }, {}, - ), -); + ); +}); export const guildCommands: RESTPostAPIChatInputApplicationCommandsJSONBody[] = []; From d2edf4b89da21f906f5d6a9d56b7cebc95de7fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 4 Mar 2024 02:54:51 -0600 Subject: [PATCH 14/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 54 ++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index 998a77718..2c41848ec 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -3,47 +3,47 @@ "common": { "wn8": { "$": "WN8", - "very_bad": "Very bad", - "bad": "Bad", - "below_average": "Below average", - "average": "Average", - "above_average": "Above average", - "good": "Good", - "very_good": "Very good", - "great": "Great", - "unicum": "Unicum", - "super_unicum": "Super unicum" + "very_bad": "Muy malo", + "bad": "Malo", + "below_average": "Inferior a la media", + "average": "Promedio", + "above_average": "Superior a la media", + "good": "Bueno", + "very_good": "Muy bueno", + "great": "Estupendo", + "unicum": "Jugador único", + "super_unicum": "Superjugador único" } }, "commands": { "about": { - "$": "about", - "description": "All the info you need about the bot", + "$": "acerca de", + "description": "Toda la información que necesitas acerca del bot", "subcommands": { "commands": { - "$": "commands", - "description": "List of all commands and their descriptions", - "body": "# Commands\n\nBlitzkrieg offers the following commands:" + "$": "comandos", + "description": "Lista de todos los comandos y sus descripciones", + "body": "# Comandos\n\nBlitzkrieg ofrece los siguientes comandos:" }, "embeds": { "$": "embeds", - "description": "How embeds work" + "description": "Cómo funcionan los embeds" }, "introduction": { - "$": "introduction", - "description": "About the bot" + "$": "introducción", + "description": "Acerca del bot" }, "invite": { - "$": "invite", - "description": "How to invite the bot" + "$": "invitar", + "description": "Cómo invitar el bot" }, "timezones": { - "$": "timezones", + "$": "zonas horarias", "description": "How Blitzkrieg infers and uses your timezone" }, "creators": { - "$": "creators", - "description": "People who have worked on the bot" + "$": "creadores", + "description": "Las personas que han trabajado en el bot" } } }, @@ -52,12 +52,12 @@ "description": "A period's breakdown by tanks played", "body": { "total": "Total", - "battles": "Battles", - "winrate": "Winrate", - "damage": "Damage" + "battles": "Batallas", + "winrate": "Ratio de victorias", + "damage": "Daño" }, "buttons": { - "refresh": "Refresh" + "refresh": "Actualizar" } } } From 47c3aa4842d445a334efe1f85fe95fb053360a8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 4 Mar 2024 05:42:48 -0600 Subject: [PATCH 15/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index 2c41848ec..232ba08ae 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -39,7 +39,7 @@ }, "timezones": { "$": "zonas horarias", - "description": "How Blitzkrieg infers and uses your timezone" + "description": "Cómo Blitzkrieg infiere y usa tu zona horaria" }, "creators": { "$": "creadores", @@ -48,8 +48,8 @@ } }, "breakdown": { - "$": "breakdown", - "description": "A period's breakdown by tanks played", + "$": "desglose", + "description": "Desglose de un período por tanques jugados", "body": { "total": "Total", "battles": "Batallas", From ac60a436f135a06b9bd21aa067b52c3e492330e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Mon, 4 Mar 2024 10:33:02 -0600 Subject: [PATCH 16/90] feat: started implementation of translations --- src/commands/about.ts | 69 ++++++++++++++++------------- src/core/localization/strings.ts | 27 +++++++---- src/core/localization/translator.ts | 27 ++++++----- src/lang/en-US.json | 2 +- src/lang/es-ES.json | 2 +- src/lang/ja-JP.json | 66 --------------------------- 6 files changed, 75 insertions(+), 118 deletions(-) delete mode 100644 src/lang/ja-JP.json diff --git a/src/commands/about.ts b/src/commands/about.ts index fd97d6ed7..961d61abd 100644 --- a/src/commands/about.ts +++ b/src/commands/about.ts @@ -2,6 +2,7 @@ import { SlashCommandBuilder, SlashCommandSubcommandsOnlyBuilder, } from 'discord.js'; +import { translator } from '../core/localization/translator'; import { COMMANDS_RAW, CommandRegistry } from '../events/interactionCreate'; const RAW_PATH = `https://raw.githubusercontent.com/tresabhi/blitzkrieg/main/docs/`; @@ -28,40 +29,46 @@ function addDocsSubcommands(option: SlashCommandSubcommandsOnlyBuilder) { return option; } -export const aboutCommand: CommandRegistry = { - inProduction: true, - inPublic: true, +export const aboutCommand = new Promise(async (resolve) => { + resolve({ + inProduction: true, + inPublic: true, - command: addDocsSubcommands( - new SlashCommandBuilder() - .setName('about') - .setDescription('All the info you need about the bot') - .addSubcommand((option) => - option - .setName('commands') - .setDescription('List of all commands and their descriptions'), - ), - ), + command: addDocsSubcommands( + new SlashCommandBuilder() + .setName('about') + .setNameLocalizations({ + // "es-ES": + }) + .setDescription('All the info you need about the bot') + .addSubcommand((option) => + option + .setName('commands') + .setDescription('List of all commands and their descriptions'), + ), + ), - async handler(interaction) { - const subcommand = interaction.options.getSubcommand(); + async handler(interaction) { + const subcommand = interaction.options.getSubcommand(); + const { t } = await translator(interaction.locale); - if (subcommand === 'commands') { - return `# Commands\n\nBlitzkrieg offers the following commands:\n\n${( - await Promise.all(COMMANDS_RAW) - ) - .filter((registry) => registry.inPublic && registry.inProduction) - .sort((a, b) => (a.command.name < b.command.name ? -1 : 1)) - .map( - (registry) => - `- \`/${registry.command.name}\`: ${registry.command.description}`, + if (subcommand === 'commands') { + return t`bot.commands.about.subcommands.commands.body${( + await Promise.all(COMMANDS_RAW) ) - .join('\n')}`; - } + .filter((registry) => registry.inPublic && registry.inProduction) + .sort((a, b) => (a.command.name < b.command.name ? -1 : 1)) + .map( + (registry) => + `- \`/${registry.command.name}\`: ${registry.command.description}`, + ) + .join('\n')}`; + } - const url = `${RAW_PATH}${DOCS[subcommand as keyof typeof DOCS]}.md`; - const content = fetch(url).then((response) => response.text()); + const url = `${RAW_PATH}${DOCS[subcommand as keyof typeof DOCS]}.md`; + const content = fetch(url).then((response) => response.text()); - return content; - }, -}; + return content; + }, + }); +}); diff --git a/src/core/localization/strings.ts b/src/core/localization/strings.ts index f372d8a45..eb479c46b 100644 --- a/src/core/localization/strings.ts +++ b/src/core/localization/strings.ts @@ -1,6 +1,7 @@ -const LANGUAGES = ['en-US', 'es-ES', 'ja-JP'] as const; +import { Locale } from 'discord.js'; + +const LOCALES: Locale[number][] = ['en-US', 'es-ES']; -export type Language = (typeof LANGUAGES)[number]; export type TranslationFragmentTree = { [key: string]: TranslationFragment; } & { @@ -8,10 +9,18 @@ export type TranslationFragmentTree = { }; export type TranslationFragment = string | TranslationFragmentTree; -export const translations = LANGUAGES.reduce( - (table, language) => ({ - ...table, - [language]: import(`../../lang/${language}.json`), - }), - {}, -) as Record>; +export const translations = Promise.all( + LOCALES.map(async (locale) => ({ + locale, + translations: await import(`../../lang/${locale}.json`), + })), +).then( + (translations) => + translations.reduce( + (table, { locale, translations }) => ({ + ...table, + [locale]: translations, + }), + {}, + ) as Record, +); diff --git a/src/core/localization/translator.ts b/src/core/localization/translator.ts index 3f3e204a4..81629e9e7 100644 --- a/src/core/localization/translator.ts +++ b/src/core/localization/translator.ts @@ -1,13 +1,14 @@ -import { Language, translations } from './strings'; +import { Locale } from 'discord.js'; +import { translations } from './strings'; -export async function translator(language: Language) { - const strings = await translations[language]; +export async function translator(locale: Locale) { + const strings = (await translations)[locale]; - function translate(key: string) { - const path = key.split('.'); + function translate(path: string) { + const pathArray = path.split('.'); let fragment = strings; - for (const pathItem in path) { + for (const pathItem of pathArray) { if (typeof fragment === 'string') { throw new SyntaxError( `Attempted to access sub-string ("${pathItem}" in "${path}") from a string instead of a tree`, @@ -17,7 +18,7 @@ export async function translator(language: Language) { if (typeof fragment === 'undefined') { throw new Error( - `Undefined translation for key "${key}" (in "${path}") in language "${language}"`, + `Undefined translation for key "${path}" (in "${path}") in language "${locale}"`, ); } } @@ -29,13 +30,19 @@ export async function translator(language: Language) { return fragment.$!; } else { throw new Error( - `Unresolved tree ending for path "${path}" in language "${language}"`, + `Unresolved tree ending for "${path}" in language "${locale}"`, ); } } - function t(strings: TemplateStringsArray) { - return translate(strings[0]); + function t(paths: TemplateStringsArray, ...embeds: string[]) { + return paths + .map((path, index) => + path.length === 0 + ? '' + : `${translate(path)}${index === embeds.length ? '' : embeds[index]}`, + ) + .join(''); } return { translate, t }; diff --git a/src/lang/en-US.json b/src/lang/en-US.json index 0c13dcf64..4a704e281 100644 --- a/src/lang/en-US.json +++ b/src/lang/en-US.json @@ -24,7 +24,7 @@ "commands": { "$": "commands", "description": "List of all commands and their descriptions", - "body": "# Commands\n\nBlitzkrieg offers the following commands:" + "body": "# Commands\n\nBlitzkrieg offers the following commands:\n\n" }, "embeds": { "$": "embeds", diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index 232ba08ae..b20f01a5e 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -23,7 +23,7 @@ "commands": { "$": "comandos", "description": "Lista de todos los comandos y sus descripciones", - "body": "# Comandos\n\nBlitzkrieg ofrece los siguientes comandos:" + "body": "# Comandos\n\nBlitzkrieg ofrece los siguientes comandos:\n\n" }, "embeds": { "$": "embeds", diff --git a/src/lang/ja-JP.json b/src/lang/ja-JP.json deleted file mode 100644 index 998a77718..000000000 --- a/src/lang/ja-JP.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "bot": { - "common": { - "wn8": { - "$": "WN8", - "very_bad": "Very bad", - "bad": "Bad", - "below_average": "Below average", - "average": "Average", - "above_average": "Above average", - "good": "Good", - "very_good": "Very good", - "great": "Great", - "unicum": "Unicum", - "super_unicum": "Super unicum" - } - }, - "commands": { - "about": { - "$": "about", - "description": "All the info you need about the bot", - "subcommands": { - "commands": { - "$": "commands", - "description": "List of all commands and their descriptions", - "body": "# Commands\n\nBlitzkrieg offers the following commands:" - }, - "embeds": { - "$": "embeds", - "description": "How embeds work" - }, - "introduction": { - "$": "introduction", - "description": "About the bot" - }, - "invite": { - "$": "invite", - "description": "How to invite the bot" - }, - "timezones": { - "$": "timezones", - "description": "How Blitzkrieg infers and uses your timezone" - }, - "creators": { - "$": "creators", - "description": "People who have worked on the bot" - } - } - }, - "breakdown": { - "$": "breakdown", - "description": "A period's breakdown by tanks played", - "body": { - "total": "Total", - "battles": "Battles", - "winrate": "Winrate", - "damage": "Damage" - }, - "buttons": { - "refresh": "Refresh" - } - } - } - }, - "website": {} -} From 77ac893a7cd4d40fbb092d10af252d61eb38d7e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 5 Mar 2024 09:30:20 -0600 Subject: [PATCH 17/90] feat: es fix --- src/lang/es-ES.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index b20f01a5e..b6e4654ba 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -17,7 +17,7 @@ }, "commands": { "about": { - "$": "acerca de", + "$": "acerca", "description": "Toda la información que necesitas acerca del bot", "subcommands": { "commands": { From c9d50f39b514f90d3c8b3fb4c81efc88aa53cbf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 5 Mar 2024 09:47:34 -0600 Subject: [PATCH 18/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index b6e4654ba..766e933a7 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -17,7 +17,7 @@ }, "commands": { "about": { - "$": "acerca", + "$": "acerca-de", "description": "Toda la información que necesitas acerca del bot", "subcommands": { "commands": { @@ -38,7 +38,7 @@ "description": "Cómo invitar el bot" }, "timezones": { - "$": "zonas horarias", + "$": "zonas-horarias", "description": "Cómo Blitzkrieg infiere y usa tu zona horaria" }, "creators": { From db7c865c769455309c49fbd55883ef0ae3300992 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 5 Mar 2024 09:59:46 -0600 Subject: [PATCH 19/90] feat: partially translated /about --- src/commands/about.ts | 69 ++++++++++------------ src/core/discord/createLocalizedCommand.ts | 48 +++++++++++++++ src/core/discord/localizationObject.ts | 17 ++++++ src/core/localization/strings.ts | 6 +- src/core/localization/translator.ts | 7 ++- 5 files changed, 104 insertions(+), 43 deletions(-) create mode 100644 src/core/discord/createLocalizedCommand.ts create mode 100644 src/core/discord/localizationObject.ts diff --git a/src/commands/about.ts b/src/commands/about.ts index 961d61abd..d328d9107 100644 --- a/src/commands/about.ts +++ b/src/commands/about.ts @@ -1,52 +1,33 @@ -import { - SlashCommandBuilder, - SlashCommandSubcommandsOnlyBuilder, -} from 'discord.js'; +import { createLocalizedCommand } from '../core/discord/createLocalizedCommand'; import { translator } from '../core/localization/translator'; import { COMMANDS_RAW, CommandRegistry } from '../events/interactionCreate'; -const RAW_PATH = `https://raw.githubusercontent.com/tresabhi/blitzkrieg/main/docs/`; -const DOCS = { +const subcommands = [ + 'embeds', + 'introduction', + 'invite', + 'timezones', + 'creators', +] as const; +type Subcommand = (typeof subcommands)[number]; + +const DOCS: Record = { embeds: 'guide/embeds', introduction: 'guide/introduction', invite: 'guide/invite', timezones: 'guide/timezones', creators: 'guide/creators', }; -const DOC_DESCRIPTIONS: Record = { - embeds: 'How embeds work', - introduction: 'About the bot', - invite: 'How to invite the bot', - timezones: 'How Blitzkrieg infers and uses your timezone', - creators: 'Who created the bot', -}; - -function addDocsSubcommands(option: SlashCommandSubcommandsOnlyBuilder) { - Object.entries(DOC_DESCRIPTIONS).forEach(([key, value]) => { - option.addSubcommand((sub) => sub.setName(key).setDescription(value)); - }); - - return option; -} export const aboutCommand = new Promise(async (resolve) => { resolve({ inProduction: true, inPublic: true, - command: addDocsSubcommands( - new SlashCommandBuilder() - .setName('about') - .setNameLocalizations({ - // "es-ES": - }) - .setDescription('All the info you need about the bot') - .addSubcommand((option) => - option - .setName('commands') - .setDescription('List of all commands and their descriptions'), - ), - ), + command: await createLocalizedCommand('about', [ + { subcommand: 'commands' }, + ...subcommands.map((subcommand) => ({ subcommand })), + ]), async handler(interaction) { const subcommand = interaction.options.getSubcommand(); @@ -57,15 +38,29 @@ export const aboutCommand = new Promise(async (resolve) => { await Promise.all(COMMANDS_RAW) ) .filter((registry) => registry.inPublic && registry.inProduction) - .sort((a, b) => (a.command.name < b.command.name ? -1 : 1)) + .sort((a, b) => + (a.command.name_localizations?.[interaction.locale] ?? + a.command.name) < + (b.command.name_localizations?.[interaction.locale] ?? + b.command.name) + ? -1 + : 1, + ) .map( (registry) => - `- \`/${registry.command.name}\`: ${registry.command.description}`, + `- \`/${ + registry.command.name_localizations?.[interaction.locale] ?? + registry.command.name + }\`: ${ + registry.command.description_localizations?.[ + interaction.locale + ] ?? registry.command.description + }`, ) .join('\n')}`; } - const url = `${RAW_PATH}${DOCS[subcommand as keyof typeof DOCS]}.md`; + const url = `https://raw.githubusercontent.com/tresabhi/blitzkrieg/main/docs/${DOCS[subcommand as Subcommand]}.md`; const content = fetch(url).then((response) => response.text()); return content; diff --git a/src/core/discord/createLocalizedCommand.ts b/src/core/discord/createLocalizedCommand.ts new file mode 100644 index 000000000..89e6fa1c1 --- /dev/null +++ b/src/core/discord/createLocalizedCommand.ts @@ -0,0 +1,48 @@ +import { Locale, SlashCommandBuilder } from 'discord.js'; +import { translator } from '../localization/translator'; +import { localizationObject } from './localizationObject'; + +interface CreateLocalizedCommandSubcommand { + subcommand: string; +} + +export async function createLocalizedCommand( + command: string, + subcommands?: CreateLocalizedCommandSubcommand[], +) { + const { translate } = await translator(Locale.EnglishUS); + const slashCommand = new SlashCommandBuilder() + .setName(command) + .setNameLocalizations(await localizationObject(`bot.commands.${command}`)) + .setDescription(translate(`bot.commands.${command}.description`)) + .setDescriptionLocalizations( + await localizationObject(`bot.commands.${command}.description`), + ); + + if (subcommands) { + await Promise.all( + subcommands.map(async ({ subcommand }) => { + const subcommandNameLocalizations = await localizationObject( + `bot.commands.${command}.subcommands.${subcommand}`, + ); + const subcommandDescriptionLocalizations = await localizationObject( + `bot.commands.${command}.subcommands.${subcommand}.description`, + ); + + slashCommand.addSubcommand((option) => + option + .setName(subcommand) + .setNameLocalizations(subcommandNameLocalizations) + .setDescription( + translate( + `bot.commands.${command}.subcommands.${subcommand}.description`, + ), + ) + .setDescriptionLocalizations(subcommandDescriptionLocalizations), + ); + }), + ); + } + + return slashCommand; +} diff --git a/src/core/discord/localizationObject.ts b/src/core/discord/localizationObject.ts new file mode 100644 index 000000000..0ba60c83b --- /dev/null +++ b/src/core/discord/localizationObject.ts @@ -0,0 +1,17 @@ +import { Locale } from 'discord.js'; +import { locales } from '../localization/strings'; +import { translator } from '../localization/translator'; + +export async function localizationObject(path: string) { + return ( + await Promise.all(locales.map(async (locale) => await translator(locale))) + ).reduce>>( + (localizations, { locale, translate }) => { + return { + ...localizations, + [locale]: translate(path), + }; + }, + {}, + ); +} diff --git a/src/core/localization/strings.ts b/src/core/localization/strings.ts index eb479c46b..c72f48624 100644 --- a/src/core/localization/strings.ts +++ b/src/core/localization/strings.ts @@ -1,6 +1,6 @@ import { Locale } from 'discord.js'; -const LOCALES: Locale[number][] = ['en-US', 'es-ES']; +export const locales: Locale[] = [Locale.EnglishUS, Locale.SpanishES]; export type TranslationFragmentTree = { [key: string]: TranslationFragment; @@ -10,7 +10,7 @@ export type TranslationFragmentTree = { export type TranslationFragment = string | TranslationFragmentTree; export const translations = Promise.all( - LOCALES.map(async (locale) => ({ + locales.map(async (locale) => ({ locale, translations: await import(`../../lang/${locale}.json`), })), @@ -22,5 +22,5 @@ export const translations = Promise.all( [locale]: translations, }), {}, - ) as Record, + ) as Record, ); diff --git a/src/core/localization/translator.ts b/src/core/localization/translator.ts index 81629e9e7..8271f765f 100644 --- a/src/core/localization/translator.ts +++ b/src/core/localization/translator.ts @@ -1,7 +1,8 @@ import { Locale } from 'discord.js'; -import { translations } from './strings'; +import { locales, translations } from './strings'; -export async function translator(locale: Locale) { +export async function translator(localeRaw: Locale) { + const locale = locales.includes(localeRaw) ? localeRaw : Locale.EnglishUS; const strings = (await translations)[locale]; function translate(path: string) { @@ -45,5 +46,5 @@ export async function translator(locale: Locale) { .join(''); } - return { translate, t }; + return { locale, translate, t }; } From 8a76558f014291ac5940ddb4e4f30fbf03ff79a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 5 Mar 2024 10:51:14 -0600 Subject: [PATCH 20/90] feat: period subcommands --- src/commands/about.ts | 4 +- src/commands/breakdown.tsx | 14 +-- src/commands/evolution.tsx | 20 ++-- src/core/discord/addPeriodSubCommands.ts | 111 +++++++++++++++---- src/core/discord/addPeriodicFilterOptions.ts | 3 +- src/core/discord/createLocalizedCommand.ts | 48 ++++---- src/core/discord/localizationObject.ts | 9 +- src/core/localization/strings.ts | 25 ++--- src/core/localization/translator.ts | 23 ++-- src/lang/en-US.json | 42 ++++++- 10 files changed, 199 insertions(+), 100 deletions(-) diff --git a/src/commands/about.ts b/src/commands/about.ts index d328d9107..8ecb65b2f 100644 --- a/src/commands/about.ts +++ b/src/commands/about.ts @@ -24,14 +24,14 @@ export const aboutCommand = new Promise(async (resolve) => { inProduction: true, inPublic: true, - command: await createLocalizedCommand('about', [ + command: createLocalizedCommand('about', [ { subcommand: 'commands' }, ...subcommands.map((subcommand) => ({ subcommand })), ]), async handler(interaction) { const subcommand = interaction.options.getSubcommand(); - const { t } = await translator(interaction.locale); + const { t } = translator(interaction.locale); if (subcommand === 'commands') { return t`bot.commands.about.subcommands.commands.body${( diff --git a/src/commands/breakdown.tsx b/src/commands/breakdown.tsx index f69921b73..c81ac1f6a 100644 --- a/src/commands/breakdown.tsx +++ b/src/commands/breakdown.tsx @@ -1,4 +1,3 @@ -import { SlashCommandBuilder } from 'discord.js'; import { chunk } from 'lodash'; import * as Breakdown from '../components/Breakdown'; import NoData, { NoDataType } from '../components/NoData'; @@ -21,6 +20,7 @@ import autocompleteTanks from '../core/discord/autocompleteTanks'; import autocompleteUsername from '../core/discord/autocompleteUsername'; import buttonPrimary from '../core/discord/buttonPrimary'; import commandToURL from '../core/discord/commandToURL'; +import { createLocalizedCommand } from '../core/discord/createLocalizedCommand'; import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams'; import { getFiltersFromButton } from '../core/discord/getFiltersFromButton'; import { getFiltersFromCommand } from '../core/discord/getFiltersFromCommand'; @@ -292,18 +292,14 @@ export async function renderBreakdown( export const breakdownCommand = new Promise( async (resolve) => { - const command = await addPeriodicFilterOptions( - new SlashCommandBuilder() - .setName('breakdown') - .setDescription("A period's breakdown by tanks played"), - (option) => option.addStringOption(addUsernameChoices), - ); - resolve({ inProduction: true, inPublic: true, - command, + command: await addPeriodicFilterOptions( + createLocalizedCommand('breakdown'), + (option) => option.addStringOption(addUsernameChoices), + ), async handler(interaction) { const player = await resolvePlayerFromCommand(interaction); diff --git a/src/commands/evolution.tsx b/src/commands/evolution.tsx index 5b8cbd213..9a6983402 100644 --- a/src/commands/evolution.tsx +++ b/src/commands/evolution.tsx @@ -13,7 +13,7 @@ import { emblemIdToURL } from '../core/blitzkrieg/emblemIdToURL'; import getTankHistories from '../core/blitzkrieg/getTankHistories'; import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton'; import getPlayerHistories from '../core/blitzstars/getPlayerHistories'; -import addPeriodSubCommands from '../core/discord/addPeriodSubCommands'; +import { addPeriodSubCommands } from '../core/discord/addPeriodSubCommands'; import addTankChoices from '../core/discord/addTankChoices'; import addUsernameChoices from '../core/discord/addUsernameChoices'; import autocompleteTanks from '../core/discord/autocompleteTanks'; @@ -127,16 +127,14 @@ export const evolutionCommand = new Promise( return tankGroup; }); - await Promise.all([ - addPeriodSubCommands(playerGroup!, (option) => - option.addStringOption(addUsernameChoices), - ), - addPeriodSubCommands(tankGroup!, (option) => - option - .addStringOption(addTankChoices) - .addStringOption(addUsernameChoices), - ), - ]); + addPeriodSubCommands(playerGroup!, (option) => + option.addStringOption(addUsernameChoices), + ); + addPeriodSubCommands(tankGroup!, (option) => + option + .addStringOption(addTankChoices) + .addStringOption(addUsernameChoices), + ); resolve({ inProduction: true, diff --git a/src/core/discord/addPeriodSubCommands.ts b/src/core/discord/addPeriodSubCommands.ts index fa900898a..5ad7d20b2 100644 --- a/src/core/discord/addPeriodSubCommands.ts +++ b/src/core/discord/addPeriodSubCommands.ts @@ -1,49 +1,120 @@ import { + Locale, SlashCommandBuilder, SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder, } from 'discord.js'; +import { translator } from '../localization/translator'; +import { localizationObject } from './localizationObject'; export type PeriodType = 'today' | '30' | '60' | '90' | 'career' | 'custom'; export type PeriodSize = 'today' | `${number}` | 'career'; -export default function addPeriodSubCommands( +export function addPeriodSubCommands( option: SlashCommandBuilder | SlashCommandSubcommandGroupBuilder, extra: ( option: SlashCommandSubcommandBuilder, ) => SlashCommandSubcommandBuilder = (option) => option, ) { + const { translate } = translator(Locale.EnglishUS); + const subcommandNameLocalizations = { + today: localizationObject('bot.common.subcommands.today'), + 30: localizationObject('bot.common.subcommands.30'), + 60: localizationObject('bot.common.subcommands.60'), + 90: localizationObject('bot.common.subcommands.90'), + career: localizationObject('bot.common.subcommands.career'), + custom: localizationObject('bot.common.subcommands.custom'), + }; + const subcommandDescriptionLocalizations = { + today: localizationObject('bot.common.subcommands.today.description'), + 30: localizationObject('bot.common.subcommands.30.description'), + 60: localizationObject('bot.common.subcommands.60.description'), + 90: localizationObject('bot.common.subcommands.90.description'), + career: localizationObject('bot.common.subcommands.career.description'), + custom: localizationObject('bot.common.subcommands.custom.description'), + }; + option .addSubcommand((option) => - extra(option.setName('today').setDescription('Today')), - ) - .addSubcommand((option) => - extra(option.setName('30').setDescription('30 days')), - ) - .addSubcommand((option) => - extra(option.setName('60').setDescription('60 days')), - ) - .addSubcommand((option) => - extra(option.setName('90').setDescription('90 days')), - ) - .addSubcommand((option) => - extra(option.setName('career').setDescription('Career')), + extra( + option + .setName(translate('bot.common.subcommands.today')) + .setNameLocalizations(subcommandNameLocalizations.today) + .setDescription(translate('bot.common.subcommands.today.description')) + .setDescriptionLocalizations( + subcommandDescriptionLocalizations.today, + ), + ), ) .addSubcommand((option) => extra( option - .setName('custom') - .setDescription('Custom range') + .setName(translate('bot.common.subcommands.30')) + .setNameLocalizations(subcommandNameLocalizations[30]) + .setDescription(translate('bot.common.subcommands.30.description')) + .setDescriptionLocalizations(subcommandDescriptionLocalizations[30]), + ), + ) + .addSubcommand((option) => + extra( + option + .setName(translate('bot.common.subcommands.60')) + .setNameLocalizations(subcommandNameLocalizations[60]) + .setDescription(translate('bot.common.subcommands.60.description')) + .setDescriptionLocalizations(subcommandDescriptionLocalizations[60]), + ), + ) + .addSubcommand((option) => + extra( + option + .setName(translate('bot.common.subcommands.90')) + .setNameLocalizations(subcommandNameLocalizations[90]) + .setDescription(translate('bot.common.subcommands.90.description')) + .setDescriptionLocalizations(subcommandDescriptionLocalizations[90]), + ), + ) + .addSubcommand((option) => + extra( + option + .setName(translate('bot.common.subcommands.career')) + .setNameLocalizations(subcommandNameLocalizations.career) + .setDescription( + translate('bot.common.subcommands.career.description'), + ) + .setDescriptionLocalizations( + subcommandDescriptionLocalizations.career, + ), + ), + ) + .addSubcommand((option) => + extra( + option + .setName(translate('bot.common.subcommands.custom')) + .setNameLocalizations(subcommandNameLocalizations.custom) + .setDescription( + translate('bot.common.subcommands.custom.description'), + ) + .setDescriptionLocalizations( + subcommandDescriptionLocalizations.custom, + ) .addIntegerOption((option) => option - .setName('start') - .setDescription('Start in number of days ago from today') + .setName(translate('bot.common.subcommands.custom.options.start')) + .setDescription( + translate( + 'bot.common.subcommands.custom.options.start.description', + ), + ) .setRequired(true), ) .addIntegerOption((option) => option - .setName('end') - .setDescription('End in number of days') + .setName(translate('bot.common.subcommands.custom.options.end')) + .setDescription( + translate( + 'bot.common.subcommands.custom.options.end.description', + ), + ) .setRequired(true), ), ), diff --git a/src/core/discord/addPeriodicFilterOptions.ts b/src/core/discord/addPeriodicFilterOptions.ts index 7840258d3..48188b0cb 100644 --- a/src/core/discord/addPeriodicFilterOptions.ts +++ b/src/core/discord/addPeriodicFilterOptions.ts @@ -5,7 +5,7 @@ import { } from 'discord.js'; import { encyclopediaInfo } from '../blitz/encyclopediaInfo'; import { addFilterOptions } from './addFilterOptions'; -import addPeriodSubCommands from './addPeriodSubCommands'; +import { addPeriodSubCommands } from './addPeriodSubCommands'; export default async function addPeriodicFilterOptions< OptionType extends SlashCommandBuilder | SlashCommandSubcommandGroupBuilder, @@ -16,7 +16,6 @@ export default async function addPeriodicFilterOptions< ) => SlashCommandSubcommandBuilder = (option) => option, ): Promise { const awaitedEncyclopediaInfo = await encyclopediaInfo; - addPeriodSubCommands(option, (option) => { return addFilterOptions(extra(option), awaitedEncyclopediaInfo); }); diff --git a/src/core/discord/createLocalizedCommand.ts b/src/core/discord/createLocalizedCommand.ts index 89e6fa1c1..1efda3cf8 100644 --- a/src/core/discord/createLocalizedCommand.ts +++ b/src/core/discord/createLocalizedCommand.ts @@ -6,42 +6,40 @@ interface CreateLocalizedCommandSubcommand { subcommand: string; } -export async function createLocalizedCommand( +export function createLocalizedCommand( command: string, subcommands?: CreateLocalizedCommandSubcommand[], ) { - const { translate } = await translator(Locale.EnglishUS); + const { translate } = translator(Locale.EnglishUS); const slashCommand = new SlashCommandBuilder() .setName(command) - .setNameLocalizations(await localizationObject(`bot.commands.${command}`)) + .setNameLocalizations(localizationObject(`bot.commands.${command}`)) .setDescription(translate(`bot.commands.${command}.description`)) .setDescriptionLocalizations( - await localizationObject(`bot.commands.${command}.description`), + localizationObject(`bot.commands.${command}.description`), ); if (subcommands) { - await Promise.all( - subcommands.map(async ({ subcommand }) => { - const subcommandNameLocalizations = await localizationObject( - `bot.commands.${command}.subcommands.${subcommand}`, - ); - const subcommandDescriptionLocalizations = await localizationObject( - `bot.commands.${command}.subcommands.${subcommand}.description`, - ); + subcommands.map(({ subcommand }) => { + const subcommandNameLocalizations = localizationObject( + `bot.commands.${command}.subcommands.${subcommand}`, + ); + const subcommandDescriptionLocalizations = localizationObject( + `bot.commands.${command}.subcommands.${subcommand}.description`, + ); - slashCommand.addSubcommand((option) => - option - .setName(subcommand) - .setNameLocalizations(subcommandNameLocalizations) - .setDescription( - translate( - `bot.commands.${command}.subcommands.${subcommand}.description`, - ), - ) - .setDescriptionLocalizations(subcommandDescriptionLocalizations), - ); - }), - ); + slashCommand.addSubcommand((option) => + option + .setName(subcommand) + .setNameLocalizations(subcommandNameLocalizations) + .setDescription( + translate( + `bot.commands.${command}.subcommands.${subcommand}.description`, + ), + ) + .setDescriptionLocalizations(subcommandDescriptionLocalizations), + ); + }); } return slashCommand; diff --git a/src/core/discord/localizationObject.ts b/src/core/discord/localizationObject.ts index 0ba60c83b..28f2f0e90 100644 --- a/src/core/discord/localizationObject.ts +++ b/src/core/discord/localizationObject.ts @@ -2,11 +2,10 @@ import { Locale } from 'discord.js'; import { locales } from '../localization/strings'; import { translator } from '../localization/translator'; -export async function localizationObject(path: string) { - return ( - await Promise.all(locales.map(async (locale) => await translator(locale))) - ).reduce>>( - (localizations, { locale, translate }) => { +export function localizationObject(path: string) { + return locales.reduce>>( + (localizations, locale) => { + const { translate } = translator(locale); return { ...localizations, [locale]: translate(path), diff --git a/src/core/localization/strings.ts b/src/core/localization/strings.ts index c72f48624..7ce45396b 100644 --- a/src/core/localization/strings.ts +++ b/src/core/localization/strings.ts @@ -9,18 +9,15 @@ export type TranslationFragmentTree = { }; export type TranslationFragment = string | TranslationFragmentTree; -export const translations = Promise.all( - locales.map(async (locale) => ({ +export const translations = locales + .map((locale) => ({ locale, - translations: await import(`../../lang/${locale}.json`), - })), -).then( - (translations) => - translations.reduce( - (table, { locale, translations }) => ({ - ...table, - [locale]: translations, - }), - {}, - ) as Record, -); + translations: require(`../../lang/${locale}.json`), + })) + .reduce( + (table, { locale, translations }) => ({ + ...table, + [locale]: translations, + }), + {}, + ) as Record; diff --git a/src/core/localization/translator.ts b/src/core/localization/translator.ts index 8271f765f..0b55fb879 100644 --- a/src/core/localization/translator.ts +++ b/src/core/localization/translator.ts @@ -1,26 +1,33 @@ import { Locale } from 'discord.js'; import { locales, translations } from './strings'; -export async function translator(localeRaw: Locale) { +export function translator(localeRaw: Locale) { const locale = locales.includes(localeRaw) ? localeRaw : Locale.EnglishUS; - const strings = (await translations)[locale]; + const strings = translations[locale]; - function translate(path: string) { + function translate(path: string): string { const pathArray = path.split('.'); let fragment = strings; for (const pathItem of pathArray) { if (typeof fragment === 'string') { throw new SyntaxError( - `Attempted to access sub-string ("${pathItem}" in "${path}") from a string instead of a tree`, + `Attempted to access string with key "${pathItem}" in "${path}" from within a string instead of an object in locale "${locale}"`, ); } else { fragment = fragment[pathItem]; if (typeof fragment === 'undefined') { - throw new Error( - `Undefined translation for key "${path}" (in "${path}") in language "${locale}"`, - ); + if (locale === Locale.EnglishUS) { + throw new Error( + `Undefined translation at "${pathItem}" in "${path}" for locale "${locale}"`, + ); + } else { + console.warn( + `Undefined translation at "${pathItem}" in "${path}" for locale "${locale}"; falling back to en-US`, + ); + return translator(Locale.EnglishUS).translate(path); + } } } } @@ -31,7 +38,7 @@ export async function translator(localeRaw: Locale) { return fragment.$!; } else { throw new Error( - `Unresolved tree ending for "${path}" in language "${locale}"`, + `Unresolved tree ending for "${path}" in locale "${locale}"`, ); } } diff --git a/src/lang/en-US.json b/src/lang/en-US.json index 4a704e281..e33bf1bff 100644 --- a/src/lang/en-US.json +++ b/src/lang/en-US.json @@ -13,6 +13,44 @@ "great": "Great", "unicum": "Unicum", "super_unicum": "Super unicum" + }, + + "subcommands": { + "today": { + "$": "today", + "description": "Today" + }, + "30": { + "$": "30", + "description": "30 days" + }, + "60": { + "$": "60", + "description": "60 days" + }, + "90": { + "$": "90", + "description": "90 days" + }, + "career": { + "$": "career", + "description": "Career" + }, + "custom": { + "$": "custom", + "description": "Custom range", + + "options": { + "start": { + "$": "start", + "description": "Start in number of days ago from today" + }, + "end": { + "$": "end", + "description": "End in number of days ago from today" + } + } + } } }, @@ -58,10 +96,6 @@ "battles": "Battles", "winrate": "Winrate", "damage": "Damage" - }, - - "buttons": { - "refresh": "Refresh" } } } From 227750efad9c6b525fd363a1cb80839e6304c3c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 5 Mar 2024 10:51:17 -0600 Subject: [PATCH 21/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 67 +++++++++++++++++++++++++++++++++------------ 1 file changed, 50 insertions(+), 17 deletions(-) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index 766e933a7..2a344a3fd 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -5,20 +5,56 @@ "$": "WN8", "very_bad": "Muy malo", "bad": "Malo", - "below_average": "Inferior a la media", + "below_average": "Inferior al promedio", "average": "Promedio", - "above_average": "Superior a la media", + "above_average": "Superior al promedio", "good": "Bueno", "very_good": "Muy bueno", - "great": "Estupendo", - "unicum": "Jugador único", - "super_unicum": "Superjugador único" + "great": "Genial", + "unicum": "Unicum", + "super_unicum": "Super unicum" + }, + "subcommands": { + "today": { + "$": "today", + "description": "Today" + }, + "30": { + "$": "30", + "description": "30 days" + }, + "60": { + "$": "60", + "description": "60 days" + }, + "90": { + "$": "90", + "description": "90 days" + }, + "career": { + "$": "career", + "description": "Career" + }, + "custom": { + "$": "custom", + "description": "Custom range", + "options": { + "start": { + "$": "start", + "description": "Start in number of days ago from today" + }, + "end": { + "$": "end", + "description": "End in number of days ago from today" + } + } + } } }, "commands": { "about": { - "$": "acerca-de", - "description": "Toda la información que necesitas acerca del bot", + "$": "sobre", + "description": "Toda la información que necesitas sobre el bot", "subcommands": { "commands": { "$": "comandos", @@ -31,33 +67,30 @@ }, "introduction": { "$": "introducción", - "description": "Acerca del bot" + "description": "Sobre el bot" }, "invite": { "$": "invitar", - "description": "Cómo invitar el bot" + "description": "Cómo invitar al bot" }, "timezones": { "$": "zonas-horarias", - "description": "Cómo Blitzkrieg infiere y usa tu zona horaria" + "description": "Cómo Blitzkrieg infiere y utiliza tu zona horaria" }, "creators": { "$": "creadores", - "description": "Las personas que han trabajado en el bot" + "description": "Personas que han trabajado en el bot" } } }, "breakdown": { - "$": "desglose", - "description": "Desglose de un período por tanques jugados", + "$": "resumen", + "description": "Resumen de un período por tanques jugados", "body": { "total": "Total", "battles": "Batallas", - "winrate": "Ratio de victorias", + "winrate": "Tasa de victorias", "damage": "Daño" - }, - "buttons": { - "refresh": "Actualizar" } } } From ceb4ed52c090197de2355f341faf2d351de2afc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 5 Mar 2024 10:58:35 -0600 Subject: [PATCH 22/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index 2a344a3fd..c72fa3ae5 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -92,6 +92,16 @@ "winrate": "Tasa de victorias", "damage": "Daño" } + }, + "debug": { + "$": "debug", + "description": "Debug information about the bot", + "body": { + "title": "# Debug\n\n", + "version": "Version", + "shard": "Shard", + "uptime": "Uptime" + } } } }, From e355bd10b146a8c772f3a8e440e176d784a1e190 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 5 Mar 2024 10:58:41 -0600 Subject: [PATCH 23/90] feat: debug command --- src/commands/debug.ts | 23 +++++++++++------------ src/lang/en-US.json | 12 ++++++++++++ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/src/commands/debug.ts b/src/commands/debug.ts index ef768d4d6..fbea9e25a 100644 --- a/src/commands/debug.ts +++ b/src/commands/debug.ts @@ -1,6 +1,7 @@ -import { SlashCommandBuilder } from 'discord.js'; import packageJSON from '../../package.json' assert { type: 'json' }; import { client } from '../core/discord/client'; +import { createLocalizedCommand } from '../core/discord/createLocalizedCommand'; +import { translator } from '../core/localization/translator'; import { CommandRegistry } from '../events/interactionCreate'; const executionStart = new Date().getTime(); @@ -9,28 +10,26 @@ export const debugCommand: CommandRegistry = { inProduction: true, inPublic: true, - command: new SlashCommandBuilder() - .setName('debug') - .setDescription('Debug information about the bot'), + command: createLocalizedCommand('debug'), - async handler() { + async handler(interaction) { + const { t } = translator(interaction.locale); const currentTime = new Date().getTime(); const uptime = currentTime - executionStart; const list = [ - ['Version', packageJSON.version], - ['Shard', client.shard?.ids[0] ?? 'default'], + [t`bot.commands.debug.body.version`, packageJSON.version], + [t`bot.commands.debug.body.shard`, client.shard?.ids[0] ?? 'default'], [ - 'Uptime', + t`bot.commands.debug.body.uptime`, `${Math.floor((uptime / 1000 / 60 / 60) % 24)}h ${Math.floor( (uptime / 1000 / 60) % 60, )}m ${Math.floor((uptime / 1000) % 60)}s ${Math.floor( uptime % 1000, )}ms`, ], - ] + ]; + return t`bot.commands.debug.body.title${list .map(([key, value]) => `- ${key}: ${value}`) - .join('\n'); - - return `# Debug\n\n${list}`; + .join('\n')}`; }, }; diff --git a/src/lang/en-US.json b/src/lang/en-US.json index e33bf1bff..daabda4a8 100644 --- a/src/lang/en-US.json +++ b/src/lang/en-US.json @@ -97,6 +97,18 @@ "winrate": "Winrate", "damage": "Damage" } + }, + + "debug": { + "$": "debug", + "description": "Debug information about the bot", + + "body": { + "title": "# Debug\n\n", + "version": "Version", + "shard": "Shard", + "uptime": "Uptime" + } } } }, From 6dcef6b199679d4feba5976ea781ccfffb2e77e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 5 Mar 2024 11:00:53 -0600 Subject: [PATCH 24/90] feat: default shard localew --- src/commands/debug.ts | 5 ++++- src/lang/en-US.json | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/commands/debug.ts b/src/commands/debug.ts index fbea9e25a..f0a10993d 100644 --- a/src/commands/debug.ts +++ b/src/commands/debug.ts @@ -18,7 +18,10 @@ export const debugCommand: CommandRegistry = { const uptime = currentTime - executionStart; const list = [ [t`bot.commands.debug.body.version`, packageJSON.version], - [t`bot.commands.debug.body.shard`, client.shard?.ids[0] ?? 'default'], + [ + t`bot.commands.debug.body.shard`, + client.shard?.ids[0] ?? t`bot.commands.debug.body.shard.default`, + ], [ t`bot.commands.debug.body.uptime`, `${Math.floor((uptime / 1000 / 60 / 60) % 24)}h ${Math.floor( diff --git a/src/lang/en-US.json b/src/lang/en-US.json index daabda4a8..2fa78623d 100644 --- a/src/lang/en-US.json +++ b/src/lang/en-US.json @@ -106,7 +106,10 @@ "body": { "title": "# Debug\n\n", "version": "Version", - "shard": "Shard", + "shard": { + "$": "Shard", + "default": "default" + }, "uptime": "Uptime" } } From 9232a95e00a306f04a10de1724525982e94da15b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Tue, 5 Mar 2024 12:32:21 -0600 Subject: [PATCH 25/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index c72fa3ae5..70bd479b1 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -16,36 +16,36 @@ }, "subcommands": { "today": { - "$": "today", - "description": "Today" + "$": "hoy", + "description": "Hoy" }, "30": { "$": "30", - "description": "30 days" + "description": "30 días" }, "60": { "$": "60", - "description": "60 days" + "description": "60 días" }, "90": { "$": "90", - "description": "90 days" + "description": "90 días" }, "career": { - "$": "career", - "description": "Career" + "$": "carrera", + "description": "Carrera" }, "custom": { - "$": "custom", - "description": "Custom range", + "$": "personalizado", + "description": "Rango personalizado", "options": { "start": { - "$": "start", - "description": "Start in number of days ago from today" + "$": "comienzo", + "description": "Comenzar en cantidad de días pasados desde hoy" }, "end": { - "$": "end", - "description": "End in number of days ago from today" + "$": "fin", + "description": "Terminar en cantidad de días pasados desde hoy" } } } @@ -94,13 +94,16 @@ } }, "debug": { - "$": "debug", - "description": "Debug information about the bot", + "$": "depurar", + "description": "Muestra más información sobre el bot", "body": { - "title": "# Debug\n\n", - "version": "Version", - "shard": "Shard", - "uptime": "Uptime" + "title": "# Depuración\n\n", + "version": "Versión", + "shard": { + "$": "Partición", + "default": "predeterminado" + }, + "uptime": "Tiempo en línea" } } } From f6058015551d19dbd4b0652be7668ddb2f58b9ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 6 Mar 2024 09:52:09 -0600 Subject: [PATCH 26/90] feat: better titlebar --- src/commands/breakdown.tsx | 14 ++-- src/commands/evolution.tsx | 8 +- src/commands/fullStats.tsx | 8 +- src/commands/inactive.tsx | 8 +- src/commands/link.tsx | 6 +- src/commands/ownedTanks.tsx | 8 +- src/commands/ratings.tsx | 8 +- src/commands/stats.tsx | 8 +- .../{Wrapper.tsx => CommandWrapper.tsx} | 12 +-- src/components/TitleBar.tsx | 73 +++++++------------ 10 files changed, 63 insertions(+), 90 deletions(-) rename src/components/{Wrapper.tsx => CommandWrapper.tsx} (60%) diff --git a/src/commands/breakdown.tsx b/src/commands/breakdown.tsx index c81ac1f6a..6e7c3a90e 100644 --- a/src/commands/breakdown.tsx +++ b/src/commands/breakdown.tsx @@ -1,8 +1,8 @@ import { chunk } from 'lodash'; import * as Breakdown from '../components/Breakdown'; +import CommandWrapper from '../components/CommandWrapper'; import NoData, { NoDataType } from '../components/NoData'; import TitleBar from '../components/TitleBar'; -import Wrapper from '../components/Wrapper'; import { AllStats, getAccountInfo } from '../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo'; import getTankStats from '../core/blitz/getTankStats'; @@ -254,10 +254,10 @@ export async function renderBreakdown( if (filteredOrder.length > 0) { return pages .map((page, index) => ( - + {index === 0 && ( {page} - + )) .reverse(); } else { return [ - + - , + , ]; } } diff --git a/src/commands/evolution.tsx b/src/commands/evolution.tsx index 9a6983402..e2bb7bd99 100644 --- a/src/commands/evolution.tsx +++ b/src/commands/evolution.tsx @@ -2,10 +2,10 @@ import { SlashCommandBuilder, SlashCommandSubcommandGroupBuilder, } from 'discord.js'; +import CommandWrapper from '../components/CommandWrapper'; import * as Graph from '../components/Graph'; import NoData, { NoDataType } from '../components/NoData'; import TitleBar from '../components/TitleBar'; -import Wrapper from '../components/Wrapper'; import { getAccountInfo } from '../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo'; import resolveTankId from '../core/blitz/resolveTankId'; @@ -74,8 +74,8 @@ async function render( const minX = Math.min(...xs); return ( - - + + {plot.length > 0 && ( } - + ); } diff --git a/src/commands/fullStats.tsx b/src/commands/fullStats.tsx index 01ca868a8..fce3f7205 100644 --- a/src/commands/fullStats.tsx +++ b/src/commands/fullStats.tsx @@ -1,9 +1,9 @@ import { SlashCommandBuilder } from 'discord.js'; +import CommandWrapper from '../components/CommandWrapper'; import GenericAllStats from '../components/GenericAllStats'; import NoData, { NoDataType } from '../components/NoData'; import TierWeights from '../components/TierWeights'; import TitleBar from '../components/TitleBar'; -import Wrapper from '../components/Wrapper'; import { getAccountInfo } from '../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo'; import { emblemIdToURL } from '../core/blitzkrieg/emblemIdToURL'; @@ -48,9 +48,9 @@ async function render( const tierWeights = await getTierWeights(diffedTankStats.diff, filteredOrder); return ( - + @@ -60,7 +60,7 @@ async function render( {stats.battles > 0 && ( )} - + ); } diff --git a/src/commands/inactive.tsx b/src/commands/inactive.tsx index aabc824aa..762ff3515 100644 --- a/src/commands/inactive.tsx +++ b/src/commands/inactive.tsx @@ -1,8 +1,8 @@ import { SlashCommandBuilder } from 'discord.js'; +import CommandWrapper from '../components/CommandWrapper'; import GenericStats from '../components/GenericStats'; import NoData, { NoDataType } from '../components/NoData'; import TitleBar from '../components/TitleBar'; -import Wrapper from '../components/Wrapper'; import { getAccountInfo } from '../core/blitz/getAccountInfo'; import { getClanInfo } from '../core/blitz/getClanInfo'; import addClanChoices from '../core/discord/addClanChoices'; @@ -52,16 +52,16 @@ export const inactiveCommand: CommandRegistry = { const hasInactiveMembers = inactive.length > 0; return ( - + {!hasInactiveMembers && } {hasInactiveMembers && } - + ); }, diff --git a/src/commands/link.tsx b/src/commands/link.tsx index 46110c6ed..a7a7ea8c6 100644 --- a/src/commands/link.tsx +++ b/src/commands/link.tsx @@ -3,7 +3,7 @@ import { GuildMemberRoleManager, SlashCommandBuilder } from 'discord.js'; import markdownEscape from 'markdown-escape'; import discord from '../../discord.json' assert { type: 'json' }; import { Glow } from '../components/AllStatsOverview/components/WN8Display/components/Glow'; -import Wrapper from '../components/Wrapper'; +import CommandWrapper from '../components/CommandWrapper'; import { REGION_NAMES } from '../constants/regions'; import { getAccountInfo } from '../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo'; @@ -50,7 +50,7 @@ export const verifyCommand: CommandRegistry = { } return [ - +
-
, + , embedInfo( 'Incorrect account?', diff --git a/src/commands/ownedTanks.tsx b/src/commands/ownedTanks.tsx index 8842c9ff0..0e61e4f31 100644 --- a/src/commands/ownedTanks.tsx +++ b/src/commands/ownedTanks.tsx @@ -1,8 +1,8 @@ import { SlashCommandBuilder } from 'discord.js'; +import CommandWrapper from '../components/CommandWrapper'; import NoData, { NoDataType } from '../components/NoData'; import * as Tanks from '../components/Tanks'; import TitleBar from '../components/TitleBar'; -import Wrapper from '../components/Wrapper'; import { encyclopediaInfo } from '../core/blitz/encyclopediaInfo'; import { getAccountInfo } from '../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo'; @@ -61,9 +61,9 @@ export const ownedTanksCommand: CommandRegistry = { nations.sort(); return ( - + + ); }, diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx index 479583f0d..91dc7d817 100644 --- a/src/commands/ratings.tsx +++ b/src/commands/ratings.tsx @@ -6,9 +6,9 @@ import { import { range } from 'lodash'; import markdownEscape from 'markdown-escape'; import { BlitzkriegRatingsLeaderboard } from '../../scripts/buildRatingsLeaderboard'; +import CommandWrapper from '../components/CommandWrapper'; import * as Leaderboard from '../components/Leaderboard'; import TitleBar from '../components/TitleBar'; -import Wrapper from '../components/Wrapper'; import { LEAGUES } from '../constants/leagues'; import { FIRST_ARCHIVED_RATINGS_SEASON } from '../constants/ratings'; import { REGION_NAMES_SHORT, Region } from '../constants/regions'; @@ -423,9 +423,9 @@ export const ratingsCommand = new Promise( }); return ( - + @@ -445,7 +445,7 @@ export const ratingsCommand = new Promise( )} )} - + ); }, diff --git a/src/commands/stats.tsx b/src/commands/stats.tsx index bf6146259..27ca249c3 100644 --- a/src/commands/stats.tsx +++ b/src/commands/stats.tsx @@ -1,8 +1,8 @@ import { SlashCommandBuilder } from 'discord.js'; import AllStatsOverview from '../components/AllStatsOverview'; +import CommandWrapper from '../components/CommandWrapper'; import NoData, { NoDataType } from '../components/NoData'; import TitleBar from '../components/TitleBar'; -import Wrapper from '../components/Wrapper'; import { getAccountInfo } from '../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo'; import { emblemIdToURL } from '../core/blitzkrieg/emblemIdToURL'; @@ -42,9 +42,9 @@ async function render( const filterDescriptions = await filtersToDescription(filters); return ( - + @@ -53,7 +53,7 @@ async function render( {stats.battles > 0 && ( )} - + ); } diff --git a/src/components/Wrapper.tsx b/src/components/CommandWrapper.tsx similarity index 60% rename from src/components/Wrapper.tsx rename to src/components/CommandWrapper.tsx index 0af54719e..bf9b99f49 100644 --- a/src/components/Wrapper.tsx +++ b/src/components/CommandWrapper.tsx @@ -1,26 +1,22 @@ import { ReactNode } from 'react'; import { theme } from '../stitches.config'; -export interface WrapperProps { +interface CommandWrapperProps { children: ReactNode; - naked?: boolean; fat?: boolean; } -export default function Wrapper({ children, naked, fat }: WrapperProps) { +export default function CommandWrapper({ children, fat }: CommandWrapperProps) { return (
{children} diff --git a/src/components/TitleBar.tsx b/src/components/TitleBar.tsx index 7449da129..ef5b284cc 100644 --- a/src/components/TitleBar.tsx +++ b/src/components/TitleBar.tsx @@ -1,75 +1,52 @@ import { theme } from '../stitches.config'; export interface TitleBarProps { - name: string; + title: string; image?: string; description?: string; } -export default function TitleBar({ name, image, description }: TitleBarProps) { +export default function TitleBar({ title, image, description }: TitleBarProps) { return (
-
+
{image && ( )} -
-
- - {name} - -
- -
- - {description} - -
-
+ {title} +
+ + + {description} +
); } From 267f23cad89bb4adf2f2ac5cc19b87ceb64f22d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 6 Mar 2024 09:53:28 -0600 Subject: [PATCH 27/90] fix: use dot in /link --- src/commands/link.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/link.tsx b/src/commands/link.tsx index a7a7ea8c6..27e744b34 100644 --- a/src/commands/link.tsx +++ b/src/commands/link.tsx @@ -186,7 +186,7 @@ export const verifyCommand: CommandRegistry = { }} > {clanAccountInfo?.clan - ? `[${clanAccountInfo?.clan?.tag}] - ` + ? `[${clanAccountInfo?.clan?.tag}] • ` : ''} {REGION_NAMES[region]} From adad668157a7a38e8a3ea236a4c0b8c04a84b9b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 6 Mar 2024 15:34:10 -0600 Subject: [PATCH 28/90] feat: some period translations --- src/commands/breakdown.tsx | 4 +- src/commands/debug.ts | 58 +- src/commands/eligible.ts | 363 +++++------ src/commands/evolution.tsx | 60 +- src/commands/fullStats.tsx | 4 +- src/commands/inactive.tsx | 103 ++-- src/commands/link.tsx | 292 ++++----- src/commands/ownedTanks.tsx | 194 +++--- src/commands/permissions.ts | 74 +-- src/commands/ping.ts | 52 +- src/commands/playerAchievements.ts | 99 +-- src/commands/playerInfo.ts | 80 +-- src/commands/ratings.tsx | 611 +++++++++---------- src/commands/searchClans.ts | 68 ++- src/commands/searchPlayers.ts | 80 +-- src/commands/searchTanks.ts | 62 +- src/commands/stats.tsx | 80 ++- src/commands/today.ts | 90 ++- src/core/discord/createLocalizedCommand.ts | 80 ++- src/core/discord/resolvePeriodFromButton.ts | 2 +- src/core/discord/resolvePeriodFromCommand.ts | 15 +- src/core/localization/translator.ts | 19 +- src/events/interactionCreate/index.ts | 11 +- src/lang/en-US.json | 24 + 24 files changed, 1309 insertions(+), 1216 deletions(-) diff --git a/src/commands/breakdown.tsx b/src/commands/breakdown.tsx index 6e7c3a90e..e635142b8 100644 --- a/src/commands/breakdown.tsx +++ b/src/commands/breakdown.tsx @@ -35,7 +35,7 @@ import resolvePlayerFromCommand, { import calculateWN8 from '../core/statistics/calculateWN8'; import { StatFilters, filterStats } from '../core/statistics/filterStats'; import getWN8Percentile from '../core/statistics/getWN8Percentile'; -import { CommandRegistryPromisable } from '../events/interactionCreate'; +import { CommandRegistry } from '../events/interactionCreate'; const ROWS_PER_PAGE = 8; const MAX_PAGES = 9; @@ -290,7 +290,7 @@ export async function renderBreakdown( } } -export const breakdownCommand = new Promise( +export const breakdownCommand = new Promise( async (resolve) => { resolve({ inProduction: true, diff --git a/src/commands/debug.ts b/src/commands/debug.ts index f0a10993d..57452999b 100644 --- a/src/commands/debug.ts +++ b/src/commands/debug.ts @@ -6,33 +6,35 @@ import { CommandRegistry } from '../events/interactionCreate'; const executionStart = new Date().getTime(); -export const debugCommand: CommandRegistry = { - inProduction: true, - inPublic: true, +export const debugCommand = new Promise((resolve) => { + resolve({ + inProduction: true, + inPublic: true, - command: createLocalizedCommand('debug'), + command: createLocalizedCommand('debug'), - async handler(interaction) { - const { t } = translator(interaction.locale); - const currentTime = new Date().getTime(); - const uptime = currentTime - executionStart; - const list = [ - [t`bot.commands.debug.body.version`, packageJSON.version], - [ - t`bot.commands.debug.body.shard`, - client.shard?.ids[0] ?? t`bot.commands.debug.body.shard.default`, - ], - [ - t`bot.commands.debug.body.uptime`, - `${Math.floor((uptime / 1000 / 60 / 60) % 24)}h ${Math.floor( - (uptime / 1000 / 60) % 60, - )}m ${Math.floor((uptime / 1000) % 60)}s ${Math.floor( - uptime % 1000, - )}ms`, - ], - ]; - return t`bot.commands.debug.body.title${list - .map(([key, value]) => `- ${key}: ${value}`) - .join('\n')}`; - }, -}; + async handler(interaction) { + const { t } = translator(interaction.locale); + const currentTime = new Date().getTime(); + const uptime = currentTime - executionStart; + const list = [ + [t`bot.commands.debug.body.version`, packageJSON.version], + [ + t`bot.commands.debug.body.shard`, + client.shard?.ids[0] ?? t`bot.commands.debug.body.shard.default`, + ], + [ + t`bot.commands.debug.body.uptime`, + `${Math.floor((uptime / 1000 / 60 / 60) % 24)}h ${Math.floor( + (uptime / 1000 / 60) % 60, + )}m ${Math.floor((uptime / 1000) % 60)}s ${Math.floor( + uptime % 1000, + )}ms`, + ], + ]; + return t`bot.commands.debug.body.title${list + .map(([key, value]) => `- ${key}: ${value}`) + .join('\n')}`; + }, + }); +}); diff --git a/src/commands/eligible.ts b/src/commands/eligible.ts index 8225885e6..33fcc5812 100644 --- a/src/commands/eligible.ts +++ b/src/commands/eligible.ts @@ -1,198 +1,205 @@ -import { SlashCommandBuilder } from 'discord.js'; -import { getAccountInfo } from '../core/blitz/getAccountInfo'; -import getPeriodNow from '../core/blitzkrieg/getPeriodNow'; -import getTimeDaysAgo from '../core/blitzkrieg/getTimeDaysAgo'; -import { tankDefinitions } from '../core/blitzkrieg/tankDefinitions'; -import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod'; -import { tankAverages } from '../core/blitzstars/tankAverages'; -import addUsernameChoices from '../core/discord/addUsernameChoices'; -import embedNegative from '../core/discord/embedNegative'; -import embedPositive from '../core/discord/embedPositive'; -import markdownTable, { TableInput } from '../core/discord/markdownTable'; -import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; -import calculateWN8 from '../core/statistics/calculateWN8'; -import sumStats from '../core/statistics/sumStats'; -import { CommandRegistry } from '../events/interactionCreate'; -import { PossiblyPromise } from '../types/possiblyPromise'; +// _____ _____ _ _ _ _ _____ ____ __ __ ______ ____ _____ _ __ +// |_ _| / ____| | | | /\ | | | | / ____/ __ \| \/ | ____| | _ \ /\ / ____| |/ / +// | | | (___ | |__| | / \ | | | | | | | | | | \ / | |__ | |_) | / \ | | | ' / +// | | \___ \| __ | / /\ \ | | | | | | | | | | |\/| | __| | _ < / /\ \| | | < +// _| |_ ____) | | | |/ ____ \| |____| |____ | |___| |__| | | | | |____ | |_) / ____ \ |____| . \ +// |_____| |_____/|_| |_/_/ \_\______|______| \_____\____/|_| |_|______| |____/_/ \_\_____|_|\_\ -export type SkilledClan = 'SKLLD' | 'SMRI'; +// import { SlashCommandBuilder } from 'discord.js'; +// import { getAccountInfo } from '../core/blitz/getAccountInfo'; +// import getPeriodNow from '../core/blitzkrieg/getPeriodNow'; +// import getTimeDaysAgo from '../core/blitzkrieg/getTimeDaysAgo'; +// import { tankDefinitions } from '../core/blitzkrieg/tankDefinitions'; +// import getStatsInPeriod from '../core/blitzstars/getStatsInPeriod'; +// import { tankAverages } from '../core/blitzstars/tankAverages'; +// import addUsernameChoices from '../core/discord/addUsernameChoices'; +// import embedNegative from '../core/discord/embedNegative'; +// import embedPositive from '../core/discord/embedPositive'; +// import markdownTable, { TableInput } from '../core/discord/markdownTable'; +// import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; +// import calculateWN8 from '../core/statistics/calculateWN8'; +// import sumStats from '../core/statistics/sumStats'; +// import { CommandRegistry } from '../events/interactionCreate'; +// import { PossiblyPromise } from '../types/possiblyPromise'; -export const SKILLED_CLANS: Record = { - SKLLD: 'Skilled', - SMRI: 'Samurai', -}; +// export type SkilledClan = 'SKLLD' | 'SMRI'; -export const eligibleCommand: CommandRegistry = { - inProduction: true, - inPublic: false, +// export const SKILLED_CLANS: Record = { +// SKLLD: 'Skilled', +// SMRI: 'Samurai', +// }; - command: new SlashCommandBuilder() - .setName('eligible') - .setDescription('Checks eligibility for any Skilled clan') - .addStringOption((option) => - option - .setName('clan') - .setDescription('The clan to check') - .setChoices( - { - name: SKILLED_CLANS.SKLLD, - value: 'SKLLD' satisfies SkilledClan, - }, - { name: SKILLED_CLANS.SMRI, value: 'SMRI' satisfies SkilledClan }, - ) - .setRequired(true), - ) - .addStringOption(addUsernameChoices), +// export const eligibleCommand: CommandRegistry = { +// inProduction: true, +// inPublic: false, - async handler(interaction) { - const clan = interaction.options.getString('clan') as SkilledClan; - const { id, region: region } = await resolvePlayerFromCommand(interaction); - const accountInfo = await getAccountInfo(region, id); - const problems: TableInput = []; - const title = `${accountInfo.nickname}'s eligibility for ${SKILLED_CLANS[clan]}`; - let isEligible = false; - let body = ''; +// command: new SlashCommandBuilder() +// .setName('eligible') +// .setDescription('Checks eligibility for any Skilled clan') +// .addStringOption((option) => +// option +// .setName('clan') +// .setDescription('The clan to check') +// .setChoices( +// { +// name: SKILLED_CLANS.SKLLD, +// value: 'SKLLD' satisfies SkilledClan, +// }, +// { name: SKILLED_CLANS.SMRI, value: 'SMRI' satisfies SkilledClan }, +// ) +// .setRequired(true), +// ) +// .addStringOption(addUsernameChoices), - if (clan === 'SKLLD') { - const { diff: diffed } = await getStatsInPeriod( - region, - id, - getTimeDaysAgo(region, 30), - getPeriodNow(), - ); - const entries = Object.entries(diffed); - const stats = sumStats(entries.map(([, stats]) => stats)); - const averageTier = - (await entries.reduce>( - async (accumulator, [tankIdString, stats]) => { - const tankId = parseInt(tankIdString); - const tankDefinition = (await tankDefinitions)[tankId]; +// async handler(interaction) { +// const clan = interaction.options.getString('clan') as SkilledClan; +// const { id, region: region } = await resolvePlayerFromCommand(interaction); +// const accountInfo = await getAccountInfo(region, id); +// const problems: TableInput = []; +// const title = `${accountInfo.nickname}'s eligibility for ${SKILLED_CLANS[clan]}`; +// let isEligible = false; +// let body = ''; - if (!tankDefinition) return accumulator; +// if (clan === 'SKLLD') { +// const { diff: diffed } = await getStatsInPeriod( +// region, +// id, +// getTimeDaysAgo(region, 30), +// getPeriodNow(), +// ); +// const entries = Object.entries(diffed); +// const stats = sumStats(entries.map(([, stats]) => stats)); +// const averageTier = +// (await entries.reduce>( +// async (accumulator, [tankIdString, stats]) => { +// const tankId = parseInt(tankIdString); +// const tankDefinition = (await tankDefinitions)[tankId]; - const tankTier = tankDefinition.tier; +// if (!tankDefinition) return accumulator; - return (await accumulator) + tankTier * stats.battles; - }, - 0, - )) / - (await entries.reduce>( - async (accumulator, [tankIdString, stats]) => { - const tankId = parseInt(tankIdString); - const tankDefinition = (await tankDefinitions)[tankId]; +// const tankTier = tankDefinition.tier; - if (!tankDefinition) return accumulator; +// return (await accumulator) + tankTier * stats.battles; +// }, +// 0, +// )) / +// (await entries.reduce>( +// async (accumulator, [tankIdString, stats]) => { +// const tankId = parseInt(tankIdString); +// const tankDefinition = (await tankDefinitions)[tankId]; - return stats.battles; - }, - 0, - )); - const WN8 = - (await entries.reduce>( - async (accumulator, [tankIdString, stats]) => { - const tankId = parseInt(tankIdString); +// if (!tankDefinition) return accumulator; - // edge case where new tanks don't have averages - if ((await tankAverages)[tankId]) { - const tankWN8 = calculateWN8( - (await tankAverages)[tankId].all, - stats, - ); +// return stats.battles; +// }, +// 0, +// )); +// const WN8 = +// (await entries.reduce>( +// async (accumulator, [tankIdString, stats]) => { +// const tankId = parseInt(tankIdString); - if (isNaN(tankWN8)) return accumulator; - return (await accumulator) + tankWN8 * stats.battles; - } else return accumulator; - }, - 0, - )) / stats.battles; - const tier10Damage = - (await entries.reduce>( - async (accumulator, [tankIdString, stats]) => { - const tankId = parseInt(tankIdString); +// // edge case where new tanks don't have averages +// if ((await tankAverages)[tankId]) { +// const tankWN8 = calculateWN8( +// (await tankAverages)[tankId].all, +// stats, +// ); - if ((await tankDefinitions)[tankId]?.tier === 10) { - const tankStats = diffed[tankId]; - return (await accumulator) + tankStats.damage_dealt; - } else return accumulator; - }, - 0, - )) / - (await entries.reduce>( - async (accumulator, [tankIdString, stats]) => { - const tankId = parseInt(tankIdString); +// if (isNaN(tankWN8)) return accumulator; +// return (await accumulator) + tankWN8 * stats.battles; +// } else return accumulator; +// }, +// 0, +// )) / stats.battles; +// const tier10Damage = +// (await entries.reduce>( +// async (accumulator, [tankIdString, stats]) => { +// const tankId = parseInt(tankIdString); - if ((await tankDefinitions)[tankId]?.tier === 10) { - const tankStats = diffed[tankId]; - return (await accumulator) + tankStats.battles; - } else return accumulator; - }, - 0, - )); +// if ((await tankDefinitions)[tankId]?.tier === 10) { +// const tankStats = diffed[tankId]; +// return (await accumulator) + tankStats.damage_dealt; +// } else return accumulator; +// }, +// 0, +// )) / +// (await entries.reduce>( +// async (accumulator, [tankIdString, stats]) => { +// const tankId = parseInt(tankIdString); - if (stats.battles < 126) { - problems.push(['Battles', `${stats.battles} (${stats.battles - 126})`]); - } - if (averageTier < 8) { - problems.push([ - 'Average tier', - `${averageTier.toFixed(2)} (${(averageTier - 8).toFixed(2)})`, - ]); - } - if (stats.wins / stats.battles < 0.6) { - problems.push([ - 'Winrate', - `${(100 * (stats.wins / stats.battles)).toFixed(2)}% (${( - 100 * - (stats.wins / stats.battles - 0.6) - ).toFixed(2)}%)`, - ]); - } - if (WN8 < 2450) { - problems.push([ - 'WN8', - `${WN8.toFixed(0)} (${(WN8 - 2450).toFixed(0)})`, - ]); - } - if (tier10Damage < 1250) { - problems.push([ - 'Tier 10 damage', - `${tier10Damage.toFixed(0)} (${tier10Damage - 1250})`, - ]); - } +// if ((await tankDefinitions)[tankId]?.tier === 10) { +// const tankStats = diffed[tankId]; +// return (await accumulator) + tankStats.battles; +// } else return accumulator; +// }, +// 0, +// )); - isEligible = problems.length === 0; - body = isEligible - ? 'Player is fully eligible.' - : `Player is not eligible. Shortcomings in 30-day statistics:\n\n${markdownTable( - problems, - )}`; - } else if (clan === 'SMRI') { - const stats = accountInfo.statistics.all; +// if (stats.battles < 126) { +// problems.push(['Battles', `${stats.battles} (${stats.battles - 126})`]); +// } +// if (averageTier < 8) { +// problems.push([ +// 'Average tier', +// `${averageTier.toFixed(2)} (${(averageTier - 8).toFixed(2)})`, +// ]); +// } +// if (stats.wins / stats.battles < 0.6) { +// problems.push([ +// 'Winrate', +// `${(100 * (stats.wins / stats.battles)).toFixed(2)}% (${( +// 100 * +// (stats.wins / stats.battles - 0.6) +// ).toFixed(2)}%)`, +// ]); +// } +// if (WN8 < 2450) { +// problems.push([ +// 'WN8', +// `${WN8.toFixed(0)} (${(WN8 - 2450).toFixed(0)})`, +// ]); +// } +// if (tier10Damage < 1250) { +// problems.push([ +// 'Tier 10 damage', +// `${tier10Damage.toFixed(0)} (${tier10Damage - 1250})`, +// ]); +// } - if (stats.battles < 1500) { - problems.push([ - 'Battles', - `${stats.battles} (${stats.battles - 1500})`, - ]); - } - if (stats.wins / stats.battles <= 0.53) { - problems.push([ - 'Winrate', - `${(100 * (stats.wins / stats.battles)).toFixed(2)}% (${( - 100 * - (stats.wins / stats.battles - 0.53) - ).toFixed(2)}%)`, - ]); - } +// isEligible = problems.length === 0; +// body = isEligible +// ? 'Player is fully eligible.' +// : `Player is not eligible. Shortcomings in 30-day statistics:\n\n${markdownTable( +// problems, +// )}`; +// } else if (clan === 'SMRI') { +// const stats = accountInfo.statistics.all; - isEligible = problems.length === 0; - body = isEligible - ? 'Player is fully eligible.' - : `Player is not eligible. Shortcomings in career statistics:\n\n${markdownTable( - problems, - )}`; - } +// if (stats.battles < 1500) { +// problems.push([ +// 'Battles', +// `${stats.battles} (${stats.battles - 1500})`, +// ]); +// } +// if (stats.wins / stats.battles <= 0.53) { +// problems.push([ +// 'Winrate', +// `${(100 * (stats.wins / stats.battles)).toFixed(2)}% (${( +// 100 * +// (stats.wins / stats.battles - 0.53) +// ).toFixed(2)}%)`, +// ]); +// } - return (isEligible ? embedPositive : embedNegative)(title, body); - }, -}; +// isEligible = problems.length === 0; +// body = isEligible +// ? 'Player is fully eligible.' +// : `Player is not eligible. Shortcomings in career statistics:\n\n${markdownTable( +// problems, +// )}`; +// } + +// return (isEligible ? embedPositive : embedNegative)(title, body); +// }, +// }; diff --git a/src/commands/evolution.tsx b/src/commands/evolution.tsx index e2bb7bd99..8bc87cfe7 100644 --- a/src/commands/evolution.tsx +++ b/src/commands/evolution.tsx @@ -1,7 +1,4 @@ -import { - SlashCommandBuilder, - SlashCommandSubcommandGroupBuilder, -} from 'discord.js'; +import { SlashCommandSubcommandGroupBuilder } from 'discord.js'; import CommandWrapper from '../components/CommandWrapper'; import * as Graph from '../components/Graph'; import NoData, { NoDataType } from '../components/NoData'; @@ -20,6 +17,7 @@ import autocompleteTanks from '../core/discord/autocompleteTanks'; import autocompleteUsername from '../core/discord/autocompleteUsername'; import buttonPrimary from '../core/discord/buttonPrimary'; import commandToURL from '../core/discord/commandToURL'; +import { createLocalizedCommand } from '../core/discord/createLocalizedCommand'; import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams'; import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton'; import resolvePeriodFromCommand, { @@ -29,7 +27,7 @@ import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton'; import resolvePlayerFromCommand, { ResolvedPlayer, } from '../core/discord/resolvePlayerFromCommand'; -import { CommandRegistryPromisable } from '../events/interactionCreate'; +import { CommandRegistry } from '../events/interactionCreate'; type EvolutionStatType = 'player' | 'tank'; @@ -107,40 +105,32 @@ async function render( ); } -export const evolutionCommand = new Promise( +export const evolutionCommand = new Promise( async (resolve) => { - let playerGroup: SlashCommandSubcommandGroupBuilder; - let tankGroup: SlashCommandSubcommandGroupBuilder; - const command = new SlashCommandBuilder() - .setName('evolution') - .setDescription('Evolution of statistics') - .addSubcommandGroup((option) => { - playerGroup = option - .setName('player') - .setDescription('Player statistics'); - - return playerGroup; - }) - .addSubcommandGroup((option) => { - tankGroup = option.setName('tank').setDescription('Tank statistics'); - - return tankGroup; - }); - - addPeriodSubCommands(playerGroup!, (option) => - option.addStringOption(addUsernameChoices), - ); - addPeriodSubCommands(tankGroup!, (option) => - option - .addStringOption(addTankChoices) - .addStringOption(addUsernameChoices), - ); - resolve({ inProduction: true, inPublic: true, - command, + command: createLocalizedCommand('evolution', [ + { + group: 'player', + modify(option: SlashCommandSubcommandGroupBuilder) { + addPeriodSubCommands(option, (option) => + option.addStringOption(addUsernameChoices), + ); + }, + }, + { + group: 'tank', + modify(option: SlashCommandSubcommandGroupBuilder) { + addPeriodSubCommands(option, (option) => + option + .addStringOption(addTankChoices) + .addStringOption(addUsernameChoices), + ); + }, + }, + ]), async handler(interaction) { const commandGroup = @@ -156,8 +146,6 @@ export const evolutionCommand = new Promise( tankId, }); - getBlitzStarsLinkButton; - return [ await render(player, period, commandGroup, tankId), buttonPrimary(path, 'Refresh'), diff --git a/src/commands/fullStats.tsx b/src/commands/fullStats.tsx index fce3f7205..bb7e1f37f 100644 --- a/src/commands/fullStats.tsx +++ b/src/commands/fullStats.tsx @@ -29,7 +29,7 @@ import resolvePlayerFromCommand, { } from '../core/discord/resolvePlayerFromCommand'; import { StatFilters, filterStats } from '../core/statistics/filterStats'; import { getTierWeights } from '../core/statistics/getTierWeights'; -import { CommandRegistryPromisable } from '../events/interactionCreate'; +import { CommandRegistry } from '../events/interactionCreate'; async function render( { region, id }: ResolvedPlayer, @@ -64,7 +64,7 @@ async function render( ); } -export const fullStatsCommand = new Promise( +export const fullStatsCommand = new Promise( async (resolve) => { const command = await addPeriodicFilterOptions( new SlashCommandBuilder() diff --git a/src/commands/inactive.tsx b/src/commands/inactive.tsx index 762ff3515..f5a4807f5 100644 --- a/src/commands/inactive.tsx +++ b/src/commands/inactive.tsx @@ -12,58 +12,61 @@ import { CommandRegistry } from '../events/interactionCreate'; const DEFAULT_THRESHOLD = 7; -export const inactiveCommand: CommandRegistry = { - inProduction: true, - inPublic: true, +export const inactiveCommand = new Promise((resolve) => { + resolve({ + inProduction: true, + inPublic: true, - command: new SlashCommandBuilder() - .setName('inactive') - .setDescription('Lists all inactive players') - .addStringOption(addClanChoices) - .addNumberOption((option) => - option - .setName('threshold') - .setDescription( - `The number of days inactive (default: ${DEFAULT_THRESHOLD})`, + command: new SlashCommandBuilder() + .setName('inactive') + .setDescription('Lists all inactive players') + .addStringOption(addClanChoices) + .addNumberOption((option) => + option + .setName('threshold') + .setDescription( + `The number of days inactive (default: ${DEFAULT_THRESHOLD})`, + ) + .setMinValue(0), + ), + + async handler(interaction) { + const { region, id } = await resolveClanFromCommand(interaction); + const threshold = + interaction.options.getNumber('threshold')! ?? DEFAULT_THRESHOLD; + const time = new Date().getTime() / 1000; + const clanInfo = await getClanInfo(region, id); + const accountInfo = await getAccountInfo(region, clanInfo.members_ids); + const inactive = accountInfo + .map( + (account) => + [ + account.nickname, + (time - account.last_battle_time) / 60 / 60 / 24, + ] as [string, number], ) - .setMinValue(0), - ), + .filter(([, inactiveDays]) => inactiveDays >= threshold) + .sort((a, b) => b[1] - a[1]) + .map( + ([name, days]) => + [name, `${days.toFixed(0)} days`] as [string, string], + ); + const hasInactiveMembers = inactive.length > 0; - async handler(interaction) { - const { region, id } = await resolveClanFromCommand(interaction); - const threshold = - interaction.options.getNumber('threshold')! ?? DEFAULT_THRESHOLD; - const time = new Date().getTime() / 1000; - const clanInfo = await getClanInfo(region, id); - const accountInfo = await getAccountInfo(region, clanInfo.members_ids); - const inactive = accountInfo - .map( - (account) => - [ - account.nickname, - (time - account.last_battle_time) / 60 / 60 / 24, - ] as [string, number], - ) - .filter(([, inactiveDays]) => inactiveDays >= threshold) - .sort((a, b) => b[1] - a[1]) - .map( - ([name, days]) => [name, `${days.toFixed(0)} days`] as [string, string], + return ( + + + + {!hasInactiveMembers && } + {hasInactiveMembers && } + ); - const hasInactiveMembers = inactive.length > 0; + }, - return ( - - - - {!hasInactiveMembers && } - {hasInactiveMembers && } - - ); - }, - - autocomplete: autocompleteClan, -}; + autocomplete: autocompleteClan, + }); +}); diff --git a/src/commands/link.tsx b/src/commands/link.tsx index 27e744b34..5f81fa346 100644 --- a/src/commands/link.tsx +++ b/src/commands/link.tsx @@ -16,194 +16,198 @@ import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import { CommandRegistry } from '../events/interactionCreate'; import { theme } from '../stitches.config'; -export const verifyCommand: CommandRegistry = { - inProduction: true, - inPublic: true, +export const verifyCommand = new Promise((resolve) => { + resolve({ + inProduction: true, + inPublic: true, - command: new SlashCommandBuilder() - .setName('link') - .setDescription('Links your Blitz and Discord account') - .addStringOption((option) => addUsernameChoices(option).setRequired(true)), + command: new SlashCommandBuilder() + .setName('link') + .setDescription('Links your Blitz and Discord account') + .addStringOption((option) => + addUsernameChoices(option).setRequired(true), + ), - async handler(interaction) { - const { id, region } = await resolvePlayerFromCommand(interaction); - const discordId = parseInt(interaction.user.id); - const accountInfo = await getAccountInfo(region, id); - const clanAccountInfo = await getClanAccountInfo(region, id, ['clan']); + async handler(interaction) { + const { id, region } = await resolvePlayerFromCommand(interaction); + const discordId = parseInt(interaction.user.id); + const accountInfo = await getAccountInfo(region, id); + const clanAccountInfo = await getClanAccountInfo(region, id, ['clan']); - await linkBlitzAndDiscord(discordId, region, id); + await linkBlitzAndDiscord(discordId, region, id); - if (interaction.guildId === discord.sklld_guild_id) { - if (!interaction.guild?.members.me?.permissions.has('ManageRoles')) { - return embedNegative( - `${markdownEscape(interaction.user.username)} failed to verify`, - "I don't have the permission to change your manage roles.", + if (interaction.guildId === discord.sklld_guild_id) { + if (!interaction.guild?.members.me?.permissions.has('ManageRoles')) { + return embedNegative( + `${markdownEscape(interaction.user.username)} failed to verify`, + "I don't have the permission to change your manage roles.", + ); + } + + await (interaction.member!.roles as GuildMemberRoleManager).remove( + discord.sklld_verify_role, + ); + await (interaction.member!.roles as GuildMemberRoleManager).add( + discord.sklld_peasant_role, ); } - await (interaction.member!.roles as GuildMemberRoleManager).remove( - discord.sklld_verify_role, - ); - await (interaction.member!.roles as GuildMemberRoleManager).add( - discord.sklld_peasant_role, - ); - } - - return [ - -
- - + return [ +
+ +
- -
- - {interaction.user.displayName} - - - @{interaction.user.username} - -
-
- -
- - Accounts linked -
- -
- {clanAccountInfo?.clan ? ( - ) : null} + +
+ + {interaction.user.displayName} + + + @{interaction.user.username} + +
+
+
- + Accounts linked +
+ +
+ {clanAccountInfo?.clan ? ( + + ) : null} +
- {accountInfo.nickname} - - - {clanAccountInfo?.clan - ? `[${clanAccountInfo?.clan?.tag}] • ` - : ''} - {REGION_NAMES[region]} - + + {accountInfo.nickname} + + + {clanAccountInfo?.clan + ? `[${clanAccountInfo?.clan?.tag}] • ` + : ''} + {REGION_NAMES[region]} + +
+ +
+
, - -
- , + embedInfo( + 'Incorrect account?', + 'Use the command again and select a username from the search results.', + ), + ]; + }, - embedInfo( - 'Incorrect account?', - 'Use the command again and select a username from the search results.', - ), - ]; - }, - - autocomplete: autocompleteUsername, -}; + autocomplete: autocompleteUsername, + }); +}); diff --git a/src/commands/ownedTanks.tsx b/src/commands/ownedTanks.tsx index 0e61e4f31..4c8640c7a 100644 --- a/src/commands/ownedTanks.tsx +++ b/src/commands/ownedTanks.tsx @@ -21,108 +21,112 @@ import autocompleteUsername from '../core/discord/autocompleteUsername'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import { CommandRegistry } from '../events/interactionCreate'; -export const ownedTanksCommand: CommandRegistry = { - inProduction: true, - inPublic: true, +export const ownedTanksCommand = new Promise((resolve) => { + resolve({ + inProduction: true, + inPublic: true, - command: new SlashCommandBuilder() - .setName('owned-tanks') - .setDescription("Shows a player's owned tanks") - .addStringOption(addTierChoices) - .addStringOption(addUsernameChoices), + command: new SlashCommandBuilder() + .setName('owned-tanks') + .setDescription("Shows a player's owned tanks") + .addStringOption(addTierChoices) + .addStringOption(addUsernameChoices), - async handler(interaction) { - const tier = Number(interaction.options.getString('tier')); - const account = await resolvePlayerFromCommand(interaction); - const { id, region: server } = account; - const accountInfo = await getAccountInfo(server, id); - const tankStats = await getTankStats(server, id); - const filteredTanks = ( - await Promise.all( - tankStats.map(async (tankData) => ({ - tankDefinitions: (await tankDefinitions)[tankData.tank_id]!, - id: tankData.tank_id, - })), - ) - ).filter((tank) => tank.tankDefinitions?.tier === tier); - const clanAccountInfo = await getClanAccountInfo(server, id, ['clan']); - const groupedTanks: Record = {}; - const nations: string[] = []; + async handler(interaction) { + const tier = Number(interaction.options.getString('tier')); + const account = await resolvePlayerFromCommand(interaction); + const { id, region: server } = account; + const accountInfo = await getAccountInfo(server, id); + const tankStats = await getTankStats(server, id); + const filteredTanks = ( + await Promise.all( + tankStats.map(async (tankData) => ({ + tankDefinitions: (await tankDefinitions)[tankData.tank_id]!, + id: tankData.tank_id, + })), + ) + ).filter((tank) => tank.tankDefinitions?.tier === tier); + const clanAccountInfo = await getClanAccountInfo(server, id, ['clan']); + const groupedTanks: Record = {}; + const nations: string[] = []; - filteredTanks.forEach((tank) => { - if (groupedTanks[tank.tankDefinitions.nation] === undefined) { - groupedTanks[tank.tankDefinitions.nation] = [tank.tankDefinitions]; - nations.push(tank.tankDefinitions.nation); - } else { - groupedTanks[tank.tankDefinitions.nation].push(tank.tankDefinitions); - } - }); + filteredTanks.forEach((tank) => { + if (groupedTanks[tank.tankDefinitions.nation] === undefined) { + groupedTanks[tank.tankDefinitions.nation] = [tank.tankDefinitions]; + nations.push(tank.tankDefinitions.nation); + } else { + groupedTanks[tank.tankDefinitions.nation].push(tank.tankDefinitions); + } + }); - nations.sort(); + nations.sort(); - return ( - - + return ( + + - {filteredTanks.length === 0 && } + {filteredTanks.length === 0 && ( + + )} - {filteredTanks.length > 0 && - (await Promise.all( - nations.map(async (nation) => { - const tanks = groupedTanks[nation]; - const leftColumnSize = Math.ceil(tanks.length / 2); - const leftColumn = tanks.slice(0, leftColumnSize); - const rightColumn = tanks.slice(leftColumnSize); + {filteredTanks.length > 0 && + (await Promise.all( + nations.map(async (nation) => { + const tanks = groupedTanks[nation]; + const leftColumnSize = Math.ceil(tanks.length / 2); + const leftColumn = tanks.slice(0, leftColumnSize); + const rightColumn = tanks.slice(leftColumnSize); - return ( - - - {(await encyclopediaInfo).vehicle_nations[nation]} - + return ( + + + {(await encyclopediaInfo).vehicle_nations[nation]} + - - - {await Promise.all( - leftColumn.map(async (tank) => ( - - )), - )} - - - {await Promise.all( - rightColumn.map(async (tank) => ( - - )), - )} - - - - ); - }), - ))} - - ); - }, + + + {await Promise.all( + leftColumn.map(async (tank) => ( + + )), + )} + + + {await Promise.all( + rightColumn.map(async (tank) => ( + + )), + )} + + + + ); + }), + ))} + + ); + }, - autocomplete: autocompleteUsername, -}; + autocomplete: autocompleteUsername, + }); +}); diff --git a/src/commands/permissions.ts b/src/commands/permissions.ts index b8f29f563..367364d8e 100644 --- a/src/commands/permissions.ts +++ b/src/commands/permissions.ts @@ -1,42 +1,44 @@ import { SlashCommandBuilder } from 'discord.js'; import { CommandRegistry } from '../events/interactionCreate'; -export const permissionsCommand: CommandRegistry = { - inProduction: true, - inPublic: true, +export const permissionsCommand = new Promise((resolve) => { + resolve({ + inProduction: true, + inPublic: true, - command: new SlashCommandBuilder() - .setName('permissions') - .setDescription('Checks wether the bot has the required permissions'), + command: new SlashCommandBuilder() + .setName('permissions') + .setDescription('Checks wether the bot has the required permissions'), - async handler(interaction) { - // friday the 13th permissions easter egg - const isFridayThe13th = - new Date().getDay() === 5 && new Date().getDate() === 13; - const permissions = [ - [ - interaction.appPermissions?.has('ViewChannel'), - 'View channels: needed for the refresh button to work', - ], - [ - interaction.appPermissions?.has('ReadMessageHistory'), - 'Read message history: also needed for the refresh button to work', - ], - [ - interaction.appPermissions?.has('AttachFiles'), - 'Attach files: needed for image-based commands to work', - ], - ]; - const body = permissions - .map(([hasPermission, description]) => { - if (hasPermission) { - return `${isFridayThe13th ? '😇' : '✅'} ${description}`; - } else { - return `${isFridayThe13th ? '💀' : '❌'} ${description}`; - } - }) - .join('\n'); + async handler(interaction) { + // friday the 13th permissions easter egg + const isFridayThe13th = + new Date().getDay() === 5 && new Date().getDate() === 13; + const permissions = [ + [ + interaction.appPermissions?.has('ViewChannel'), + 'View channels: needed for the refresh button to work', + ], + [ + interaction.appPermissions?.has('ReadMessageHistory'), + 'Read message history: also needed for the refresh button to work', + ], + [ + interaction.appPermissions?.has('AttachFiles'), + 'Attach files: needed for image-based commands to work', + ], + ]; + const body = permissions + .map(([hasPermission, description]) => { + if (hasPermission) { + return `${isFridayThe13th ? '😇' : '✅'} ${description}`; + } else { + return `${isFridayThe13th ? '💀' : '❌'} ${description}`; + } + }) + .join('\n'); - return `# Permissions\n${body}`; - }, -}; + return `# Permissions\n${body}`; + }, + }); +}); diff --git a/src/commands/ping.ts b/src/commands/ping.ts index bb93c57dc..e02eabcd0 100644 --- a/src/commands/ping.ts +++ b/src/commands/ping.ts @@ -1,32 +1,34 @@ import { SlashCommandBuilder } from 'discord.js'; import { CommandRegistry } from '../events/interactionCreate'; -export const pingCommand: CommandRegistry = { - inProduction: true, - inPublic: true, - handlesInteraction: true, +export const pingCommand = new Promise((resolve) => { + resolve({ + inProduction: true, + inPublic: true, + handlesInteraction: true, - command: new SlashCommandBuilder() - .setName('ping') - .setDescription('Check if the bot is alive') - .addSubcommand((option) => - option.setName('blitzkrieg').setDescription('Ping Blitzkrieg'), - ) - .addSubcommand((option) => - option.setName('blitz').setDescription('Ping Blitz'), - ), + command: new SlashCommandBuilder() + .setName('ping') + .setDescription('Check if the bot is alive') + .addSubcommand((option) => + option.setName('blitzkrieg').setDescription('Ping Blitzkrieg'), + ) + .addSubcommand((option) => + option.setName('blitz').setDescription('Ping Blitz'), + ), - async handler(interaction) { - const subcommand = interaction.options.getSubcommand(); - const executionStart = Date.now(); + async handler(interaction) { + const subcommand = interaction.options.getSubcommand(); + const executionStart = Date.now(); - if (subcommand === 'blitzkrieg') { - await interaction.editReply('Pong 🏓'); - } else { - await fetch('https://api.wotblitz.com/'); - } + if (subcommand === 'blitzkrieg') { + await interaction.editReply('Pong 🏓'); + } else { + await fetch('https://api.wotblitz.com/'); + } - const executionTime = Date.now() - executionStart; - interaction.editReply(`Pong 🏓 - ${executionTime}ms`); - }, -}; + const executionTime = Date.now() - executionStart; + interaction.editReply(`Pong 🏓 - ${executionTime}ms`); + }, + }); +}); diff --git a/src/commands/playerAchievements.ts b/src/commands/playerAchievements.ts index 440b4d954..ec76f0a0f 100644 --- a/src/commands/playerAchievements.ts +++ b/src/commands/playerAchievements.ts @@ -12,57 +12,62 @@ import { CommandRegistry } from '../events/interactionCreate'; type SortBy = 'name' | 'count'; -export const playerAchievementsCommand: CommandRegistry = { - inProduction: true, - inPublic: true, +export const playerAchievementsCommand = new Promise( + (resolve) => { + resolve({ + inProduction: true, + inPublic: true, - command: new SlashCommandBuilder() - .setName('player-achievements') - .setDescription("All the player's achievements") - .addStringOption(addUsernameChoices) - .addStringOption((option) => - option - .setName('sort') - .setDescription('What to sort by (default: name)') - .addChoices( - { name: 'By name', value: 'name' satisfies SortBy }, - { name: 'By count', value: 'count' satisfies SortBy }, + command: new SlashCommandBuilder() + .setName('player-achievements') + .setDescription("All the player's achievements") + .addStringOption(addUsernameChoices) + .addStringOption((option) => + option + .setName('sort') + .setDescription('What to sort by (default: name)') + .addChoices( + { name: 'By name', value: 'name' satisfies SortBy }, + { name: 'By count', value: 'count' satisfies SortBy }, + ), ), - ), - async handler(interaction) { - const sortBy = (interaction.options.getString('sort') ?? 'name') as SortBy; - const account = await resolvePlayerFromCommand(interaction); - const { id, region: region } = account; - const accountInfo = await getAccountInfo(region, id); - const accountsAchievements = await getAccountAchievements(region, id); - const accountAchievements = accountsAchievements; - const compound = { - ...accountAchievements.achievements, - ...accountAchievements.max_series, - }; + async handler(interaction) { + const sortBy = (interaction.options.getString('sort') ?? + 'name') as SortBy; + const account = await resolvePlayerFromCommand(interaction); + const { id, region: region } = account; + const accountInfo = await getAccountInfo(region, id); + const accountsAchievements = await getAccountAchievements(region, id); + const accountAchievements = accountsAchievements; + const compound = { + ...accountAchievements.achievements, + ...accountAchievements.max_series, + }; - return embedInfo( - `${markdownEscape(accountInfo.nickname)}'s information`, + return embedInfo( + `${markdownEscape(accountInfo.nickname)}'s information`, - markdownTable( - Object.keys(compound) - .filter((achievement) => compound[achievement] > 0) - .sort( - sortBy === 'count' - ? (a, b) => compound[b] - compound[a] - : undefined, - ) - .map( - (achievement) => - [ - startCase(achievement), - compound[achievement], - ] as TableInputEntry, + markdownTable( + Object.keys(compound) + .filter((achievement) => compound[achievement] > 0) + .sort( + sortBy === 'count' + ? (a, b) => compound[b] - compound[a] + : undefined, + ) + .map( + (achievement) => + [ + startCase(achievement), + compound[achievement], + ] as TableInputEntry, + ), ), - ), - ); - }, + ); + }, - autocomplete: autocompleteUsername, -}; + autocomplete: autocompleteUsername, + }); + }, +); diff --git a/src/commands/playerInfo.ts b/src/commands/playerInfo.ts index 64342da2d..69a628be9 100644 --- a/src/commands/playerInfo.ts +++ b/src/commands/playerInfo.ts @@ -8,47 +8,49 @@ import markdownTable from '../core/discord/markdownTable'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; import { CommandRegistry } from '../events/interactionCreate'; -export const playerInfoCommand: CommandRegistry = { - inProduction: true, - inPublic: true, +export const playerInfoCommand = new Promise((resolve) => { + resolve({ + inProduction: true, + inPublic: true, - command: new SlashCommandBuilder() - .setName('player-info') - .setDescription('Basic information about a player') - .addStringOption(addUsernameChoices), + command: new SlashCommandBuilder() + .setName('player-info') + .setDescription('Basic information about a player') + .addStringOption(addUsernameChoices), - async handler(interaction) { - const account = await resolvePlayerFromCommand(interaction); - const { id, region: region } = account; - const accountInfo = await getAccountInfo(region, id); + async handler(interaction) { + const account = await resolvePlayerFromCommand(interaction); + const { id, region: region } = account; + const accountInfo = await getAccountInfo(region, id); - return embedInfo( - `${markdownEscape(accountInfo.nickname)}'s information`, + return embedInfo( + `${markdownEscape(accountInfo.nickname)}'s information`, - markdownTable([ - ['Nickname', `${accountInfo.nickname}`], - ['Battles', `${accountInfo.statistics.all.battles}`], - [ - 'Winrate', - `${( - 100 * - (accountInfo.statistics.all.wins / - accountInfo.statistics.all.battles) - ).toFixed(2)}%`, - ], - [], - ['Account ID', `${accountInfo.account_id}`], - [ - 'Created', - `${new Date(accountInfo.created_at * 1000).toDateString()}`, - ], - [ - 'Last battle', - `${new Date(accountInfo.last_battle_time * 1000).toDateString()}`, - ], - ]), - ); - }, + markdownTable([ + ['Nickname', `${accountInfo.nickname}`], + ['Battles', `${accountInfo.statistics.all.battles}`], + [ + 'Winrate', + `${( + 100 * + (accountInfo.statistics.all.wins / + accountInfo.statistics.all.battles) + ).toFixed(2)}%`, + ], + [], + ['Account ID', `${accountInfo.account_id}`], + [ + 'Created', + `${new Date(accountInfo.created_at * 1000).toDateString()}`, + ], + [ + 'Last battle', + `${new Date(accountInfo.last_battle_time * 1000).toDateString()}`, + ], + ]), + ); + }, - autocomplete: autocompleteUsername, -}; + autocomplete: autocompleteUsername, + }); +}); diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx index 91dc7d817..5fbe47c79 100644 --- a/src/commands/ratings.tsx +++ b/src/commands/ratings.tsx @@ -27,7 +27,7 @@ import addUsernameChoices from '../core/discord/addUsernameChoices'; import autocompleteUsername from '../core/discord/autocompleteUsername'; import embedNegative from '../core/discord/embedNegative'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; -import { CommandRegistryPromisable } from '../events/interactionCreate'; +import { CommandRegistry } from '../events/interactionCreate'; import { UserError } from '../hooks/userError'; export interface RatingsPlayer { @@ -122,334 +122,329 @@ const noOngoingSeason = embedNegative( "Wargaming didn't provide any data for this season.", ); -export const ratingsCommand = new Promise( - async (resolve) => { - const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber(); - const onGoingSeason = await isOnGoingRatingsSeason(); - const seasonNumbers = range( - FIRST_ARCHIVED_RATINGS_SEASON, - latestArchivedSeasonNumber + (onGoingSeason ? 1 : 0) + 1, +export const ratingsCommand = new Promise(async (resolve) => { + const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber(); + const onGoingSeason = await isOnGoingRatingsSeason(); + const seasonNumbers = range( + FIRST_ARCHIVED_RATINGS_SEASON, + latestArchivedSeasonNumber + (onGoingSeason ? 1 : 0) + 1, + ); + + function addSubcommands(option: SlashCommandSubcommandBuilder) { + return option + .addStringOption(addUsernameChoices) + .addIntegerOption((option) => + option + .setName('limit') + .setDescription('How many neighbors to display (default: 10)') + .setRequired(false) + .setMinValue(5) + .setMaxValue(30), + ) + .addStringOption((option) => + option + .setName('season') + .setDescription('Season number') + .setRequired(false) + .addChoices( + ...seasonNumbers + .map( + (number, index) => + ({ + name: `${number}${index === seasonNumbers.length - 1 && onGoingSeason ? ' (current)' : ''}`, + value: `${number}`, + }) satisfies APIApplicationCommandOptionChoice, + ) + .reverse(), + ), + ); + } + + const command = new SlashCommandBuilder() + .setName('ratings') + .setDescription('Ratings battles statistics') + .addSubcommand((option) => + addSubcommands( + option + .setName('neighbors') + .setDescription('Shows neighboring players on the leaderboard'), + ), + ) + .addSubcommand((option) => + addSubcommands( + option + .setName('league') + .setDescription('Shows top players from a league') + .addStringOption(addRegionChoices) + .addStringOption((option) => + option + .setName('league') + .setDescription('The league to display') + .addChoices( + ...LEAGUES.map( + ({ name }, value) => + ({ + name, + value: `${value}`, + }) satisfies APIApplicationCommandOptionChoice, + ), + ) + .setRequired(true), + ), + ), ); - function addSubcommands(option: SlashCommandSubcommandBuilder) { - return option - .addStringOption(addUsernameChoices) - .addIntegerOption((option) => - option - .setName('limit') - .setDescription('How many neighbors to display (default: 10)') - .setRequired(false) - .setMinValue(5) - .setMaxValue(30), - ) - .addStringOption((option) => - option - .setName('season') - .setDescription('Season number') - .setRequired(false) - .addChoices( - ...seasonNumbers - .map( - (number, index) => + resolve({ + inProduction: true, + inPublic: true, + + command, + + async handler(interaction) { + interface SimplifiedPlayer { + id: number; + score: number; + position: number; + clan: string | undefined; + nickname: string; + } + + const subcommand = interaction.options.getSubcommand(true) as + | 'league' + | 'neighbors'; + const limit = interaction.options.getInteger('limit') ?? 10; + const seasonOption = interaction.options.getString('season'); + const season = seasonOption + ? parseInt(seasonOption) + : onGoingSeason + ? latestArchivedSeasonNumber + 1 + : latestArchivedSeasonNumber; + const isCurrentSeason = season === latestArchivedSeasonNumber + 1; + let titleName: string; + let titleImage: string | undefined; + let titleDescription: string; + let playersBefore: number; + let playersAfter: number; + let players: SimplifiedPlayer[] | undefined; + let midnightLeaderboard: BlitzkriegRatingsLeaderboard | undefined; + let playerId: number | undefined; + let regionRatingsInfo: RatingsInfo; + + if (subcommand === 'league') { + const leagueIndex = parseInt(interaction.options.getString('league')!); + const region = interaction.options.getString('region') as Region; + regionRatingsInfo = await (isCurrentSeason + ? getRatingsInfo(region) + : getArchivedRatingsInfo(region, season!)); + + if (regionRatingsInfo.detail !== undefined) return noOngoingSeason; + + const result = isCurrentSeason + ? await getRatingsLeague(region, leagueIndex).then(async (result) => + result.slice(0, limit).map( + (player) => + ({ + id: player.spa_id, + score: player.score, + position: player.number, + clan: player.clan_tag, + nickname: player.nickname, + }) satisfies SimplifiedPlayer, + ), + ) + : await getArchivedRatingsLeaderboard(region, season!).then( + async (players) => { + const firstPlayerIndex = + leagueIndex === 0 + ? 0 + : players.findIndex( + (player) => + player.score < LEAGUES[leagueIndex - 1].minScore, + ); + const lastPlayerIndex = firstPlayerIndex + limit - 1; + const trimmed = players.slice( + firstPlayerIndex, + lastPlayerIndex + 1, + ); + const clanData = await getClanAccountInfo( + region, + trimmed.map(({ id }) => id), + ['clan'], + ); + + return trimmed.map( + (player, index) => ({ - name: `${number}${index === seasonNumbers.length - 1 && onGoingSeason ? ' (current)' : ''}`, - value: `${number}`, - }) satisfies APIApplicationCommandOptionChoice, - ) - .reverse(), - ), - ); - } + id: player.id, + score: player.score, + position: firstPlayerIndex + index + 1, + clan: clanData[player.id]?.clan?.tag, + nickname: + clanData[index]?.account_name ?? `Deleted ${player.id}`, + }) satisfies SimplifiedPlayer, + ); + }, + ); + const leagueInfo = regionRatingsInfo.leagues[leagueIndex]; + midnightLeaderboard = isCurrentSeason + ? await getArchivedRatingsMidnightLeaderboard( + region, + regionRatingsInfo.current_season, + ) + : await getArchivedRatingsMidnightLeaderboard(region, season!); - const command = new SlashCommandBuilder() - .setName('ratings') - .setDescription('Ratings battles statistics') - .addSubcommand((option) => - addSubcommands( - option - .setName('neighbors') - .setDescription('Shows neighboring players on the leaderboard'), - ), - ) - .addSubcommand((option) => - addSubcommands( - option - .setName('league') - .setDescription('Shows top players from a league') - .addStringOption(addRegionChoices) - .addStringOption((option) => - option - .setName('league') - .setDescription('The league to display') - .addChoices( - ...LEAGUES.map( - ({ name }, value) => - ({ - name, - value: `${value}`, - }) satisfies APIApplicationCommandOptionChoice, - ), - ) - .setRequired(true), - ), - ), - ); + players = result; + playersBefore = result ? result[0].position - 1 : 0; + playersAfter = + regionRatingsInfo.count - result[result.length - 1].position; + titleName = `${ + leagueInfo ? leagueInfo.title : 'No Ongoing Season' + } - ${REGION_NAMES_SHORT[region]}`; + titleImage = leagueInfo.big_icon.startsWith('http') + ? leagueInfo.big_icon + : `https:${leagueInfo.big_icon}`; + titleDescription = `Top ${limit} players`; + } else { + const { region, id } = await resolvePlayerFromCommand(interaction); + regionRatingsInfo = isCurrentSeason + ? await getRatingsInfo(region) + : await getArchivedRatingsInfo(region, season!); - resolve({ - inProduction: true, - inPublic: true, + if (regionRatingsInfo.detail !== undefined) return noOngoingSeason; - command, - - async handler(interaction) { - interface SimplifiedPlayer { - id: number; - score: number; - position: number; - clan: string | undefined; - nickname: string; - } - - const subcommand = interaction.options.getSubcommand(true) as - | 'league' - | 'neighbors'; - const limit = interaction.options.getInteger('limit') ?? 10; - const seasonOption = interaction.options.getString('season'); - const season = seasonOption - ? parseInt(seasonOption) - : onGoingSeason - ? latestArchivedSeasonNumber + 1 - : latestArchivedSeasonNumber; - const isCurrentSeason = season === latestArchivedSeasonNumber + 1; - let titleName: string; - let titleImage: string | undefined; - let titleDescription: string; - let playersBefore: number; - let playersAfter: number; - let players: SimplifiedPlayer[] | undefined; - let midnightLeaderboard: BlitzkriegRatingsLeaderboard | undefined; - let playerId: number | undefined; - let regionRatingsInfo: RatingsInfo; - - if (subcommand === 'league') { - const leagueIndex = parseInt( - interaction.options.getString('league')!, - ); - const region = interaction.options.getString('region') as Region; - regionRatingsInfo = await (isCurrentSeason - ? getRatingsInfo(region) - : getArchivedRatingsInfo(region, season!)); - - if (regionRatingsInfo.detail !== undefined) return noOngoingSeason; - - const result = isCurrentSeason - ? await getRatingsLeague(region, leagueIndex).then(async (result) => - result.slice(0, limit).map( + const accountInfo = await getAccountInfo(region, id); + const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan; + const neighbors = isCurrentSeason + ? await getRatingsNeighbors(region, id, Math.round(limit / 2)).then( + (players) => + players.neighbors.map( (player) => ({ id: player.spa_id, - score: player.score, position: player.number, + score: player.score, clan: player.clan_tag, nickname: player.nickname, }) satisfies SimplifiedPlayer, ), - ) - : await getArchivedRatingsLeaderboard(region, season!).then( - async (players) => { - const firstPlayerIndex = - leagueIndex === 0 - ? 0 - : players.findIndex( - (player) => - player.score < LEAGUES[leagueIndex - 1].minScore, - ); - const lastPlayerIndex = firstPlayerIndex + limit - 1; - const trimmed = players.slice( - firstPlayerIndex, - lastPlayerIndex + 1, - ); - const clanData = await getClanAccountInfo( - region, - trimmed.map(({ id }) => id), - ['clan'], + ) + : await getArchivedRatingsLeaderboard(region, season!).then( + async (players) => { + const playerIndex = players.findIndex( + (player) => player.id === id, + ); + + if (playerIndex === -1) { + throw new UserError( + `# ${markdownEscape(accountInfo.nickname)} didn't play ratings in season ${season}\nThis player did not participate in this season or did not get past calibration.`, ); + } - return trimmed.map( - (player, index) => - ({ - id: player.id, - score: player.score, - position: firstPlayerIndex + index + 1, - clan: clanData[player.id]?.clan?.tag, - nickname: - clanData[index]?.account_name ?? - `Deleted ${player.id}`, - }) satisfies SimplifiedPlayer, - ); - }, - ); - const leagueInfo = regionRatingsInfo.leagues[leagueIndex]; - midnightLeaderboard = isCurrentSeason - ? await getArchivedRatingsMidnightLeaderboard( - region, - regionRatingsInfo.current_season, - ) - : await getArchivedRatingsMidnightLeaderboard(region, season!); + const halfRange = Math.round(limit / 2); + const trimmed = players.slice( + playerIndex - halfRange, + playerIndex + halfRange + 1, + ); + const clanData = await getClanAccountInfo( + region, + trimmed.map((player) => player.id), + ['clan'], + ); - players = result; - playersBefore = result ? result[0].position - 1 : 0; - playersAfter = - regionRatingsInfo.count - result[result.length - 1].position; - titleName = `${ - leagueInfo ? leagueInfo.title : 'No Ongoing Season' - } - ${REGION_NAMES_SHORT[region]}`; - titleImage = leagueInfo.big_icon.startsWith('http') - ? leagueInfo.big_icon - : `https:${leagueInfo.big_icon}`; - titleDescription = `Top ${limit} players`; - } else { - const { region, id } = await resolvePlayerFromCommand(interaction); - regionRatingsInfo = isCurrentSeason - ? await getRatingsInfo(region) - : await getArchivedRatingsInfo(region, season!); + return trimmed.map((player, index) => ({ + id: player.id, + score: player.score, + position: playerIndex + index + 1, + clan: clanData[index]?.clan?.tag, + nickname: clanData[index] + ? clanData[index]!.account_name + : `Deleted ${player.id}`, + })); + }, + ); + midnightLeaderboard = isCurrentSeason + ? await getArchivedRatingsMidnightLeaderboard( + region, + regionRatingsInfo.current_season, + ) + : await getArchivedRatingsMidnightLeaderboard(region, season!); - if (regionRatingsInfo.detail !== undefined) return noOngoingSeason; + players = neighbors; + titleImage = clan + ? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png` + : undefined; + titleName = neighbors + ? accountInfo.nickname + : `No Ongoing Season - ${REGION_NAMES_SHORT[region]}`; + titleDescription = 'Ratings neighbors'; + playersBefore = neighbors ? neighbors[0].position - 1 : 0; + playersAfter = + regionRatingsInfo.count - neighbors[neighbors.length - 1].position; + playerId = id; + } - const accountInfo = await getAccountInfo(region, id); - const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan; - const neighbors = isCurrentSeason - ? await getRatingsNeighbors(region, id, Math.round(limit / 2)).then( - (players) => - players.neighbors.map( - (player) => - ({ - id: player.spa_id, - position: player.number, - score: player.score, - clan: player.clan_tag, - nickname: player.nickname, - }) satisfies SimplifiedPlayer, - ), - ) - : await getArchivedRatingsLeaderboard(region, season!).then( - async (players) => { - const playerIndex = players.findIndex( - (player) => player.id === id, - ); + const resolvedRegionRatingsInfo: typeof regionRatingsInfo & { + detail: undefined; + } = regionRatingsInfo; - if (playerIndex === -1) { - throw new UserError( - `# ${markdownEscape(accountInfo.nickname)} didn't play ratings in season ${season}\nThis player did not participate in this season or did not get past calibration.`, - ); - } - - const halfRange = Math.round(limit / 2); - const trimmed = players.slice( - playerIndex - halfRange, - playerIndex + halfRange + 1, - ); - const clanData = await getClanAccountInfo( - region, - trimmed.map((player) => player.id), - ['clan'], - ); - - return trimmed.map((player, index) => ({ - id: player.id, - score: player.score, - position: playerIndex + index + 1, - clan: clanData[index]?.clan?.tag, - nickname: clanData[index] - ? clanData[index]!.account_name - : `Deleted ${player.id}`, - })); - }, - ); - midnightLeaderboard = isCurrentSeason - ? await getArchivedRatingsMidnightLeaderboard( - region, - regionRatingsInfo.current_season, - ) - : await getArchivedRatingsMidnightLeaderboard(region, season!); - - players = neighbors; - titleImage = clan - ? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png` - : undefined; - titleName = neighbors - ? accountInfo.nickname - : `No Ongoing Season - ${REGION_NAMES_SHORT[region]}`; - titleDescription = 'Ratings neighbors'; - playersBefore = neighbors ? neighbors[0].position - 1 : 0; - playersAfter = - regionRatingsInfo.count - neighbors[neighbors.length - 1].position; - playerId = id; - } - - const resolvedRegionRatingsInfo: typeof regionRatingsInfo & { - detail: undefined; - } = regionRatingsInfo; - - const items = players.map((player) => { - const reward = resolvedRegionRatingsInfo.rewards.find( - (reward) => - player.position >= reward.from_position && - player.position <= reward.to_position, - ); - const midnightIndex = midnightLeaderboard?.findIndex( - (item) => item.id === player.id, - ); - const midnightScore = - midnightIndex === undefined - ? player.score - : midnightLeaderboard![midnightIndex].score; - const midnightPosition = - midnightIndex === undefined ? player.position : midnightIndex + 1; - - return ( - - ); - }); + const items = players.map((player) => { + const reward = resolvedRegionRatingsInfo.rewards.find( + (reward) => + player.position >= reward.from_position && + player.position <= reward.to_position, + ); + const midnightIndex = midnightLeaderboard?.findIndex( + (item) => item.id === player.id, + ); + const midnightScore = + midnightIndex === undefined + ? player.score + : midnightLeaderboard![midnightIndex].score; + const midnightPosition = + midnightIndex === undefined ? player.position : midnightIndex + 1; return ( - - - - {items && ( - - {playersBefore > 0 && ( - - )} - {items} - {playersAfter > 0 && ( - - )} - - )} - + ); - }, + }); - autocomplete: autocompleteUsername, - }); - }, -); + return ( + + + + {items && ( + + {playersBefore > 0 && ( + + )} + {items} + {playersAfter > 0 && ( + + )} + + )} + + ); + }, + + autocomplete: autocompleteUsername, + }); +}); diff --git a/src/commands/searchClans.ts b/src/commands/searchClans.ts index 9b4860799..4b70afae5 100644 --- a/src/commands/searchClans.ts +++ b/src/commands/searchClans.ts @@ -9,38 +9,42 @@ import embedInfo from '../core/discord/embedInfo'; import { CommandRegistry } from '../events/interactionCreate'; import { ClanList } from '../types/clanList'; -export const searchClansCommand: CommandRegistry = { - inProduction: true, - inPublic: true, +export const searchClansCommand = new Promise((resolve) => { + resolve({ + inProduction: true, + inPublic: true, - command: new SlashCommandBuilder() - .setName('search-clans') - .setDescription('Search clans') - .addStringOption(addRegionChoices) - .addStringOption((option) => addClanChoices(option).setAutocomplete(false)) - .addIntegerOption((option) => - option - .setName('limit') - .setDescription('The size of the search result (default: 25)') - .setMinValue(1) - .setMaxValue(100), - ), + command: new SlashCommandBuilder() + .setName('search-clans') + .setDescription('Search clans') + .addStringOption(addRegionChoices) + .addStringOption((option) => + addClanChoices(option).setAutocomplete(false), + ) + .addIntegerOption((option) => + option + .setName('limit') + .setDescription('The size of the search result (default: 25)') + .setMinValue(1) + .setMaxValue(100), + ), - async handler(interaction) { - const server = interaction.options.getString('region') as Region; - const clan = interaction.options.getString('clan')!; - const limit = interaction.options.getInteger('limit') ?? 25; - const clanList = await fetchBlitz( - `https://api.wotblitz.${server}/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${clan}&limit=${limit}`, - ); + async handler(interaction) { + const server = interaction.options.getString('region') as Region; + const clan = interaction.options.getString('clan')!; + const limit = interaction.options.getInteger('limit') ?? 25; + const clanList = await fetchBlitz( + `https://api.wotblitz.${server}/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${clan}&limit=${limit}`, + ); - return embedInfo( - `Clan search for "${markdownEscape(clan)}" in ${REGION_NAMES[server]}`, - clanList.length === 0 - ? 'No clans found.' - : `\`\`\`\n${clanList - .map((clan) => `${clan.name} [${clan.tag}]`) - .join('\n')}\n\`\`\``, - ); - }, -}; + return embedInfo( + `Clan search for "${markdownEscape(clan)}" in ${REGION_NAMES[server]}`, + clanList.length === 0 + ? 'No clans found.' + : `\`\`\`\n${clanList + .map((clan) => `${clan.name} [${clan.tag}]`) + .join('\n')}\n\`\`\``, + ); + }, + }); +}); diff --git a/src/commands/searchPlayers.ts b/src/commands/searchPlayers.ts index 3d6037bcc..11f642735 100644 --- a/src/commands/searchPlayers.ts +++ b/src/commands/searchPlayers.ts @@ -12,45 +12,47 @@ import addUsernameChoices from '../core/discord/addUsernameChoices'; import embedInfo from '../core/discord/embedInfo'; import { CommandRegistry } from '../events/interactionCreate'; -export const searchPlayersCommand: CommandRegistry = { - inProduction: true, - inPublic: true, +export const searchPlayersCommand = new Promise((resolve) => { + resolve({ + inProduction: true, + inPublic: true, - command: new SlashCommandBuilder() - .setName('search-players') - .setDescription('Search players in a Blitz server') - .addStringOption(addRegionChoices) - .addStringOption((option) => - addUsernameChoices(option).setAutocomplete(false).setRequired(true), - ) - .addIntegerOption((option) => - option - .setName('limit') - .setDescription('The size of the search result (default: 25)') - .setMinValue(1) - .setMaxValue(100), - ), + command: new SlashCommandBuilder() + .setName('search-players') + .setDescription('Search players in a Blitz server') + .addStringOption(addRegionChoices) + .addStringOption((option) => + addUsernameChoices(option).setAutocomplete(false).setRequired(true), + ) + .addIntegerOption((option) => + option + .setName('limit') + .setDescription('The size of the search result (default: 25)') + .setMinValue(1) + .setMaxValue(100), + ), - async handler(interaction) { - const server = interaction.options.getString('region') as Region; - const name = interaction.options.getString('username')!; - const limit = interaction.options.getInteger('limit') ?? 25; - const trimmedSearch = name.trim(); - const players = usernamePattern.test(trimmedSearch) - ? await fetchBlitz( - `https://api.wotblitz.${server}/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodeURIComponent( - trimmedSearch, - )}&limit=${limit}`, - ) - : []; + async handler(interaction) { + const server = interaction.options.getString('region') as Region; + const name = interaction.options.getString('username')!; + const limit = interaction.options.getInteger('limit') ?? 25; + const trimmedSearch = name.trim(); + const players = usernamePattern.test(trimmedSearch) + ? await fetchBlitz( + `https://api.wotblitz.${server}/wotb/account/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${encodeURIComponent( + trimmedSearch, + )}&limit=${limit}`, + ) + : []; - return embedInfo( - `Player search for "${markdownEscape(name)}" in ${REGION_NAMES[server]}`, - `\`\`\`${ - players.length === 0 - ? 'No players found.' - : players.map((player) => player.nickname).join('\n') - }\`\`\``, - ); - }, -}; + return embedInfo( + `Player search for "${markdownEscape(name)}" in ${REGION_NAMES[server]}`, + `\`\`\`${ + players.length === 0 + ? 'No players found.' + : players.map((player) => player.nickname).join('\n') + }\`\`\``, + ); + }, + }); +}); diff --git a/src/commands/searchTanks.ts b/src/commands/searchTanks.ts index e06414c46..86dfa42ab 100644 --- a/src/commands/searchTanks.ts +++ b/src/commands/searchTanks.ts @@ -6,35 +6,39 @@ import addTankChoices from '../core/discord/addTankChoices'; import embedInfo from '../core/discord/embedInfo'; import { CommandRegistry } from '../events/interactionCreate'; -export const searchTanksCommand: CommandRegistry = { - inProduction: true, - inPublic: true, +export const searchTanksCommand = new Promise((resolve) => { + resolve({ + inProduction: true, + inPublic: true, - command: new SlashCommandBuilder() - .setName('search-tanks') - .setDescription('Search tanks') - .addStringOption((option) => addTankChoices(option).setAutocomplete(false)) - .addIntegerOption((option) => - option - .setName('limit') - .setDescription('The size of the search result (default: 25)') - .setMinValue(1) - .setMaxValue(100), - ), + command: new SlashCommandBuilder() + .setName('search-tanks') + .setDescription('Search tanks') + .addStringOption((option) => + addTankChoices(option).setAutocomplete(false), + ) + .addIntegerOption((option) => + option + .setName('limit') + .setDescription('The size of the search result (default: 25)') + .setMinValue(1) + .setMaxValue(100), + ), - async handler(interaction) { - const tank = interaction.options.getString('tank')!; - const limit = interaction.options.getInteger('limit') ?? 25; - const results = go(tank, await tankNames, { - limit, - keys: ['combined'], - }).map((result) => result.obj.original); + async handler(interaction) { + const tank = interaction.options.getString('tank')!; + const limit = interaction.options.getInteger('limit') ?? 25; + const results = go(tank, await tankNames, { + limit, + keys: ['combined'], + }).map((result) => result.obj.original); - return embedInfo( - `Tank search for "${markdownEscape(tank)}"`, - results.length === 0 - ? 'No tanks found.' - : `\`\`\`\n${results.join('\n')}\n\`\`\``, - ); - }, -}; + return embedInfo( + `Tank search for "${markdownEscape(tank)}"`, + results.length === 0 + ? 'No tanks found.' + : `\`\`\`\n${results.join('\n')}\n\`\`\``, + ); + }, + }); +}); diff --git a/src/commands/stats.tsx b/src/commands/stats.tsx index 27ca249c3..0e18962d7 100644 --- a/src/commands/stats.tsx +++ b/src/commands/stats.tsx @@ -27,7 +27,7 @@ import resolvePlayerFromCommand, { ResolvedPlayer, } from '../core/discord/resolvePlayerFromCommand'; import { StatFilters, filterStats } from '../core/statistics/filterStats'; -import { CommandRegistryPromisable } from '../events/interactionCreate'; +import { CommandRegistry } from '../events/interactionCreate'; async function render( { region, id }: ResolvedPlayer, @@ -57,50 +57,48 @@ async function render( ); } -export const statsCommand = new Promise( - async (resolve) => { - const command = await addPeriodicFilterOptions( - new SlashCommandBuilder() - .setName('stats') - .setDescription('Regular battles statistics'), - (option) => option.addStringOption(addUsernameChoices), - ); +export const statsCommand = new Promise(async (resolve) => { + const command = await addPeriodicFilterOptions( + new SlashCommandBuilder() + .setName('stats') + .setDescription('Regular battles statistics'), + (option) => option.addStringOption(addUsernameChoices), + ); - resolve({ - inProduction: true, - inPublic: true, + resolve({ + inProduction: true, + inPublic: true, - command, + command, - async handler(interaction) { - const player = await resolvePlayerFromCommand(interaction); - const period = resolvePeriodFromCommand(player.region, interaction); - const filters = await getFiltersFromCommand(interaction); - const path = commandToURL(interaction, { - ...player, - ...getCustomPeriodParams(interaction), - ...filters, - }); + async handler(interaction) { + const player = await resolvePlayerFromCommand(interaction); + const period = resolvePeriodFromCommand(player.region, interaction); + const filters = await getFiltersFromCommand(interaction); + const path = commandToURL(interaction, { + ...player, + ...getCustomPeriodParams(interaction), + ...filters, + }); - return Promise.all([ - render(player, period, filters), - buttonRefresh(interaction, path), - getBlitzStarsLinkButton(player.region, player.id), - ]); - }, + return Promise.all([ + render(player, period, filters), + buttonRefresh(interaction, path), + getBlitzStarsLinkButton(player.region, player.id), + ]); + }, - autocomplete: (interaction) => { - autocompleteUsername(interaction); - autocompleteTanks(interaction); - }, + autocomplete: (interaction) => { + autocompleteUsername(interaction); + autocompleteTanks(interaction); + }, - async button(interaction) { - const player = await resolvePlayerFromButton(interaction); - const period = resolvePeriodFromButton(player.region, interaction); - const filters = getFiltersFromButton(interaction); + async button(interaction) { + const player = await resolvePlayerFromButton(interaction); + const period = resolvePeriodFromButton(player.region, interaction); + const filters = getFiltersFromButton(interaction); - return await render(player, period, filters); - }, - }); - }, -); + return await render(player, period, filters); + }, + }); +}); diff --git a/src/commands/today.ts b/src/commands/today.ts index 81e82405b..cc7e0e30f 100644 --- a/src/commands/today.ts +++ b/src/commands/today.ts @@ -14,58 +14,56 @@ import resolvePeriodFromButton from '../core/discord/resolvePeriodFromButton'; import resolvePeriodFromCommand from '../core/discord/resolvePeriodFromCommand'; import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; -import { CommandRegistryPromisable } from '../events/interactionCreate'; +import { CommandRegistry } from '../events/interactionCreate'; import { renderBreakdown } from './breakdown'; -export const todayCommand = new Promise( - async (resolve) => { - const awaitedEncyclopediaInfo = await encyclopediaInfo; - const command = addFilterOptions( - new SlashCommandBuilder() - .setName('today') - .setDescription("Today's played tanks and statistics"), - awaitedEncyclopediaInfo, - ).addStringOption(addUsernameChoices); +export const todayCommand = new Promise(async (resolve) => { + const awaitedEncyclopediaInfo = await encyclopediaInfo; + const command = addFilterOptions( + new SlashCommandBuilder() + .setName('today') + .setDescription("Today's played tanks and statistics"), + awaitedEncyclopediaInfo, + ).addStringOption(addUsernameChoices); - resolve({ - inProduction: true, - inPublic: true, + resolve({ + inProduction: true, + inPublic: true, - command, + command, - async handler(interaction) { - const player = await resolvePlayerFromCommand(interaction); - const period = resolvePeriodFromCommand( - player.region, - interaction, - 'today', - ); - const filters = await getFiltersFromCommand(interaction); - const path = commandToURL(interaction, { - ...player, - ...getCustomPeriodParams(interaction, true), - ...filters, - }); + async handler(interaction) { + const player = await resolvePlayerFromCommand(interaction); + const period = resolvePeriodFromCommand( + player.region, + interaction, + 'today', + ); + const filters = await getFiltersFromCommand(interaction); + const path = commandToURL(interaction, { + ...player, + ...getCustomPeriodParams(interaction, true), + ...filters, + }); - return [ - ...(await renderBreakdown(player, period, filters)), - buttonPrimary(path, 'Refresh'), - await getBlitzStarsLinkButton(player.region, player.id), - ]; - }, + return [ + ...(await renderBreakdown(player, period, filters)), + buttonPrimary(path, 'Refresh'), + await getBlitzStarsLinkButton(player.region, player.id), + ]; + }, - autocomplete: (interaction) => { - autocompleteUsername(interaction); - autocompleteTanks(interaction); - }, + autocomplete: (interaction) => { + autocompleteUsername(interaction); + autocompleteTanks(interaction); + }, - async button(interaction) { - const player = await resolvePlayerFromButton(interaction); - const period = resolvePeriodFromButton(player.region, interaction); - const filters = getFiltersFromButton(interaction); + async button(interaction) { + const player = await resolvePlayerFromButton(interaction); + const period = resolvePeriodFromButton(player.region, interaction); + const filters = getFiltersFromButton(interaction); - return await renderBreakdown(player, period, filters); - }, - }); - }, -); + return await renderBreakdown(player, period, filters); + }, + }); +}); diff --git a/src/core/discord/createLocalizedCommand.ts b/src/core/discord/createLocalizedCommand.ts index 1efda3cf8..dbadcd776 100644 --- a/src/core/discord/createLocalizedCommand.ts +++ b/src/core/discord/createLocalizedCommand.ts @@ -1,14 +1,27 @@ -import { Locale, SlashCommandBuilder } from 'discord.js'; +import { + Locale, + SlashCommandBuilder, + SlashCommandSubcommandBuilder, + SlashCommandSubcommandGroupBuilder, +} from 'discord.js'; import { translator } from '../localization/translator'; import { localizationObject } from './localizationObject'; interface CreateLocalizedCommandSubcommand { subcommand: string; + modify?: (option: SlashCommandSubcommandBuilder) => void; +} + +interface CreateLocalizedCommandSubcommandGroup { + group: string; + modify?: (option: SlashCommandSubcommandGroupBuilder) => void; } export function createLocalizedCommand( command: string, - subcommands?: CreateLocalizedCommandSubcommand[], + sub?: + | CreateLocalizedCommandSubcommand[] + | CreateLocalizedCommandSubcommandGroup[], ) { const { translate } = translator(Locale.EnglishUS); const slashCommand = new SlashCommandBuilder() @@ -19,26 +32,51 @@ export function createLocalizedCommand( localizationObject(`bot.commands.${command}.description`), ); - if (subcommands) { - subcommands.map(({ subcommand }) => { - const subcommandNameLocalizations = localizationObject( - `bot.commands.${command}.subcommands.${subcommand}`, - ); - const subcommandDescriptionLocalizations = localizationObject( - `bot.commands.${command}.subcommands.${subcommand}.description`, - ); + if (sub) { + sub.map((subItem) => { + if ('subcommand' in subItem) { + const subcommandNameLocalizations = localizationObject( + `bot.commands.${command}.subcommands.${subItem.subcommand}`, + ); + const subcommandDescriptionLocalizations = localizationObject( + `bot.commands.${command}.subcommands.${subItem.subcommand}.description`, + ); - slashCommand.addSubcommand((option) => - option - .setName(subcommand) - .setNameLocalizations(subcommandNameLocalizations) - .setDescription( - translate( - `bot.commands.${command}.subcommands.${subcommand}.description`, - ), - ) - .setDescriptionLocalizations(subcommandDescriptionLocalizations), - ); + slashCommand.addSubcommand((option) => { + if (subItem.modify) subItem.modify(option); + + return option + .setName(subItem.subcommand) + .setNameLocalizations(subcommandNameLocalizations) + .setDescription( + translate( + `bot.commands.${command}.subcommands.${subItem.subcommand}.description`, + ), + ) + .setDescriptionLocalizations(subcommandDescriptionLocalizations); + }); + } else { + const subcommandNameLocalizations = localizationObject( + `bot.commands.${command}.groups.${subItem.group}`, + ); + const subcommandDescriptionLocalizations = localizationObject( + `bot.commands.${command}.groups.${subItem.group}.description`, + ); + + slashCommand.addSubcommandGroup((option) => { + if (subItem.modify) subItem.modify(option); + + return option + .setName(subItem.group) + .setNameLocalizations(subcommandNameLocalizations) + .setDescription( + translate( + `bot.commands.${command}.groups.${subItem.group}.description`, + ), + ) + .setDescriptionLocalizations(subcommandDescriptionLocalizations); + }); + } }); } diff --git a/src/core/discord/resolvePeriodFromButton.ts b/src/core/discord/resolvePeriodFromButton.ts index 5498b7553..ecaf7f155 100644 --- a/src/core/discord/resolvePeriodFromButton.ts +++ b/src/core/discord/resolvePeriodFromButton.ts @@ -30,7 +30,7 @@ export default function resolvePeriodFromButton( start = getTimeDaysAgo(region, endDaysAgoMax); end = getTimeDaysAgo(region, startDaysAgoMin); } else { - name = getPeriodOptionName(periodSubcommand); + name = getPeriodOptionName(periodSubcommand, interaction.locale); start = getPeriodStart(region, periodSubcommand); end = getPeriodNow(); } diff --git a/src/core/discord/resolvePeriodFromCommand.ts b/src/core/discord/resolvePeriodFromCommand.ts index fc09987aa..043c53140 100644 --- a/src/core/discord/resolvePeriodFromCommand.ts +++ b/src/core/discord/resolvePeriodFromCommand.ts @@ -1,14 +1,17 @@ -import { CacheType, ChatInputCommandInteraction } from 'discord.js'; +import { CacheType, ChatInputCommandInteraction, Locale } from 'discord.js'; import { Region } from '../../constants/regions'; import getPeriodNow from '../blitzkrieg/getPeriodNow'; import getPeriodStart from '../blitzkrieg/getPeriodStart'; import getTimeDaysAgo from '../blitzkrieg/getTimeDaysAgo'; +import { translator } from '../localization/translator'; import { PeriodSize, PeriodType } from './addPeriodSubCommands'; -export function getPeriodOptionName(period: PeriodSize) { - if (period === 'career') return 'Career'; - if (period === 'today') return 'Today'; - return `${period} days`; +export function getPeriodOptionName(period: PeriodSize, locale: Locale) { + const { translate } = translator(locale); + + if (period === 'career') return translate('bot.common.periods.career'); + if (period === 'today') return translate('bot.common.periods.today'); + return translate('bot.common.periods.days', [period]); } export interface ResolvedPeriod { @@ -39,7 +42,7 @@ export default function resolvePeriodFromCommand( start = getTimeDaysAgo(region, endDaysAgoMax); end = getTimeDaysAgo(region, startDaysAgoMin); } else { - name = getPeriodOptionName(periodSubcommand); + name = getPeriodOptionName(periodSubcommand, interaction.locale); start = getPeriodStart(region, periodSubcommand); end = getPeriodNow(); } diff --git a/src/core/localization/translator.ts b/src/core/localization/translator.ts index 0b55fb879..78714fbcd 100644 --- a/src/core/localization/translator.ts +++ b/src/core/localization/translator.ts @@ -5,9 +5,10 @@ export function translator(localeRaw: Locale) { const locale = locales.includes(localeRaw) ? localeRaw : Locale.EnglishUS; const strings = translations[locale]; - function translate(path: string): string { + function translate(path: string, literals?: string[]): string { const pathArray = path.split('.'); let fragment = strings; + let resolvedString: string; for (const pathItem of pathArray) { if (typeof fragment === 'string') { @@ -33,14 +34,26 @@ export function translator(localeRaw: Locale) { } if (typeof fragment === 'string') { - return fragment; + resolvedString = fragment; } else if (fragment.$) { - return fragment.$!; + resolvedString = fragment.$!; } else { throw new Error( `Unresolved tree ending for "${path}" in locale "${locale}"`, ); } + + if (literals) { + const chunks = resolvedString.split('%s'); + let embeddedString = ''; + + chunks.forEach((chunk, index) => { + if (index !== 0) embeddedString += literals[index - 1]; + embeddedString += chunk; + }); + } + + return resolvedString; } function t(paths: TemplateStringsArray, ...embeds: string[]) { diff --git a/src/events/interactionCreate/index.ts b/src/events/interactionCreate/index.ts index 81e6b9452..c90b78c6c 100644 --- a/src/events/interactionCreate/index.ts +++ b/src/events/interactionCreate/index.ts @@ -17,7 +17,6 @@ import discord from '../../../discord.json' assert { type: 'json' }; import { aboutCommand } from '../../commands/about'; import { breakdownCommand } from '../../commands/breakdown'; import { debugCommand } from '../../commands/debug'; -import { eligibleCommand } from '../../commands/eligible'; import { evolutionCommand } from '../../commands/evolution'; import { fullStatsCommand } from '../../commands/fullStats'; import { inactiveCommand } from '../../commands/inactive'; @@ -78,22 +77,18 @@ type CommandRegistryDefinitionDoesHandleInteraction = { interaction: ChatInputCommandInteraction, ) => InteractionReturnable; }; -export type CommandRegistryPromisable = CommandRegistryBase & +export type CommandRegistry = CommandRegistryBase & (CommandRegistryDefinitionBase & ( | CommandRegistryDefinitionHandlesInteraction | CommandRegistryDefinitionDoesHandleInteraction )); -export type CommandRegistry = - | CommandRegistryPromisable - | Promise; const rest = new REST().setToken(secrets.DISCORD_TOKEN); -export const COMMANDS_RAW: CommandRegistry[] = [ +export const COMMANDS_RAW: Promise[] = [ permissionsCommand, debugCommand, - eligibleCommand, aboutCommand, inactiveCommand, ownedTanksCommand, @@ -113,7 +108,7 @@ export const COMMANDS_RAW: CommandRegistry[] = [ ]; export const commands = Promise.allSettled(COMMANDS_RAW).then((rawCommands) => { - return rawCommands.reduce>( + return rawCommands.reduce>( (commands, registry) => { if (registry.status === 'rejected') return commands; if (isDev()) registry.value.command.setDefaultMemberPermissions(0); diff --git a/src/lang/en-US.json b/src/lang/en-US.json index 2fa78623d..fa80328a0 100644 --- a/src/lang/en-US.json +++ b/src/lang/en-US.json @@ -51,6 +51,13 @@ } } } + }, + + "periods": { + "career": "Career", + "today": "Today", + "days": "%s days", + "custom": "%s - %s days" } }, @@ -112,6 +119,23 @@ }, "uptime": "Uptime" } + }, + + "evolution": { + "$": "evolution", + "description": "Evolution of statistics", + + "groups": { + "player": { + "$": "player", + "description": "Player statistics" + }, + + "tank": { + "$": "tank", + "description": "Tank statistics" + } + } } } }, From e141a7ca3f499819484c7973350821415fa500b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 6 Mar 2024 15:35:14 -0600 Subject: [PATCH 29/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index 70bd479b1..7939f2b2c 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -49,6 +49,12 @@ } } } + }, + "periods": { + "career": "Career", + "today": "Today", + "days": "%s days", + "custom": "%s - %s days" } }, "commands": { @@ -105,6 +111,20 @@ }, "uptime": "Tiempo en línea" } + }, + "evolution": { + "$": "evolution", + "description": "Evolution of statistics", + "groups": { + "player": { + "$": "player", + "description": "Player statistics" + }, + "tank": { + "$": "tank", + "description": "Tank statistics" + } + } } } }, From 49d0dbe86d332c2e8ac7a002e30b1cec4220a958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 6 Mar 2024 15:38:53 -0600 Subject: [PATCH 30/90] feat: simpler command types --- src/events/interactionCreate/index.ts | 33 +++++++++++---------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/src/events/interactionCreate/index.ts b/src/events/interactionCreate/index.ts index c90b78c6c..3008bf026 100644 --- a/src/events/interactionCreate/index.ts +++ b/src/events/interactionCreate/index.ts @@ -53,38 +53,30 @@ export type InteractionReturnable = | InteractionIterableReturnable | Promise; -type CommandRegistryBase = { - command: - | SlashCommandBuilder - | SlashCommandSubcommandsOnlyBuilder - | Omit; -}; -type CommandRegistryDefinitionBase = { +interface CommandRegistryBase { inProduction: boolean; inPublic: boolean; inPreview?: boolean; + command: + | SlashCommandBuilder + | SlashCommandSubcommandsOnlyBuilder + | Omit; + autocomplete?: (interaction: AutocompleteInteraction) => void; button?: (interaction: ButtonInteraction) => InteractionReturnable; -}; -type CommandRegistryDefinitionHandlesInteraction = { +} +interface CommandRegistryImplicit extends CommandRegistryBase { handlesInteraction: true; handler: (interaction: ChatInputCommandInteraction) => void; -}; -type CommandRegistryDefinitionDoesHandleInteraction = { +} +interface CommandRegistryExplicit extends CommandRegistryBase { handlesInteraction?: false; handler: ( interaction: ChatInputCommandInteraction, ) => InteractionReturnable; -}; -export type CommandRegistry = CommandRegistryBase & - (CommandRegistryDefinitionBase & - ( - | CommandRegistryDefinitionHandlesInteraction - | CommandRegistryDefinitionDoesHandleInteraction - )); - -const rest = new REST().setToken(secrets.DISCORD_TOKEN); +} +export type CommandRegistry = CommandRegistryImplicit | CommandRegistryExplicit; export const COMMANDS_RAW: Promise[] = [ permissionsCommand, @@ -118,6 +110,7 @@ export const commands = Promise.allSettled(COMMANDS_RAW).then((rawCommands) => { ); }); +const rest = new REST().setToken(secrets.DISCORD_TOKEN); export const guildCommands: RESTPostAPIChatInputApplicationCommandsJSONBody[] = []; export const publicCommands: RESTPostAPIChatInputApplicationCommandsJSONBody[] = From 83e31e6352b7ff5e5ea52216d73b48be097e1aaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 6 Mar 2024 16:11:07 -0600 Subject: [PATCH 31/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index 7939f2b2c..97f4f264d 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -1,4 +1,28 @@ { + "common": { + "nations": { + "ussr": "USSR", + "germany": "Germany", + "usa": "USA", + "china": "China", + "uk": "Britain", + "france": "France", + "japan": "Japan", + "european": "Europe", + "other": "Hybrid" + }, + "tank_type_short": { + "lightTank": "Light", + "mediumTank": "Medium", + "heavyTank": "Heavy", + "AT-SPG": "TD" + }, + "tree_type": { + "researchable": "Tech tree", + "premium": "Premium", + "collector": "Collector" + } + }, "bot": { "common": { "wn8": { @@ -55,6 +79,10 @@ "today": "Today", "days": "%s days", "custom": "%s - %s days" + }, + "filters": { + "none": "No filters", + "tier": "Tier %s" } }, "commands": { From 96a9e5b938ee695642da89e0721d4f5e86d273d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 6 Mar 2024 16:11:08 -0600 Subject: [PATCH 32/90] feat: filter localizations --- src/commands/stats.tsx | 9 +++--- src/components/Tanks/components/Item.tsx | 1 + src/core/blitzkrieg/filtersToDescription.ts | 34 +++++++++++--------- src/core/discord/resolvePeriodFromCommand.ts | 7 ++-- src/core/localization/translator.ts | 4 ++- src/lang/en-US.json | 32 ++++++++++++++++++ 6 files changed, 65 insertions(+), 22 deletions(-) diff --git a/src/commands/stats.tsx b/src/commands/stats.tsx index 0e18962d7..ceca9cd0c 100644 --- a/src/commands/stats.tsx +++ b/src/commands/stats.tsx @@ -1,4 +1,4 @@ -import { SlashCommandBuilder } from 'discord.js'; +import { Locale, SlashCommandBuilder } from 'discord.js'; import AllStatsOverview from '../components/AllStatsOverview'; import CommandWrapper from '../components/CommandWrapper'; import NoData, { NoDataType } from '../components/NoData'; @@ -33,13 +33,14 @@ async function render( { region, id }: ResolvedPlayer, { start, end, name }: ResolvedPeriod, filters: StatFilters, + locale: Locale, ) { const { nickname } = await getAccountInfo(region, id); const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan; const clanImage = clan ? emblemIdToURL(clan.emblem_set_id) : undefined; const diffedTankStats = await getStatsInPeriod(region, id, start, end); const { stats, supplementary } = await filterStats(diffedTankStats, filters); - const filterDescriptions = await filtersToDescription(filters); + const filterDescriptions = await filtersToDescription(filters, locale); return ( @@ -82,7 +83,7 @@ export const statsCommand = new Promise(async (resolve) => { }); return Promise.all([ - render(player, period, filters), + render(player, period, filters, interaction.locale), buttonRefresh(interaction, path), getBlitzStarsLinkButton(player.region, player.id), ]); @@ -98,7 +99,7 @@ export const statsCommand = new Promise(async (resolve) => { const period = resolvePeriodFromButton(player.region, interaction); const filters = getFiltersFromButton(interaction); - return await render(player, period, filters); + return await render(player, period, filters, interaction.locale); }, }); }); diff --git a/src/components/Tanks/components/Item.tsx b/src/components/Tanks/components/Item.tsx index da22e336f..870abd89a 100644 --- a/src/components/Tanks/components/Item.tsx +++ b/src/components/Tanks/components/Item.tsx @@ -17,6 +17,7 @@ export const TREE_TYPES = ['researchable', 'premium', 'collector'] as const; export type TankType = (typeof TANK_TYPES)[number]; export type TreeType = (typeof TREE_TYPES)[number]; +// BIG TODO: REMOVE THIS AFTER LOCALIZATION export const TREE_TYPE_NAMES: Record = { researchable: 'Tech tree', premium: 'Premium', diff --git a/src/core/blitzkrieg/filtersToDescription.ts b/src/core/blitzkrieg/filtersToDescription.ts index ce5b5a8b0..122cab1e8 100644 --- a/src/core/blitzkrieg/filtersToDescription.ts +++ b/src/core/blitzkrieg/filtersToDescription.ts @@ -1,24 +1,28 @@ -import { TREE_TYPE_NAMES } from '../../components/Tanks'; -import { encyclopediaInfo } from '../blitz/encyclopediaInfo'; +import { Locale } from 'discord.js'; +import { translator } from '../localization/translator'; import { StatFilters } from '../statistics/filterStats'; import { TIER_ROMAN_NUMERALS, Tier, tankDefinitions } from './tankDefinitions'; -export async function filtersToDescription({ - nation, - tier, - tankType, - treeType, - tank, -}: StatFilters) { - const awaitedEncyclopediaInfo = await encyclopediaInfo; +export async function filtersToDescription( + { nation, tier, tankType, treeType, tank }: StatFilters, + locale: Locale, +) { + const { translate } = translator(locale); const awaitedTankDefinitions = await tankDefinitions; const info: string[] = []; + // TODO: somehow localize tank names? if (tank) return awaitedTankDefinitions[tank].name; - if (nation) info.push(awaitedEncyclopediaInfo.vehicle_nations[nation]); - if (tier) info.push(`Tier ${TIER_ROMAN_NUMERALS[tier as Tier]}`); - if (tankType) info.push(awaitedEncyclopediaInfo.vehicle_types[tankType]); - if (treeType) info.push(TREE_TYPE_NAMES[treeType]); + if (nation) info.push(translate(`common.nations.${nation}`)); + if (tier) { + info.push( + translate('bot.common.filters.tier', [TIER_ROMAN_NUMERALS[tier as Tier]]), + ); + } + if (tankType) info.push(translate(`common.tank_type_short.${tankType}`)); + if (treeType) info.push(translate(`common.tree_type.${treeType}`)); - return info.length === 0 ? 'No filters' : info.join(', '); + return info.length === 0 + ? translate('bot.common.filters.none') + : info.join(', '); } diff --git a/src/core/discord/resolvePeriodFromCommand.ts b/src/core/discord/resolvePeriodFromCommand.ts index 043c53140..6681fd51f 100644 --- a/src/core/discord/resolvePeriodFromCommand.ts +++ b/src/core/discord/resolvePeriodFromCommand.ts @@ -8,7 +8,6 @@ import { PeriodSize, PeriodType } from './addPeriodSubCommands'; export function getPeriodOptionName(period: PeriodSize, locale: Locale) { const { translate } = translator(locale); - if (period === 'career') return translate('bot.common.periods.career'); if (period === 'today') return translate('bot.common.periods.today'); return translate('bot.common.periods.days', [period]); @@ -25,6 +24,7 @@ export default function resolvePeriodFromCommand( interaction: ChatInputCommandInteraction, forcedPeriod?: PeriodType, ) { + const { translate } = translator(interaction.locale); let name: string; let start: number; let end: number; @@ -38,7 +38,10 @@ export default function resolvePeriodFromCommand( const startDaysAgoMin = Math.min(startOption, endOption); const endDaysAgoMax = Math.max(startOption, endOption); - name = `${startDaysAgoMin} - ${endDaysAgoMax} days`; + name = translate('bot.common.periods.custom', [ + `${startDaysAgoMin}`, + `${endDaysAgoMax}`, + ]); start = getTimeDaysAgo(region, endDaysAgoMax); end = getTimeDaysAgo(region, startDaysAgoMin); } else { diff --git a/src/core/localization/translator.ts b/src/core/localization/translator.ts index 78714fbcd..c53877a93 100644 --- a/src/core/localization/translator.ts +++ b/src/core/localization/translator.ts @@ -27,7 +27,7 @@ export function translator(localeRaw: Locale) { console.warn( `Undefined translation at "${pathItem}" in "${path}" for locale "${locale}"; falling back to en-US`, ); - return translator(Locale.EnglishUS).translate(path); + return translator(Locale.EnglishUS).translate(path, literals); } } } @@ -51,6 +51,8 @@ export function translator(localeRaw: Locale) { if (index !== 0) embeddedString += literals[index - 1]; embeddedString += chunk; }); + + return embeddedString; } return resolvedString; diff --git a/src/lang/en-US.json b/src/lang/en-US.json index fa80328a0..be0637bba 100644 --- a/src/lang/en-US.json +++ b/src/lang/en-US.json @@ -1,4 +1,31 @@ { + "common": { + "nations": { + "ussr": "USSR", + "germany": "Germany", + "usa": "USA", + "china": "China", + "uk": "Britain", + "france": "France", + "japan": "Japan", + "european": "Europe", + "other": "Hybrid" + }, + + "tank_type_short": { + "lightTank": "Light", + "mediumTank": "Medium", + "heavyTank": "Heavy", + "AT-SPG": "TD" + }, + + "tree_type": { + "researchable": "Tech tree", + "premium": "Premium", + "collector": "Collector" + } + }, + "bot": { "common": { "wn8": { @@ -58,6 +85,11 @@ "today": "Today", "days": "%s days", "custom": "%s - %s days" + }, + + "filters": { + "none": "No filters", + "tier": "Tier %s" } }, From 716b446272832f3e9ee64b33177d4e165f243a3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 6 Mar 2024 16:19:29 -0600 Subject: [PATCH 33/90] fix: remove refresh button if not enough permissions --- src/commands/breakdown.tsx | 22 +++++++++++++++++----- src/commands/evolution.tsx | 4 ++-- src/commands/fullStats.tsx | 9 +++++---- src/commands/today.ts | 8 ++++---- src/core/discord/buttonRefresh.ts | 4 ++-- 5 files changed, 30 insertions(+), 17 deletions(-) diff --git a/src/commands/breakdown.tsx b/src/commands/breakdown.tsx index e635142b8..3c0e46ae5 100644 --- a/src/commands/breakdown.tsx +++ b/src/commands/breakdown.tsx @@ -1,3 +1,4 @@ +import { Locale } from 'discord.js'; import { chunk } from 'lodash'; import * as Breakdown from '../components/Breakdown'; import CommandWrapper from '../components/CommandWrapper'; @@ -18,7 +19,7 @@ import addPeriodicFilterOptions from '../core/discord/addPeriodicFilterOptions'; import addUsernameChoices from '../core/discord/addUsernameChoices'; import autocompleteTanks from '../core/discord/autocompleteTanks'; import autocompleteUsername from '../core/discord/autocompleteUsername'; -import buttonPrimary from '../core/discord/buttonPrimary'; +import { buttonRefresh } from '../core/discord/buttonRefresh'; import commandToURL from '../core/discord/commandToURL'; import { createLocalizedCommand } from '../core/discord/createLocalizedCommand'; import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams'; @@ -44,6 +45,7 @@ export async function renderBreakdown( { region, id }: ResolvedPlayer, { start, end, name }: ResolvedPeriod, filters: StatFilters, + locale: Locale, ) { const awaitedTankDefinitions = await tankDefinitions; const awaitedTankAverages = await tankAverages; @@ -52,7 +54,7 @@ export async function renderBreakdown( const accountInfo = await getAccountInfo(region, id); const clanData = await getClanAccountInfo(region, id, ['clan']); const tankStats = await getTankStats(region, id); - const filterDescriptions = await filtersToDescription(filters); + const filterDescriptions = await filtersToDescription(filters, locale); const orderedCurrentStats: AllStats[] = []; const orderedCareerStats: AllStats[] = []; const orderedCurrentWN8: (number | undefined)[] = []; @@ -312,8 +314,13 @@ export const breakdownCommand = new Promise( }); return [ - ...(await renderBreakdown(player, period, filters)), - buttonPrimary(path, 'Refresh'), + ...(await renderBreakdown( + player, + period, + filters, + interaction.locale, + )), + buttonRefresh(interaction, path), await getBlitzStarsLinkButton(player.region, player.id), ]; }, @@ -328,7 +335,12 @@ export const breakdownCommand = new Promise( const period = resolvePeriodFromButton(player.region, interaction); const filters = getFiltersFromButton(interaction); - return await renderBreakdown(player, period, filters); + return await renderBreakdown( + player, + period, + filters, + interaction.locale, + ); }, }); }, diff --git a/src/commands/evolution.tsx b/src/commands/evolution.tsx index 8bc87cfe7..ffc3abf4e 100644 --- a/src/commands/evolution.tsx +++ b/src/commands/evolution.tsx @@ -15,7 +15,7 @@ import addTankChoices from '../core/discord/addTankChoices'; import addUsernameChoices from '../core/discord/addUsernameChoices'; import autocompleteTanks from '../core/discord/autocompleteTanks'; import autocompleteUsername from '../core/discord/autocompleteUsername'; -import buttonPrimary from '../core/discord/buttonPrimary'; +import { buttonRefresh } from '../core/discord/buttonRefresh'; import commandToURL from '../core/discord/commandToURL'; import { createLocalizedCommand } from '../core/discord/createLocalizedCommand'; import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams'; @@ -148,7 +148,7 @@ export const evolutionCommand = new Promise( return [ await render(player, period, commandGroup, tankId), - buttonPrimary(path, 'Refresh'), + buttonRefresh(interaction, path), await getBlitzStarsLinkButton( player.region, player.id, diff --git a/src/commands/fullStats.tsx b/src/commands/fullStats.tsx index bb7e1f37f..1026c5a79 100644 --- a/src/commands/fullStats.tsx +++ b/src/commands/fullStats.tsx @@ -1,4 +1,4 @@ -import { SlashCommandBuilder } from 'discord.js'; +import { Locale, SlashCommandBuilder } from 'discord.js'; import CommandWrapper from '../components/CommandWrapper'; import GenericAllStats from '../components/GenericAllStats'; import NoData, { NoDataType } from '../components/NoData'; @@ -35,6 +35,7 @@ async function render( { region, id }: ResolvedPlayer, { start, end, name }: ResolvedPeriod, filters: StatFilters, + locale: Locale, ) { const { nickname } = await getAccountInfo(region, id); const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan; @@ -44,7 +45,7 @@ async function render( diffedTankStats, filters, ); - const filterDescriptions = await filtersToDescription(filters); + const filterDescriptions = await filtersToDescription(filters, locale); const tierWeights = await getTierWeights(diffedTankStats.diff, filteredOrder); return ( @@ -90,7 +91,7 @@ export const fullStatsCommand = new Promise( }); return Promise.all([ - render(player, period, filters), + render(player, period, filters, interaction.locale), buttonRefresh(interaction, path), getBlitzStarsLinkButton(player.region, player.id), ]); @@ -106,7 +107,7 @@ export const fullStatsCommand = new Promise( const period = resolvePeriodFromButton(player.region, interaction); const filters = getFiltersFromButton(interaction); - return await render(player, period, filters); + return await render(player, period, filters, interaction.locale); }, }); }, diff --git a/src/commands/today.ts b/src/commands/today.ts index cc7e0e30f..6e5e27b91 100644 --- a/src/commands/today.ts +++ b/src/commands/today.ts @@ -5,7 +5,7 @@ import { addFilterOptions } from '../core/discord/addFilterOptions'; import addUsernameChoices from '../core/discord/addUsernameChoices'; import autocompleteTanks from '../core/discord/autocompleteTanks'; import autocompleteUsername from '../core/discord/autocompleteUsername'; -import buttonPrimary from '../core/discord/buttonPrimary'; +import { buttonRefresh } from '../core/discord/buttonRefresh'; import commandToURL from '../core/discord/commandToURL'; import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams'; import { getFiltersFromButton } from '../core/discord/getFiltersFromButton'; @@ -47,8 +47,8 @@ export const todayCommand = new Promise(async (resolve) => { }); return [ - ...(await renderBreakdown(player, period, filters)), - buttonPrimary(path, 'Refresh'), + ...(await renderBreakdown(player, period, filters, interaction.locale)), + buttonRefresh(interaction, path), await getBlitzStarsLinkButton(player.region, player.id), ]; }, @@ -63,7 +63,7 @@ export const todayCommand = new Promise(async (resolve) => { const period = resolvePeriodFromButton(player.region, interaction); const filters = getFiltersFromButton(interaction); - return await renderBreakdown(player, period, filters); + return await renderBreakdown(player, period, filters, interaction.locale); }, }); }); diff --git a/src/core/discord/buttonRefresh.ts b/src/core/discord/buttonRefresh.ts index 0a210a006..b33f60e44 100644 --- a/src/core/discord/buttonRefresh.ts +++ b/src/core/discord/buttonRefresh.ts @@ -6,10 +6,10 @@ export function buttonRefresh( path: string, ) { if ( - interaction.appPermissions?.has('AttachFiles') && + interaction.appPermissions?.has('ReadMessageHistory') && interaction.appPermissions?.has('ViewChannel') ) { - return buttonPrimary(path, 'Refresh'); + return buttonPrimary(path, 'Refresh', '🔄️'); } return null; From bd6f1f35709ecff7361e48f8328a354ac5e9c046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 6 Mar 2024 16:26:36 -0600 Subject: [PATCH 34/90] feat: no data translations --- src/commands/breakdown.tsx | 4 ++-- src/commands/evolution.tsx | 16 ++++++++++++---- src/commands/fullStats.tsx | 6 ++++-- src/commands/inactive.tsx | 6 ++++-- src/commands/ownedTanks.tsx | 4 ++-- src/commands/stats.tsx | 4 ++-- src/components/NoData.tsx | 15 +++++++-------- src/lang/en-US.json | 6 ++++++ 8 files changed, 39 insertions(+), 22 deletions(-) diff --git a/src/commands/breakdown.tsx b/src/commands/breakdown.tsx index 3c0e46ae5..9a7ed439a 100644 --- a/src/commands/breakdown.tsx +++ b/src/commands/breakdown.tsx @@ -2,7 +2,7 @@ import { Locale } from 'discord.js'; import { chunk } from 'lodash'; import * as Breakdown from '../components/Breakdown'; import CommandWrapper from '../components/CommandWrapper'; -import NoData, { NoDataType } from '../components/NoData'; +import NoData from '../components/NoData'; import TitleBar from '../components/TitleBar'; import { AllStats, getAccountInfo } from '../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo'; @@ -286,7 +286,7 @@ export async function renderBreakdown( description={`${name} • ${filterDescriptions}`} /> - + , ]; } diff --git a/src/commands/evolution.tsx b/src/commands/evolution.tsx index ffc3abf4e..fd6a61cdd 100644 --- a/src/commands/evolution.tsx +++ b/src/commands/evolution.tsx @@ -1,7 +1,7 @@ -import { SlashCommandSubcommandGroupBuilder } from 'discord.js'; +import { Locale, SlashCommandSubcommandGroupBuilder } from 'discord.js'; import CommandWrapper from '../components/CommandWrapper'; import * as Graph from '../components/Graph'; -import NoData, { NoDataType } from '../components/NoData'; +import NoData from '../components/NoData'; import TitleBar from '../components/TitleBar'; import { getAccountInfo } from '../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo'; @@ -36,6 +36,7 @@ async function render( { start, end, name }: ResolvedPeriod, type: 'player' | 'tank', tankId: number | null, + locale: Locale, ) { const accountInfo = await getAccountInfo(region, id); const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan; @@ -100,7 +101,7 @@ async function render( )} - {plot.length === 0 && } + {plot.length === 0 && } ); } @@ -147,7 +148,13 @@ export const evolutionCommand = new Promise( }); return [ - await render(player, period, commandGroup, tankId), + await render( + player, + period, + commandGroup, + tankId, + interaction.locale, + ), buttonRefresh(interaction, path), await getBlitzStarsLinkButton( player.region, @@ -174,6 +181,7 @@ export const evolutionCommand = new Promise( period, commandGroup, parseInt(url.searchParams.get('tankId') ?? '0') || null, + interaction.locale, ); }, }); diff --git a/src/commands/fullStats.tsx b/src/commands/fullStats.tsx index 1026c5a79..5a3da27a0 100644 --- a/src/commands/fullStats.tsx +++ b/src/commands/fullStats.tsx @@ -1,7 +1,7 @@ import { Locale, SlashCommandBuilder } from 'discord.js'; import CommandWrapper from '../components/CommandWrapper'; import GenericAllStats from '../components/GenericAllStats'; -import NoData, { NoDataType } from '../components/NoData'; +import NoData from '../components/NoData'; import TierWeights from '../components/TierWeights'; import TitleBar from '../components/TitleBar'; import { getAccountInfo } from '../core/blitz/getAccountInfo'; @@ -56,7 +56,9 @@ async function render( description={`${name} • ${filterDescriptions}`} /> - {stats.battles === 0 && } + {stats.battles === 0 && ( + + )} {stats.battles > 0 && } {stats.battles > 0 && ( diff --git a/src/commands/inactive.tsx b/src/commands/inactive.tsx index f5a4807f5..39d803123 100644 --- a/src/commands/inactive.tsx +++ b/src/commands/inactive.tsx @@ -1,7 +1,7 @@ import { SlashCommandBuilder } from 'discord.js'; import CommandWrapper from '../components/CommandWrapper'; import GenericStats from '../components/GenericStats'; -import NoData, { NoDataType } from '../components/NoData'; +import NoData from '../components/NoData'; import TitleBar from '../components/TitleBar'; import { getAccountInfo } from '../core/blitz/getAccountInfo'; import { getClanInfo } from '../core/blitz/getClanInfo'; @@ -61,7 +61,9 @@ export const inactiveCommand = new Promise((resolve) => { description={`Inactive for ${threshold}+ Days • ${new Date().toDateString()}`} /> - {!hasInactiveMembers && } + {!hasInactiveMembers && ( + + )} {hasInactiveMembers && } ); diff --git a/src/commands/ownedTanks.tsx b/src/commands/ownedTanks.tsx index 4c8640c7a..c88dcb07a 100644 --- a/src/commands/ownedTanks.tsx +++ b/src/commands/ownedTanks.tsx @@ -1,6 +1,6 @@ import { SlashCommandBuilder } from 'discord.js'; import CommandWrapper from '../components/CommandWrapper'; -import NoData, { NoDataType } from '../components/NoData'; +import NoData from '../components/NoData'; import * as Tanks from '../components/Tanks'; import TitleBar from '../components/TitleBar'; import { encyclopediaInfo } from '../core/blitz/encyclopediaInfo'; @@ -74,7 +74,7 @@ export const ownedTanksCommand = new Promise((resolve) => { /> {filteredTanks.length === 0 && ( - + )} {filteredTanks.length > 0 && diff --git a/src/commands/stats.tsx b/src/commands/stats.tsx index ceca9cd0c..71f24f588 100644 --- a/src/commands/stats.tsx +++ b/src/commands/stats.tsx @@ -1,7 +1,7 @@ import { Locale, SlashCommandBuilder } from 'discord.js'; import AllStatsOverview from '../components/AllStatsOverview'; import CommandWrapper from '../components/CommandWrapper'; -import NoData, { NoDataType } from '../components/NoData'; +import NoData from '../components/NoData'; import TitleBar from '../components/TitleBar'; import { getAccountInfo } from '../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo'; @@ -50,7 +50,7 @@ async function render( description={`${name} • ${filterDescriptions}`} /> - {!stats.battles && } + {!stats.battles && } {stats.battles > 0 && ( )} diff --git a/src/components/NoData.tsx b/src/components/NoData.tsx index 3bf47967b..3f28d8fdf 100644 --- a/src/components/NoData.tsx +++ b/src/components/NoData.tsx @@ -1,3 +1,5 @@ +import { Locale } from 'discord.js'; +import { translator } from '../core/localization/translator'; import { theme } from '../stitches.config'; export enum NoDataType { @@ -7,16 +9,13 @@ export enum NoDataType { } export interface NoDataProps { - type: NoDataType; + type: 'battles_in_period' | 'players_in_period' | 'tanks_found'; + locale: Locale; } -const NO_DATA_MESSAGES: Record = { - [NoDataType.BattlesInPeriod]: 'No battles played in this period', - [NoDataType.PlayersInPeriod]: 'No players in this period', - [NoDataType.TanksFound]: 'No tanks found', -}; +export default function NoData({ type, locale }: NoDataProps) { + const { translate } = translator(locale); -export default function NoData({ type }: NoDataProps) { return (
- {NO_DATA_MESSAGES[type]} + {translate(`bot.common.no_data.${type}`)}
); diff --git a/src/lang/en-US.json b/src/lang/en-US.json index be0637bba..efdf3ff25 100644 --- a/src/lang/en-US.json +++ b/src/lang/en-US.json @@ -90,6 +90,12 @@ "filters": { "none": "No filters", "tier": "Tier %s" + }, + + "no_data": { + "battles_in_period": "No battles played in this period", + "players_in_period": "No players in this period", + "tanks_found": "No tanks found" } }, From bc0865680619d0353f4a546a46bde180cc84fdf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:13:57 -0600 Subject: [PATCH 35/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index 97f4f264d..d67b03efd 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -83,6 +83,11 @@ "filters": { "none": "No filters", "tier": "Tier %s" + }, + "no_data": { + "battles_in_period": "No battles played in this period", + "players_in_period": "No players in this period", + "tanks_found": "No tanks found" } }, "commands": { From 472172c50411ffeb29f0a2f5716ecf7dd8cd1a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:18:51 -0600 Subject: [PATCH 36/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index d67b03efd..3c93a362b 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -74,6 +74,12 @@ } } }, + "options": { + "clan": { + "$": "clan", + "description": "The clan name or tag" + } + }, "periods": { "career": "Career", "today": "Today", @@ -158,6 +164,20 @@ "description": "Tank statistics" } } + }, + "inactive": { + "$": "inactive", + "description": "Lists all inactive players in a clan above a threshold", + "options": { + "threshold": { + "$": "threshold", + "description": "The number of days inactive (default: %s days)" + } + }, + "body": { + "subtitle": "Inactive for %s+ days", + "listing": "%s days" + } } } }, From 9f4c8c90883848d483552385172edb6ee400f86b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:19:00 -0600 Subject: [PATCH 37/90] feat: inactive command localization --- src/commands/inactive.tsx | 39 +++++++++++++++++++++--------- src/core/discord/addClanChoices.ts | 12 +++++++-- src/lang/en-US.json | 24 ++++++++++++++++++ 3 files changed, 61 insertions(+), 14 deletions(-) diff --git a/src/commands/inactive.tsx b/src/commands/inactive.tsx index 39d803123..d5f3d78ac 100644 --- a/src/commands/inactive.tsx +++ b/src/commands/inactive.tsx @@ -1,4 +1,4 @@ -import { SlashCommandBuilder } from 'discord.js'; +import { Locale } from 'discord.js'; import CommandWrapper from '../components/CommandWrapper'; import GenericStats from '../components/GenericStats'; import NoData from '../components/NoData'; @@ -7,7 +7,10 @@ import { getAccountInfo } from '../core/blitz/getAccountInfo'; import { getClanInfo } from '../core/blitz/getClanInfo'; import addClanChoices from '../core/discord/addClanChoices'; import autocompleteClan from '../core/discord/autocompleteClan'; +import { createLocalizedCommand } from '../core/discord/createLocalizedCommand'; +import { localizationObject } from '../core/discord/localizationObject'; import resolveClanFromCommand from '../core/discord/resolveClanFromCommand'; +import { translator } from '../core/localization/translator'; import { CommandRegistry } from '../events/interactionCreate'; const DEFAULT_THRESHOLD = 7; @@ -17,20 +20,29 @@ export const inactiveCommand = new Promise((resolve) => { inProduction: true, inPublic: true, - command: new SlashCommandBuilder() - .setName('inactive') - .setDescription('Lists all inactive players') + command: createLocalizedCommand('inactive') .addStringOption(addClanChoices) - .addNumberOption((option) => - option + .addNumberOption((option) => { + const { translate } = translator(Locale.EnglishUS); + + return option .setName('threshold') - .setDescription( - `The number of days inactive (default: ${DEFAULT_THRESHOLD})`, + .setNameLocalizations( + localizationObject('bot.commands.inactive.options.threshold'), ) - .setMinValue(0), - ), + .setDescription( + translate('bot.commands.inactive.options.threshold.description'), + ) + .setDescriptionLocalizations( + localizationObject( + 'bot.commands.inactive.options.threshold.description', + ), + ) + .setMinValue(0); + }), async handler(interaction) { + const { translate } = translator(interaction.locale); const { region, id } = await resolveClanFromCommand(interaction); const threshold = interaction.options.getNumber('threshold')! ?? DEFAULT_THRESHOLD; @@ -49,7 +61,10 @@ export const inactiveCommand = new Promise((resolve) => { .sort((a, b) => b[1] - a[1]) .map( ([name, days]) => - [name, `${days.toFixed(0)} days`] as [string, string], + [ + name, + translate('bot.commands.inactive.body.listing', [`${days}`]), + ] as [string, string], ); const hasInactiveMembers = inactive.length > 0; @@ -58,7 +73,7 @@ export const inactiveCommand = new Promise((resolve) => { {!hasInactiveMembers && ( diff --git a/src/core/discord/addClanChoices.ts b/src/core/discord/addClanChoices.ts index f25c29156..7e3a46be3 100644 --- a/src/core/discord/addClanChoices.ts +++ b/src/core/discord/addClanChoices.ts @@ -1,9 +1,17 @@ -import { SlashCommandStringOption } from 'discord.js'; +import { Locale, SlashCommandStringOption } from 'discord.js'; +import { translator } from '../localization/translator'; +import { localizationObject } from './localizationObject'; export default function addClanChoices(option: SlashCommandStringOption) { + const { translate } = translator(Locale.EnglishUS); + return option .setName('clan') - .setDescription('The clan name or tag you are checking') + .setNameLocalizations(localizationObject('bot.common.options.clan')) + .setDescription(translate('bot.common.options.clan.description')) + .setDescriptionLocalizations( + localizationObject('bot.common.options.clan.description'), + ) .setAutocomplete(true) .setMinLength(2) .setRequired(true); diff --git a/src/lang/en-US.json b/src/lang/en-US.json index efdf3ff25..ec8223263 100644 --- a/src/lang/en-US.json +++ b/src/lang/en-US.json @@ -80,6 +80,13 @@ } }, + "options": { + "clan": { + "$": "clan", + "description": "The clan name or tag" + } + }, + "periods": { "career": "Career", "today": "Today", @@ -174,6 +181,23 @@ "description": "Tank statistics" } } + }, + + "inactive": { + "$": "inactive", + "description": "Lists all inactive players in a clan above a threshold", + + "options": { + "threshold": { + "$": "threshold", + "description": "The number of days inactive (default: %s days)" + } + }, + + "body": { + "subtitle": "Inactive for %s+ days", + "listing": "%s days" + } } } }, From a4a5d53f66e287f27ec161b0040c995c2d025fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:39:44 -0600 Subject: [PATCH 38/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 74 ++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index 3c93a362b..76a031ec0 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -1,26 +1,26 @@ { "common": { "nations": { - "ussr": "USSR", - "germany": "Germany", - "usa": "USA", + "ussr": "URSS", + "germany": "Alemania", + "usa": "EUA", "china": "China", - "uk": "Britain", - "france": "France", - "japan": "Japan", - "european": "Europe", - "other": "Hybrid" + "uk": "Reino Unido", + "france": "Francia", + "japan": "Japón", + "european": "Europa", + "other": "Híbrido" }, "tank_type_short": { - "lightTank": "Light", - "mediumTank": "Medium", - "heavyTank": "Heavy", - "AT-SPG": "TD" + "lightTank": "Ligero", + "mediumTank": "Medio", + "heavyTank": "Pesado", + "AT-SPG": "DT" }, "tree_type": { - "researchable": "Tech tree", - "premium": "Premium", - "collector": "Collector" + "researchable": "Árbol tecnológico", + "premium": "Prémium", + "collector": "Coleccionable" } }, "bot": { @@ -77,23 +77,23 @@ "options": { "clan": { "$": "clan", - "description": "The clan name or tag" + "description": "El nombre del clan o etiqueta" } }, "periods": { - "career": "Career", - "today": "Today", - "days": "%s days", - "custom": "%s - %s days" + "career": "Carrera", + "today": "Hoy", + "days": "%s días", + "custom": "%s - %s días" }, "filters": { - "none": "No filters", - "tier": "Tier %s" + "none": "Sin filtros", + "tier": "Rango %s" }, "no_data": { - "battles_in_period": "No battles played in this period", - "players_in_period": "No players in this period", - "tanks_found": "No tanks found" + "battles_in_period": "No hay batallas jugadas en este período", + "players_in_period": "No hay jugadores en este período", + "tanks_found": "No se encontraron tanques" } }, "commands": { @@ -152,31 +152,31 @@ } }, "evolution": { - "$": "evolution", - "description": "Evolution of statistics", + "$": "evolución", + "description": "Evolución de las estadísticas", "groups": { "player": { - "$": "player", - "description": "Player statistics" + "$": "jugador", + "description": "Estadísticas del jugador" }, "tank": { - "$": "tank", - "description": "Tank statistics" + "$": "tanque", + "description": "Estadísticas del tanque" } } }, "inactive": { - "$": "inactive", - "description": "Lists all inactive players in a clan above a threshold", + "$": "inactivo", + "description": "Lista todos los jugadores inactivos en un clan por encima de un umbral", "options": { "threshold": { - "$": "threshold", - "description": "The number of days inactive (default: %s days)" + "$": "umbral", + "description": "El número de días inactivo ( predeterminado: %s días)" } }, "body": { - "subtitle": "Inactive for %s+ days", - "listing": "%s days" + "subtitle": "Inactivo por %s+ días", + "listing": "%s días" } } } From d53b31ca407eca1d6eeb6b61606c1bab3f2b2b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:40:53 -0600 Subject: [PATCH 39/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index 76a031ec0..32ac577f6 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -178,6 +178,40 @@ "subtitle": "Inactivo por %s+ días", "listing": "%s días" } + }, + "full_stats": { + "$": "full-stats", + "description": "Full in-game statistics", + "body": { + "cards": { + "games": { + "$": "Games", + "winrate": "Winrate", + "battles": "Battles", + "wins_losses": "Wins / losses" + }, + "efficiency": { + "$": "Efficiency", + "wn8": "WN8", + "survival": "Survival", + "accuracy": "Accuracy" + }, + "lethality": { + "$": "Lethality", + "average_damage": "Average damage", + "damage_per_tier": "Damage per tier", + "damage_ratio": "Damage ratio", + "average_kills": "Average kills" + }, + "miscellaneous": { + "$": "Miscellaneous", + "average_tier": "Average tier", + "average_spots": "Average spots", + "average_xp": "Average XP", + "kills_to_death_ratio": "Kills to death ratio" + } + } + } } } }, From 5620b118f4e7d9b01ee8ff0cad787de0b7e16333 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 6 Mar 2024 17:41:05 -0600 Subject: [PATCH 40/90] feat: some full stats locale --- src/commands/fullStats.tsx | 6 +++- src/components/GenericAllStats/index.tsx | 41 +++++++++++++----------- src/lang/en-US.json | 36 +++++++++++++++++++++ 3 files changed, 64 insertions(+), 19 deletions(-) diff --git a/src/commands/fullStats.tsx b/src/commands/fullStats.tsx index 5a3da27a0..9fa3129c4 100644 --- a/src/commands/fullStats.tsx +++ b/src/commands/fullStats.tsx @@ -61,7 +61,11 @@ async function render( )} {stats.battles > 0 && } {stats.battles > 0 && ( - + )} ); diff --git a/src/components/GenericAllStats/index.tsx b/src/components/GenericAllStats/index.tsx index 21cbee368..c315011fc 100644 --- a/src/components/GenericAllStats/index.tsx +++ b/src/components/GenericAllStats/index.tsx @@ -1,4 +1,6 @@ +import { Locale } from 'discord.js'; import { AllStats, SupplementaryStats } from '../../core/blitz/getAccountInfo'; +import { translator } from '../../core/localization/translator'; import isNumber from '../../core/math/isNumber'; import getWN8Percentile from '../../core/statistics/getWN8Percentile'; import { Card } from './components/Card'; @@ -8,12 +10,15 @@ import { Row } from './components/Row'; export interface GenericAllStatsProps { stats: AllStats; supplementaryStats?: SupplementaryStats; + locale: Locale; } export default function GenericAllStats({ stats, supplementaryStats, + locale, }: GenericAllStatsProps) { + const { t } = translator(locale); const damageRatio = stats.damage_dealt / stats.damage_received; const killsToDeathRatio = stats.frags / (stats.battles - stats.survived_battles); @@ -22,27 +27,27 @@ export default function GenericAllStats({ Date: Wed, 6 Mar 2024 17:44:42 -0600 Subject: [PATCH 41/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index 32ac577f6..611746c4a 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -5,7 +5,7 @@ "germany": "Alemania", "usa": "EUA", "china": "China", - "uk": "Reino Unido", + "uk": "R. U.", "france": "Francia", "japan": "Japón", "european": "Europa", From 935c578ef6148150177cfe929dc534ffd9fa5d87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 6 Mar 2024 19:30:47 -0600 Subject: [PATCH 42/90] Update en-US.json --- src/lang/en-US.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lang/en-US.json b/src/lang/en-US.json index c3ed6e2ba..959c5812c 100644 --- a/src/lang/en-US.json +++ b/src/lang/en-US.json @@ -1,15 +1,15 @@ { "common": { "nations": { - "ussr": "USSR", + "ussr": "U.S.S.R.", "germany": "Germany", - "usa": "USA", + "usa": "U.S.A.", "china": "China", - "uk": "Britain", + "uk": "U.K.", "france": "France", "japan": "Japan", - "european": "Europe", - "other": "Hybrid" + "european": "European nation", + "other": "Hybrid nation" }, "tank_type_short": { From 4a6e94680904a9738e3290701ba86c0e1036ef58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 6 Mar 2024 19:30:57 -0600 Subject: [PATCH 43/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 56 ++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index 611746c4a..80889c3d9 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -1,19 +1,19 @@ { "common": { "nations": { - "ussr": "URSS", + "ussr": "U.S.S.R.", "germany": "Alemania", - "usa": "EUA", + "usa": "U.S.A.", "china": "China", - "uk": "R. U.", + "uk": "U.K.", "france": "Francia", "japan": "Japón", - "european": "Europa", - "other": "Híbrido" + "european": "European nation", + "other": "Hybrid nation" }, "tank_type_short": { "lightTank": "Ligero", - "mediumTank": "Medio", + "mediumTank": "Mediano", "heavyTank": "Pesado", "AT-SPG": "DT" }, @@ -153,7 +153,7 @@ }, "evolution": { "$": "evolución", - "description": "Evolución de las estadísticas", + "description": "Evolución de estadísticas", "groups": { "player": { "$": "jugador", @@ -167,11 +167,11 @@ }, "inactive": { "$": "inactivo", - "description": "Lista todos los jugadores inactivos en un clan por encima de un umbral", + "description": "Enumera a todos los jugadores inactivos en un clan por encima de un umbral", "options": { "threshold": { "$": "umbral", - "description": "El número de días inactivo ( predeterminado: %s días)" + "description": "El número de días inactivo (predeterminado: %s días)" } }, "body": { @@ -180,35 +180,35 @@ } }, "full_stats": { - "$": "full-stats", - "description": "Full in-game statistics", + "$": "estadísticas-completas", + "description": "Estadísticas completas del juego", "body": { "cards": { "games": { - "$": "Games", - "winrate": "Winrate", - "battles": "Battles", - "wins_losses": "Wins / losses" + "$": "Partidas", + "winrate": "Tasa de victorias", + "battles": "Batallas", + "wins_losses": "Victorias / derrotas" }, "efficiency": { - "$": "Efficiency", + "$": "Eficiencia", "wn8": "WN8", - "survival": "Survival", - "accuracy": "Accuracy" + "survival": "Supervivencia", + "accuracy": "Precisión" }, "lethality": { - "$": "Lethality", - "average_damage": "Average damage", - "damage_per_tier": "Damage per tier", - "damage_ratio": "Damage ratio", - "average_kills": "Average kills" + "$": "Letalidad", + "average_damage": "Daño promedio", + "damage_per_tier": "Daño por tier", + "damage_ratio": "Relación de daño", + "average_kills": "Promedio de bajas" }, "miscellaneous": { - "$": "Miscellaneous", - "average_tier": "Average tier", - "average_spots": "Average spots", - "average_xp": "Average XP", - "kills_to_death_ratio": "Kills to death ratio" + "$": "Misceláneo", + "average_tier": "Tier promedio", + "average_spots": "Promedio de detecciones", + "average_xp": "Promedio de XP", + "kills_to_death_ratio": "Relación bajas-muertes" } } } From de9077e0d2d009d426c834550b1bcfb9010daece Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Wed, 6 Mar 2024 20:44:06 -0600 Subject: [PATCH 44/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index 80889c3d9..f6c2c2426 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -1,26 +1,26 @@ { "common": { "nations": { - "ussr": "U.S.S.R.", + "ussr": "URSS", "germany": "Alemania", - "usa": "U.S.A.", + "usa": "EE. UU.", "china": "China", - "uk": "U.K.", + "uk": "Reino Unido", "france": "Francia", "japan": "Japón", - "european": "European nation", - "other": "Hybrid nation" + "european": "Nación europea", + "other": "Nación híbrida" }, "tank_type_short": { "lightTank": "Ligero", "mediumTank": "Mediano", "heavyTank": "Pesado", - "AT-SPG": "DT" + "AT-SPG": "Cazacarro" }, "tree_type": { "researchable": "Árbol tecnológico", "premium": "Prémium", - "collector": "Coleccionable" + "collector": "Coleccionista" } }, "bot": { @@ -77,7 +77,7 @@ "options": { "clan": { "$": "clan", - "description": "El nombre del clan o etiqueta" + "description": "El nombre o etiqueta del clan" } }, "periods": { @@ -88,7 +88,7 @@ }, "filters": { "none": "Sin filtros", - "tier": "Rango %s" + "tier": "Tier %s" }, "no_data": { "battles_in_period": "No hay batallas jugadas en este período", From 9437d16908c6600b17d776abd1f414a5e71a5c98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Thu, 7 Mar 2024 13:49:36 -0600 Subject: [PATCH 45/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index f6c2c2426..1f5b1209f 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -78,6 +78,24 @@ "clan": { "$": "clan", "description": "El nombre o etiqueta del clan" + }, + "tank": { + "$": "tank", + "description": "The tank to get stats for" + }, + "filters": { + "nation": { + "$": "nation", + "description": "Nation" + }, + "tank_type": { + "$": "tank-type", + "description": "Tank type" + }, + "tree_type": { + "$": "tree-type", + "description": "Tree type" + } } }, "periods": { From b60f8bfd5744f584828413f5f3718523b12f455e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Thu, 7 Mar 2024 13:49:38 -0600 Subject: [PATCH 46/90] feat: filter strings --- scripts/buildAssets/definitions.ts | 16 +- .../[id]/components/Characteristics/index.tsx | 22 +-- .../components/TankSearch/index.tsx | 5 +- src/commands/fullStats.tsx | 15 +- src/commands/today.ts | 6 +- src/components/Tanks/components/Item.tsx | 8 +- src/core/blitzkrieg/tankDefinitions.ts | 24 +-- src/core/discord/addFilterOptions.ts | 156 ++++++++++++------ src/core/discord/addPeriodicFilterOptions.ts | 10 +- src/core/discord/addTankChoices.ts | 14 +- src/core/discord/createLocalizedCommand.ts | 19 ++- src/lang/en-US.json | 20 +++ 12 files changed, 187 insertions(+), 128 deletions(-) diff --git a/scripts/buildAssets/definitions.ts b/scripts/buildAssets/definitions.ts index 41a7c5f08..1dbd313a1 100644 --- a/scripts/buildAssets/definitions.ts +++ b/scripts/buildAssets/definitions.ts @@ -34,7 +34,6 @@ import { import { DATA, POI } from './constants'; import { TankParameters } from './tankIcons'; -type BlitzTankType = 'AT-SPG' | 'lightTank' | 'mediumTank' | 'heavyTank'; interface Strings { [key: string]: string; } @@ -306,19 +305,6 @@ type ConsumablesVehicleFilter = | { name: string } | { extendedTags: string }; -const consumableProvisionEffectSuffixes = [ - 'Increase', - 'Factor', - 'Bias', - 'Coef', - 'Percent', -]; -const blitzTankTypeToBlitzkrieg: Record = { - 'AT-SPG': 'tankDestroyer', - lightTank: 'light', - mediumTank: 'medium', - heavyTank: 'heavy', -}; const blitzShellKindToBLitzkrieg: Record = { ARMOR_PIERCING: 'ap', ARMOR_PIERCING_CR: 'ap_cr', @@ -502,7 +488,7 @@ export async function definitions(production: boolean) { ? 'premium' : 'researchable', tier: tank.level as Tier, - type: blitzTankTypeToBlitzkrieg[tankTags[0] as BlitzTankType], + type: tankTags[0] as TankType, testing: tankTags.includes('testTank') ? true : undefined, price: tankPrice, camouflage: { diff --git a/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx b/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx index b7d4d3e17..dae69588d 100644 --- a/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx +++ b/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx @@ -136,12 +136,12 @@ export function Characteristics() { ]); const enginePowerCoefficient = coefficient( [ - hasEngineAccelerator && (tank.type === 'light' || tank.type === 'medium'), + hasEngineAccelerator && (tank.type === 'lightTank' || tank.type === 'mediumTank'), 0.05, ], [ hasEngineAccelerator && - (tank.type === 'heavy' || tank.type === 'tankDestroyer'), + (tank.type === 'heavyTank' || tank.type === 'AT-SPG'), 0.07, ], [hasEnginePowerBoost, 0.2], @@ -163,7 +163,7 @@ export function Characteristics() { const viewRangeCoefficient = coefficient([ hasImprovedOptics, - tank.type === 'tankDestroyer' ? 0.05 : tank.type === 'heavy' ? 0.07 : 0.1, + tank.type === 'AT-SPG' ? 0.05 : tank.type === 'heavyTank' ? 0.07 : 0.1, ]) * coefficient([true, progressiveStat(commanderMastery)]); const fireChanceCoefficient = coefficient([hasProtectiveKit, -0.2]); @@ -180,17 +180,17 @@ export function Characteristics() { const camouflageSumMoving = sum( [ hasCamouflageNet, - tank.type === 'heavy' + tank.type === 'heavyTank' ? 0.03 - : tank.type === 'tankDestroyer' + : tank.type === 'AT-SPG' ? 0.07 : 0.05, ], [ camouflage, - tank.type === 'tankDestroyer' + tank.type === 'AT-SPG' ? 0.04 - : tank.type === 'heavy' + : tank.type === 'heavyTank' ? 0.03 : 0.02, ], @@ -199,17 +199,17 @@ export function Characteristics() { [ hasCamouflageNet, 2 * - (tank.type === 'heavy' + (tank.type === 'heavyTank' ? 0.03 - : tank.type === 'tankDestroyer' + : tank.type === 'AT-SPG' ? 0.07 : 0.05), ], [ camouflage, - tank.type === 'tankDestroyer' + tank.type === 'AT-SPG' ? 0.04 - : tank.type === 'heavy' + : tank.type === 'heavyTank' ? 0.03 : 0.02, ], diff --git a/src/app/tools/tankopedia/components/TankSearch/index.tsx b/src/app/tools/tankopedia/components/TankSearch/index.tsx index 41a6f3a7d..6c5d00a10 100644 --- a/src/app/tools/tankopedia/components/TankSearch/index.tsx +++ b/src/app/tools/tankopedia/components/TankSearch/index.tsx @@ -15,7 +15,7 @@ import { } from '@radix-ui/themes'; import { go } from 'fuzzysort'; import { use, useEffect, useMemo, useRef, useState } from 'react'; -import { TankType, TreeType } from '../../../../../components/Tanks'; +import { TANK_TYPES, TreeType } from '../../../../../components/Tanks'; import { resolveNearPenetration } from '../../../../../core/blitz/resolveNearPenetration'; import { asset } from '../../../../../core/blitzkrieg/asset'; import { modelDefinitions } from '../../../../../core/blitzkrieg/modelDefinitions'; @@ -49,7 +49,6 @@ interface TankSearchProps { onSelect?: (tank: TankDefinition) => void; } -const tankTypeOrder: TankType[] = ['light', 'medium', 'heavy', 'tankDestroyer']; const treeTypeOrder: TreeType[] = ['researchable', 'premium', 'collector']; export function TankSearch({ compact, onSelect = () => {} }: TankSearchProps) { @@ -70,7 +69,7 @@ export function TankSearch({ compact, onSelect = () => {} }: TankSearchProps) { ) .sort( (a, b) => - (tankTypeOrder.indexOf(a.type) - tankTypeOrder.indexOf(b.type)) * + (TANK_TYPES.indexOf(a.type) - TANK_TYPES.indexOf(b.type)) * (sort.direction === 'descending' ? -1 : 1), ) .sort( diff --git a/src/commands/fullStats.tsx b/src/commands/fullStats.tsx index 9fa3129c4..df7d870d9 100644 --- a/src/commands/fullStats.tsx +++ b/src/commands/fullStats.tsx @@ -1,4 +1,4 @@ -import { Locale, SlashCommandBuilder } from 'discord.js'; +import { Locale } from 'discord.js'; import CommandWrapper from '../components/CommandWrapper'; import GenericAllStats from '../components/GenericAllStats'; import NoData from '../components/NoData'; @@ -16,6 +16,7 @@ import autocompleteTanks from '../core/discord/autocompleteTanks'; import autocompleteUsername from '../core/discord/autocompleteUsername'; import { buttonRefresh } from '../core/discord/buttonRefresh'; import commandToURL from '../core/discord/commandToURL'; +import { createLocalizedCommand } from '../core/discord/createLocalizedCommand'; import { getCustomPeriodParams } from '../core/discord/getCustomPeriodParams'; import { getFiltersFromButton } from '../core/discord/getFiltersFromButton'; import { getFiltersFromCommand } from '../core/discord/getFiltersFromCommand'; @@ -73,18 +74,14 @@ async function render( export const fullStatsCommand = new Promise( async (resolve) => { - const command = await addPeriodicFilterOptions( - new SlashCommandBuilder() - .setName('full-stats') - .setDescription('Full in-game statistics'), - (option) => option.addStringOption(addUsernameChoices), - ); - resolve({ inProduction: true, inPublic: true, - command, + command: await addPeriodicFilterOptions( + createLocalizedCommand('full-stats'), + (option) => option.addStringOption(addUsernameChoices), + ), async handler(interaction) { const player = await resolvePlayerFromCommand(interaction); diff --git a/src/commands/today.ts b/src/commands/today.ts index 6e5e27b91..26ee17f9e 100644 --- a/src/commands/today.ts +++ b/src/commands/today.ts @@ -1,5 +1,5 @@ import { SlashCommandBuilder } from 'discord.js'; -import { encyclopediaInfo } from '../core/blitz/encyclopediaInfo'; +import { NATIONS } from '../core/blitzkrieg/tankDefinitions'; import { getBlitzStarsLinkButton } from '../core/blitzstars/getBlitzStarsLinkButton'; import { addFilterOptions } from '../core/discord/addFilterOptions'; import addUsernameChoices from '../core/discord/addUsernameChoices'; @@ -18,12 +18,12 @@ import { CommandRegistry } from '../events/interactionCreate'; import { renderBreakdown } from './breakdown'; export const todayCommand = new Promise(async (resolve) => { - const awaitedEncyclopediaInfo = await encyclopediaInfo; + const nations = await NATIONS; const command = addFilterOptions( new SlashCommandBuilder() .setName('today') .setDescription("Today's played tanks and statistics"), - awaitedEncyclopediaInfo, + nations, ).addStringOption(addUsernameChoices); resolve({ diff --git a/src/components/Tanks/components/Item.tsx b/src/components/Tanks/components/Item.tsx index 870abd89a..886493113 100644 --- a/src/components/Tanks/components/Item.tsx +++ b/src/components/Tanks/components/Item.tsx @@ -6,10 +6,10 @@ import { import { theme } from '../../../stitches.config'; export const TANK_TYPES = [ - 'light', - 'medium', - 'heavy', - 'tankDestroyer', + 'lightTank', + 'mediumTank', + 'heavyTank', + 'AT-SPG', ] as const; export const TREE_TYPES = ['researchable', 'premium', 'collector'] as const; diff --git a/src/core/blitzkrieg/tankDefinitions.ts b/src/core/blitzkrieg/tankDefinitions.ts index 3cadb52a7..fd3d08fb6 100644 --- a/src/core/blitzkrieg/tankDefinitions.ts +++ b/src/core/blitzkrieg/tankDefinitions.ts @@ -181,24 +181,24 @@ export const tankNames = tanksDefinitionsArray.then((tanks) => ); export const TANK_ICONS: Record = { - tankDestroyer: 'https://i.imgur.com/BIHSEH0.png', - light: 'https://i.imgur.com/CSNha5V.png', - medium: 'https://i.imgur.com/wvf3ltm.png', - heavy: 'https://i.imgur.com/ECeqlZa.png', + 'AT-SPG': 'https://i.imgur.com/BIHSEH0.png', + lightTank: 'https://i.imgur.com/CSNha5V.png', + mediumTank: 'https://i.imgur.com/wvf3ltm.png', + heavyTank: 'https://i.imgur.com/ECeqlZa.png', }; export const TANK_ICONS_PREMIUM: Record = { - tankDestroyer: 'https://i.imgur.com/TCu3EdR.png', - light: 'https://i.imgur.com/zdkpTRb.png', - medium: 'https://i.imgur.com/3z7eHX6.png', - heavy: 'https://i.imgur.com/P3vbmyA.png', + 'AT-SPG': 'https://i.imgur.com/TCu3EdR.png', + lightTank: 'https://i.imgur.com/zdkpTRb.png', + mediumTank: 'https://i.imgur.com/3z7eHX6.png', + heavyTank: 'https://i.imgur.com/P3vbmyA.png', }; export const TANK_ICONS_COLLECTOR: Record = { - tankDestroyer: 'https://i.imgur.com/WTjeirB.png', - light: 'https://i.imgur.com/EwhtKkU.png', - medium: 'https://i.imgur.com/u8YDMBh.png', - heavy: 'https://i.imgur.com/8xRf3nc.png', + 'AT-SPG': 'https://i.imgur.com/WTjeirB.png', + lightTank: 'https://i.imgur.com/EwhtKkU.png', + mediumTank: 'https://i.imgur.com/u8YDMBh.png', + heavyTank: 'https://i.imgur.com/8xRf3nc.png', }; export const TIERS = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] as const; diff --git a/src/core/discord/addFilterOptions.ts b/src/core/discord/addFilterOptions.ts index 5523016c9..7e626a232 100644 --- a/src/core/discord/addFilterOptions.ts +++ b/src/core/discord/addFilterOptions.ts @@ -1,60 +1,108 @@ -import { SlashCommandBuilder, SlashCommandSubcommandBuilder } from 'discord.js'; -import { TREE_TYPE_NAMES, TreeType } from '../../components/Tanks'; -import { EncyclopediaInfo } from '../blitz/encyclopediaInfo'; +import { + APIApplicationCommandOptionChoice, + Locale, + SlashCommandBuilder, + SlashCommandSubcommandBuilder, +} from 'discord.js'; +import { TANK_TYPES, TREE_TYPES } from '../../components/Tanks'; +import { translator } from '../localization/translator'; import addTankChoices from './addTankChoices'; import addTierChoices from './addTierChoices'; +import { localizationObject } from './localizationObject'; export function addFilterOptions< Option extends SlashCommandBuilder | SlashCommandSubcommandBuilder, ->(option: Option, encyclopediaInfo: EncyclopediaInfo) { - return option - .addStringOption((option) => - option - .setName('nation') - .setDescription('Nation') - .addChoices( - ...Object.entries(encyclopediaInfo.vehicle_nations).map( - ([type, name]) => ({ name, value: type }), - ), - ) - .setRequired(false), - ) - .addStringOption((option) => addTierChoices(option).setRequired(false)) - .addStringOption((option) => - option - .setName('tank-type') - .setDescription('Tank type') - .addChoices( - ...Object.entries(encyclopediaInfo.vehicle_types).map( - ([type, name]) => ({ - name: name.replace(' Tank', ''), - value: type, - }), - ), - ) - .setRequired(false), - ) - .addStringOption((option) => - option - .setName('tree-type') - .setDescription('Tech tree type') - .addChoices( - { - name: TREE_TYPE_NAMES['researchable'], - value: 'researchable' satisfies TreeType, - }, - { - name: TREE_TYPE_NAMES.premium, - value: 'premium' satisfies TreeType, - }, - { - name: TREE_TYPE_NAMES.collector, - value: 'collector' satisfies TreeType, - }, - ) - .setRequired(false), - ) - .addStringOption((option) => - addTankChoices(option).setRequired(false), - ) as Option; +>(option: Option, nations: string[]) { + const { translate } = translator(Locale.EnglishUS); + + return ( + option + .addStringOption((option) => + option + .setName(translate('bot.common.options.filters.nation')) + .setNameLocalizations( + localizationObject('bot.common.options.filters.nation'), + ) + .setDescription( + translate('bot.common.options.filters.nation.description'), + ) + .setDescriptionLocalizations( + localizationObject('bot.common.options.filters.nation.description'), + ) + .addChoices( + ...nations.map( + (nation) => + ({ + value: nation, + name: translate(`common.nations.${nation}`), + name_localizations: localizationObject( + `common.nations.${nation}`, + ), + }) satisfies APIApplicationCommandOptionChoice, + ), + ) + .setRequired(false), + ) + .addStringOption((option) => addTierChoices(option).setRequired(false)) + .addStringOption((option) => + option + .setName(translate('bot.common.options.filters.tank_type')) + .setNameLocalizations( + localizationObject('bot.common.options.filters.tank_type'), + ) + .setDescription( + translate('bot.common.options.filters.tank_type.description'), + ) + .setDescriptionLocalizations( + localizationObject( + 'bot.common.options.filters.tank_type.description', + ), + ) + .addChoices( + ...TANK_TYPES.map( + (tankType) => + ({ + value: tankType, + name: translate(`common.tank_type_short.${tankType}`), + name_localizations: localizationObject( + `common.tank_type_short.${tankType}`, + ), + }) satisfies APIApplicationCommandOptionChoice, + ), + ) + .setRequired(false), + ) + .addStringOption((option) => + option + .setName(translate('bot.common.options.filters.tree_type')) + .setNameLocalizations( + localizationObject('bot.common.options.filters.tree_type'), + ) + .setDescription( + translate('bot.common.options.filters.tree_type.description'), + ) + .setDescriptionLocalizations( + localizationObject( + 'bot.common.options.filters.tree_type.description', + ), + ) + .addChoices( + ...TREE_TYPES.map( + (treeType) => + ({ + value: treeType, + name: translate(`common.tree_type.${treeType}`), + name_localizations: localizationObject( + `common.tree_type.${treeType}`, + ), + }) satisfies APIApplicationCommandOptionChoice, + ), + ) + .setRequired(false), + ) + // TODO: somehow localize this? + .addStringOption((option) => + addTankChoices(option).setRequired(false), + ) as Option + ); } diff --git a/src/core/discord/addPeriodicFilterOptions.ts b/src/core/discord/addPeriodicFilterOptions.ts index 48188b0cb..e5a9d7f18 100644 --- a/src/core/discord/addPeriodicFilterOptions.ts +++ b/src/core/discord/addPeriodicFilterOptions.ts @@ -3,7 +3,7 @@ import { SlashCommandSubcommandBuilder, SlashCommandSubcommandGroupBuilder, } from 'discord.js'; -import { encyclopediaInfo } from '../blitz/encyclopediaInfo'; +import { NATIONS } from '../blitzkrieg/tankDefinitions'; import { addFilterOptions } from './addFilterOptions'; import { addPeriodSubCommands } from './addPeriodSubCommands'; @@ -15,10 +15,10 @@ export default async function addPeriodicFilterOptions< option: SlashCommandSubcommandBuilder, ) => SlashCommandSubcommandBuilder = (option) => option, ): Promise { - const awaitedEncyclopediaInfo = await encyclopediaInfo; - addPeriodSubCommands(option, (option) => { - return addFilterOptions(extra(option), awaitedEncyclopediaInfo); - }); + const nations = await NATIONS; + addPeriodSubCommands(option, (option) => + addFilterOptions(extra(option), nations), + ); return option; } diff --git a/src/core/discord/addTankChoices.ts b/src/core/discord/addTankChoices.ts index 07b8d0e1a..b224fc2a9 100644 --- a/src/core/discord/addTankChoices.ts +++ b/src/core/discord/addTankChoices.ts @@ -1,9 +1,17 @@ -import { SlashCommandStringOption } from 'discord.js'; +import { Locale, SlashCommandStringOption } from 'discord.js'; +import { translator } from '../localization/translator'; +import { localizationObject } from './localizationObject'; export default function addTankChoices(option: SlashCommandStringOption) { + const { translate } = translator(Locale.EnglishUS); + return option - .setName('tank') - .setDescription('The tank to get stats for') + .setName(translate('bot.common.options.tank')) + .setNameLocalizations(localizationObject('bot.common.options.tank')) + .setDescription(translate('bot.common.options.tank.description')) + .setDescriptionLocalizations( + localizationObject('bot.common.options.tank.description'), + ) .setAutocomplete(true) .setRequired(true); } diff --git a/src/core/discord/createLocalizedCommand.ts b/src/core/discord/createLocalizedCommand.ts index dbadcd776..d77866755 100644 --- a/src/core/discord/createLocalizedCommand.ts +++ b/src/core/discord/createLocalizedCommand.ts @@ -23,23 +23,24 @@ export function createLocalizedCommand( | CreateLocalizedCommandSubcommand[] | CreateLocalizedCommandSubcommandGroup[], ) { + const commandPathItem = command.replaceAll('-', '_'); const { translate } = translator(Locale.EnglishUS); const slashCommand = new SlashCommandBuilder() .setName(command) - .setNameLocalizations(localizationObject(`bot.commands.${command}`)) - .setDescription(translate(`bot.commands.${command}.description`)) + .setNameLocalizations(localizationObject(`bot.commands.${commandPathItem}`)) + .setDescription(translate(`bot.commands.${commandPathItem}.description`)) .setDescriptionLocalizations( - localizationObject(`bot.commands.${command}.description`), + localizationObject(`bot.commands.${commandPathItem}.description`), ); if (sub) { sub.map((subItem) => { if ('subcommand' in subItem) { const subcommandNameLocalizations = localizationObject( - `bot.commands.${command}.subcommands.${subItem.subcommand}`, + `bot.commands.${commandPathItem}.subcommands.${subItem.subcommand}`, ); const subcommandDescriptionLocalizations = localizationObject( - `bot.commands.${command}.subcommands.${subItem.subcommand}.description`, + `bot.commands.${commandPathItem}.subcommands.${subItem.subcommand}.description`, ); slashCommand.addSubcommand((option) => { @@ -50,17 +51,17 @@ export function createLocalizedCommand( .setNameLocalizations(subcommandNameLocalizations) .setDescription( translate( - `bot.commands.${command}.subcommands.${subItem.subcommand}.description`, + `bot.commands.${commandPathItem}.subcommands.${subItem.subcommand}.description`, ), ) .setDescriptionLocalizations(subcommandDescriptionLocalizations); }); } else { const subcommandNameLocalizations = localizationObject( - `bot.commands.${command}.groups.${subItem.group}`, + `bot.commands.${commandPathItem}.groups.${subItem.group}`, ); const subcommandDescriptionLocalizations = localizationObject( - `bot.commands.${command}.groups.${subItem.group}.description`, + `bot.commands.${commandPathItem}.groups.${subItem.group}.description`, ); slashCommand.addSubcommandGroup((option) => { @@ -71,7 +72,7 @@ export function createLocalizedCommand( .setNameLocalizations(subcommandNameLocalizations) .setDescription( translate( - `bot.commands.${command}.groups.${subItem.group}.description`, + `bot.commands.${commandPathItem}.groups.${subItem.group}.description`, ), ) .setDescriptionLocalizations(subcommandDescriptionLocalizations); diff --git a/src/lang/en-US.json b/src/lang/en-US.json index 959c5812c..2575122f6 100644 --- a/src/lang/en-US.json +++ b/src/lang/en-US.json @@ -84,6 +84,26 @@ "clan": { "$": "clan", "description": "The clan name or tag" + }, + + "tank": { + "$": "tank", + "description": "The tank to get stats for" + }, + + "filters": { + "nation": { + "$": "nation", + "description": "Nation" + }, + "tank_type": { + "$": "tank-type", + "description": "Tank type" + }, + "tree_type": { + "$": "tree-type", + "description": "Tree type" + } } }, From 3510768786e11152857722ebe37e69f6418a504e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Thu, 7 Mar 2024 15:13:37 -0600 Subject: [PATCH 47/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index 1f5b1209f..61e5f0070 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -80,17 +80,17 @@ "description": "El nombre o etiqueta del clan" }, "tank": { - "$": "tank", + "$": "tanque", "description": "The tank to get stats for" }, "filters": { "nation": { - "$": "nation", - "description": "Nation" + "$": "nación", + "description": "Nación" }, "tank_type": { - "$": "tank-type", - "description": "Tank type" + "$": "tipo-tanque", + "description": "Tipo de tanque" }, "tree_type": { "$": "tree-type", From 796aca21cff9b76c101ba2b14f42a29d31008240 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Thu, 7 Mar 2024 17:02:37 -0600 Subject: [PATCH 48/90] feat: breakdown locale --- src/commands/breakdown.tsx | 25 +++++++++++++++---------- src/core/discord/addUsernameChoices.ts | 14 +++++++++++--- src/core/discord/buttonRefresh.ts | 2 +- src/lang/en-US.json | 9 ++++++++- 4 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/commands/breakdown.tsx b/src/commands/breakdown.tsx index 9a7ed439a..f48f8733c 100644 --- a/src/commands/breakdown.tsx +++ b/src/commands/breakdown.tsx @@ -33,6 +33,7 @@ import resolvePlayerFromButton from '../core/discord/resolvePlayerFromButton'; import resolvePlayerFromCommand, { ResolvedPlayer, } from '../core/discord/resolvePlayerFromCommand'; +import { translator } from '../core/localization/translator'; import calculateWN8 from '../core/statistics/calculateWN8'; import { StatFilters, filterStats } from '../core/statistics/filterStats'; import getWN8Percentile from '../core/statistics/getWN8Percentile'; @@ -47,6 +48,7 @@ export async function renderBreakdown( filters: StatFilters, locale: Locale, ) { + const { t, translate } = translator(locale); const awaitedTankDefinitions = await tankDefinitions; const awaitedTankAverages = await tankAverages; const statsInPeriod = await getStatsInPeriod(region, id, start, end); @@ -146,21 +148,21 @@ export async function renderBreakdown( Date: Thu, 7 Mar 2024 18:27:00 -0600 Subject: [PATCH 49/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 113 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 112 insertions(+), 1 deletion(-) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index 61e5f0070..a58c85102 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -21,6 +21,25 @@ "researchable": "Árbol tecnológico", "premium": "Prémium", "collector": "Coleccionista" + }, + "regions": { + "normal": { + "com": "North America", + "eu": "Europe", + "asia": "Asia" + }, + "short": { + "com": "NA", + "eu": "EU", + "asia": "ASIA" + } + }, + "leagues": { + "diamond": "Diamond", + "platinum": "Platinum", + "gold": "Gold", + "silver": "Silver", + "bronze": "Bronze" } }, "bot": { @@ -83,6 +102,10 @@ "$": "tanque", "description": "The tank to get stats for" }, + "username": { + "$": "username", + "description": "The Blitz username of the player" + }, "filters": { "nation": { "$": "nación", @@ -96,6 +119,10 @@ "$": "tree-type", "description": "Tree type" } + }, + "region": { + "$": "region", + "description": "Region" } }, "periods": { @@ -152,8 +179,10 @@ "body": { "total": "Total", "battles": "Batallas", + "wn8": "WN8", "winrate": "Tasa de victorias", - "damage": "Daño" + "damage": "Daño", + "unknown_tank": "Unknown tank %s" } }, "debug": { @@ -230,6 +259,88 @@ } } } + }, + "link": { + "$": "link", + "description": "Links your Blitz and Discord account", + "body": { + "accounts_linked": "Accounts linked", + "no_role_permissions": { + "$": "%s failed to verify", + "description": "I don't have the permission to change your manage roles." + } + }, + "embed": { + "title": "Incorrect account?", + "description": "Use the command again and select a username from the search results." + } + }, + "search_clans": { + "$": "search-clans", + "description": "Search clans", + "options": { + "limit": { + "$": "limit", + "description": "The max number of clan results (default: %s)" + } + }, + "body": { + "title": "Clan search for %s in region %s", + "no_results": "No clans found" + } + }, + "search_players": { + "$": "search-players", + "description": "Search players", + "options": { + "limit": { + "$": "limit", + "description": "The max number of player results (default: %s)" + } + }, + "body": { + "title": "Player search for %s in region %s", + "no_results": "No players found" + } + }, + "ratings": { + "$": "ratings", + "description": "Ratings battles statistics", + "subcommands": { + "neighbors": { + "$": "neighbors", + "description": "Neighboring players on the leaderboard" + }, + "league": { + "$": "league", + "description": "Top players of a league" + } + }, + "options": { + "limit": { + "$": "limit", + "description": "How many neighbors to display (default: %s)" + }, + "season": { + "$": "season", + "description": "Season number (default: current)", + "choices": { + "current": "%s (current)" + } + }, + "league": { + "$": "league", + "description": "The league to display" + } + }, + "body": { + "top_players": "Top %s players", + "no_participation": "# %s didn't play ratings in season %s\n\nThis player did not participate in this season or did not get past calibration.", + "no_ongoing_season": "# No ongoing season\n\nThere isn't a currently running ratings season.", + "deleted_player": "Deleted %s", + "neighbors": "Ratings neighbors", + "more": "+%s more" + } } } }, From 3325536bbc7d7d7afc4c09245b01d5f5e11a37e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Thu, 7 Mar 2024 18:27:05 -0600 Subject: [PATCH 50/90] feat: random localizations --- .vscode/settings.json | 1 + src/app/tools/ratings/page.tsx | 8 +- src/app/tools/session/page.tsx | 4 +- src/commands/evolution.tsx | 4 +- src/commands/inactive.tsx | 4 +- src/commands/link.tsx | 35 ++-- src/commands/ratings.tsx | 219 ++++++++++++++--------- src/commands/searchClans.ts | 50 ++++-- src/commands/searchPlayers.ts | 39 +++- src/constants/leagues.ts | 10 +- src/constants/regions.ts | 18 +- src/core/discord/addRegionChoices.ts | 33 +++- src/core/discord/autocompleteClan.ts | 11 +- src/core/discord/autocompleteUsername.ts | 13 +- src/core/discord/localizationObject.ts | 4 +- src/events/interactionCreate/index.ts | 11 +- src/lang/en-US.json | 125 +++++++++++++ 17 files changed, 423 insertions(+), 166 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index dca4e6fa9..57b505836 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -131,6 +131,7 @@ "TYPELESS", "ubjson", "Unicum", + "UNLOCALIZED", "UNORM", "Unregistering", "vickers", diff --git a/src/app/tools/ratings/page.tsx b/src/app/tools/ratings/page.tsx index 979e4c1c3..555bb9f8d 100644 --- a/src/app/tools/ratings/page.tsx +++ b/src/app/tools/ratings/page.tsx @@ -20,7 +20,11 @@ import PageWrapper from '../../../components/PageWrapper'; import { Skeleton } from '../../../components/Skeleton'; import { LEAGUES } from '../../../constants/leagues'; import { FIRST_ARCHIVED_RATINGS_SEASON } from '../../../constants/ratings'; -import { REGIONS, REGION_NAMES, Region } from '../../../constants/regions'; +import { + REGIONS, + Region, + UNLOCALIZED_REGION_NAMES, +} from '../../../constants/regions'; import { WARGAMING_APPLICATION_ID } from '../../../constants/wargamingApplicationID'; import fetchBlitz from '../../../core/blitz/fetchBlitz'; import { getClanAccountInfo } from '../../../core/blitz/getClanAccountInfo'; @@ -556,7 +560,7 @@ export default function Page() { {REGIONS.map((region) => ( - {REGION_NAMES[region]} + {UNLOCALIZED_REGION_NAMES[region]} ))} diff --git a/src/app/tools/session/page.tsx b/src/app/tools/session/page.tsx index eecd2e180..a092b4f8a 100644 --- a/src/app/tools/session/page.tsx +++ b/src/app/tools/session/page.tsx @@ -18,7 +18,7 @@ import { import { debounce } from 'lodash'; import { ChangeEvent, useEffect, useRef, useState } from 'react'; import PageWrapper from '../../../components/PageWrapper'; -import { REGION_NAMES } from '../../../constants/regions'; +import { UNLOCALIZED_REGION_NAMES } from '../../../constants/regions'; import searchPlayersAcrossRegions, { AccountListWithServer, } from '../../../core/blitz/searchPlayersAcrossRegions'; @@ -107,7 +107,7 @@ export default function Page() { setShowSearchResults(false); setSession(region, id, nickname); }} - shortcut={REGION_NAMES[region]} + shortcut={UNLOCALIZED_REGION_NAMES[region]} > {nickname} diff --git a/src/commands/evolution.tsx b/src/commands/evolution.tsx index fd6a61cdd..fce791045 100644 --- a/src/commands/evolution.tsx +++ b/src/commands/evolution.tsx @@ -89,8 +89,8 @@ async function render( max: maxX, precision: 0, }} - xMinLabel={new Date(minTime * 1000).toDateString()} - xMaxLabel={new Date(maxTime * 1000).toDateString()} + xMinLabel={new Date(minTime * 1000).toLocaleDateString(locale)} + xMaxLabel={new Date(maxTime * 1000).toLocaleDateString(locale)} > {Graph.Line({ color: Graph.LineColor.Red, diff --git a/src/commands/inactive.tsx b/src/commands/inactive.tsx index d5f3d78ac..0c74df574 100644 --- a/src/commands/inactive.tsx +++ b/src/commands/inactive.tsx @@ -63,7 +63,9 @@ export const inactiveCommand = new Promise((resolve) => { ([name, days]) => [ name, - translate('bot.commands.inactive.body.listing', [`${days}`]), + translate('bot.commands.inactive.body.listing', [ + days.toFixed(0), + ]), ] as [string, string], ); const hasInactiveMembers = inactive.length > 0; diff --git a/src/commands/link.tsx b/src/commands/link.tsx index 5f81fa346..55c2cc463 100644 --- a/src/commands/link.tsx +++ b/src/commands/link.tsx @@ -1,18 +1,19 @@ import { teal } from '@radix-ui/colors'; -import { GuildMemberRoleManager, SlashCommandBuilder } from 'discord.js'; +import { GuildMemberRoleManager } from 'discord.js'; import markdownEscape from 'markdown-escape'; import discord from '../../discord.json' assert { type: 'json' }; import { Glow } from '../components/AllStatsOverview/components/WN8Display/components/Glow'; import CommandWrapper from '../components/CommandWrapper'; -import { REGION_NAMES } from '../constants/regions'; import { getAccountInfo } from '../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo'; import { linkBlitzAndDiscord } from '../core/blitzkrieg/discordBlitz'; import addUsernameChoices from '../core/discord/addUsernameChoices'; import autocompleteUsername from '../core/discord/autocompleteUsername'; +import { createLocalizedCommand } from '../core/discord/createLocalizedCommand'; import embedInfo from '../core/discord/embedInfo'; import embedNegative from '../core/discord/embedNegative'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; +import { translator } from '../core/localization/translator'; import { CommandRegistry } from '../events/interactionCreate'; import { theme } from '../stitches.config'; @@ -21,14 +22,12 @@ export const verifyCommand = new Promise((resolve) => { inProduction: true, inPublic: true, - command: new SlashCommandBuilder() - .setName('link') - .setDescription('Links your Blitz and Discord account') - .addStringOption((option) => - addUsernameChoices(option).setRequired(true), - ), + command: createLocalizedCommand('link').addStringOption((option) => + addUsernameChoices(option).setRequired(true), + ), async handler(interaction) { + const { t, translate } = translator(interaction.locale); const { id, region } = await resolvePlayerFromCommand(interaction); const discordId = parseInt(interaction.user.id); const accountInfo = await getAccountInfo(region, id); @@ -39,8 +38,14 @@ export const verifyCommand = new Promise((resolve) => { if (interaction.guildId === discord.sklld_guild_id) { if (!interaction.guild?.members.me?.permissions.has('ManageRoles')) { return embedNegative( - `${markdownEscape(interaction.user.username)} failed to verify`, - "I don't have the permission to change your manage roles.", + translate(translate('bot.commands.link.body.no_role_permissions'), [ + markdownEscape(interaction.user.username), + ]), + translate( + translate( + 'bot.commands.link.body.no_role_permissions.description', + ), + ), ); } @@ -140,7 +145,9 @@ export const verifyCommand = new Promise((resolve) => { height: 16, }} /> - Accounts linked + {t`bot.commands.link.body.accounts_linked`}
((resolve) => { {clanAccountInfo?.clan ? `[${clanAccountInfo?.clan?.tag}] • ` : ''} - {REGION_NAMES[region]} + {translate(`common.regions.normal.${region}`)}
@@ -202,8 +209,8 @@ export const verifyCommand = new Promise((resolve) => { , embedInfo( - 'Incorrect account?', - 'Use the command again and select a username from the search results.', + t`bot.commands.link.embed.title`, + t`bot.commands.link.embed.description`, ), ]; }, diff --git a/src/commands/ratings.tsx b/src/commands/ratings.tsx index 5fbe47c79..fcfbf4ac2 100644 --- a/src/commands/ratings.tsx +++ b/src/commands/ratings.tsx @@ -1,6 +1,6 @@ import { APIApplicationCommandOptionChoice, - SlashCommandBuilder, + Locale, SlashCommandSubcommandBuilder, } from 'discord.js'; import { range } from 'lodash'; @@ -11,7 +11,7 @@ import * as Leaderboard from '../components/Leaderboard'; import TitleBar from '../components/TitleBar'; import { LEAGUES } from '../constants/leagues'; import { FIRST_ARCHIVED_RATINGS_SEASON } from '../constants/ratings'; -import { REGION_NAMES_SHORT, Region } from '../constants/regions'; +import { Region } from '../constants/regions'; import { getAccountInfo } from '../core/blitz/getAccountInfo'; import { getClanAccountInfo } from '../core/blitz/getClanAccountInfo'; import getRatingsInfo from '../core/blitz/getRatingsInfo'; @@ -25,8 +25,10 @@ import getArchivedRatingsMidnightLeaderboard from '../core/blitzkrieg/getArchive import addRegionChoices from '../core/discord/addRegionChoices'; import addUsernameChoices from '../core/discord/addUsernameChoices'; import autocompleteUsername from '../core/discord/autocompleteUsername'; -import embedNegative from '../core/discord/embedNegative'; +import { createLocalizedCommand } from '../core/discord/createLocalizedCommand'; +import { localizationObject } from '../core/discord/localizationObject'; import resolvePlayerFromCommand from '../core/discord/resolvePlayerFromCommand'; +import { translator } from '../core/localization/translator'; import { CommandRegistry } from '../events/interactionCreate'; import { UserError } from '../hooks/userError'; @@ -117,10 +119,15 @@ export interface RatingsNeighbors { neighbors: RatingsPlayer[]; } -const noOngoingSeason = embedNegative( - 'No ongoing season', - "Wargaming didn't provide any data for this season.", -); +interface SimplifiedPlayer { + id: number; + score: number; + position: number; + clan: string | undefined; + nickname: string; +} + +const DEFAULT_LIMIT = 10; export const ratingsCommand = new Promise(async (resolve) => { const latestArchivedSeasonNumber = await getArchivedLatestSeasonNumber(); @@ -130,89 +137,131 @@ export const ratingsCommand = new Promise(async (resolve) => { latestArchivedSeasonNumber + (onGoingSeason ? 1 : 0) + 1, ); - function addSubcommands(option: SlashCommandSubcommandBuilder) { + function addOptions(option: SlashCommandSubcommandBuilder) { + const { t, translate } = translator(Locale.EnglishUS); + return option - .addStringOption(addUsernameChoices) .addIntegerOption((option) => option - .setName('limit') - .setDescription('How many neighbors to display (default: 10)') + .setName(t`bot.commands.ratings.options.limit`) + .setNameLocalizations( + localizationObject('bot.commands.ratings.options.limit'), + ) + .setDescription( + translate('bot.commands.ratings.options.limit.description', [ + `${DEFAULT_LIMIT}`, + ]), + ) + .setDescriptionLocalizations( + localizationObject( + 'bot.commands.ratings.options.limit.description', + [`${DEFAULT_LIMIT}`], + ), + ) .setRequired(false) .setMinValue(5) .setMaxValue(30), ) .addStringOption((option) => option - .setName('season') - .setDescription('Season number') + .setName(t`bot.commands.ratings.options.season`) + .setNameLocalizations( + localizationObject('bot.commands.ratings.options.season'), + ) + .setDescription( + translate('bot.commands.ratings.options.season.description'), + ) + .setDescriptionLocalizations( + localizationObject( + 'bot.commands.ratings.options.season.description', + ), + ) .setRequired(false) .addChoices( ...seasonNumbers - .map( - (number, index) => - ({ - name: `${number}${index === seasonNumbers.length - 1 && onGoingSeason ? ' (current)' : ''}`, - value: `${number}`, - }) satisfies APIApplicationCommandOptionChoice, - ) + .map((number, index) => { + const current = + index === seasonNumbers.length - 1 && onGoingSeason; + + return { + name: current + ? translate( + 'bot.commands.ratings.options.season.choices.current', + [`${number}`], + ) + : `${number}`, + value: `${number}`, + name_localizations: current + ? localizationObject( + 'bot.commands.ratings.options.season.choices.current', + [`${number}`], + ) + : undefined, + } satisfies APIApplicationCommandOptionChoice; + }) .reverse(), ), ); } - const command = new SlashCommandBuilder() - .setName('ratings') - .setDescription('Ratings battles statistics') - .addSubcommand((option) => - addSubcommands( - option - .setName('neighbors') - .setDescription('Shows neighboring players on the leaderboard'), - ), - ) - .addSubcommand((option) => - addSubcommands( - option - .setName('league') - .setDescription('Shows top players from a league') - .addStringOption(addRegionChoices) - .addStringOption((option) => - option - .setName('league') - .setDescription('The league to display') - .addChoices( - ...LEAGUES.map( - ({ name }, value) => - ({ - name, - value: `${value}`, - }) satisfies APIApplicationCommandOptionChoice, - ), - ) - .setRequired(true), - ), - ), - ); - resolve({ inProduction: true, inPublic: true, - command, + command: createLocalizedCommand('ratings', [ + { + subcommand: 'neighbors', + modify(option: SlashCommandSubcommandBuilder) { + option.addStringOption(addUsernameChoices); + addOptions(option); + }, + }, + { + subcommand: 'league', + modify(option: SlashCommandSubcommandBuilder) { + option + .addStringOption((option) => { + const { t, translate } = translator(Locale.EnglishUS); + + return option + .setName(t`bot.commands.ratings.options.league`) + .setNameLocalizations( + localizationObject('bot.commands.ratings.options.league'), + ) + .setDescription( + translate('bot.commands.ratings.options.league.description'), + ) + .setDescriptionLocalizations( + localizationObject( + 'bot.commands.ratings.options.league.description', + ), + ) + .addChoices( + ...LEAGUES.map( + ({ name }, value) => + ({ + name: translate(`common.leagues.${name}`), + value: `${value}`, + name_localizations: localizationObject( + `common.leagues.${name}`, + ), + }) satisfies APIApplicationCommandOptionChoice, + ), + ) + .setRequired(true); + }) + .addStringOption(addRegionChoices); + addOptions(option); + }, + }, + ]), async handler(interaction) { - interface SimplifiedPlayer { - id: number; - score: number; - position: number; - clan: string | undefined; - nickname: string; - } - + const { t, translate } = translator(interaction.locale); const subcommand = interaction.options.getSubcommand(true) as | 'league' | 'neighbors'; - const limit = interaction.options.getInteger('limit') ?? 10; + const limit = interaction.options.getInteger('limit') ?? DEFAULT_LIMIT; const seasonOption = interaction.options.getString('season'); const season = seasonOption ? parseInt(seasonOption) @@ -223,7 +272,6 @@ export const ratingsCommand = new Promise(async (resolve) => { let titleName: string; let titleImage: string | undefined; let titleDescription: string; - let playersBefore: number; let playersAfter: number; let players: SimplifiedPlayer[] | undefined; let midnightLeaderboard: BlitzkriegRatingsLeaderboard | undefined; @@ -237,7 +285,8 @@ export const ratingsCommand = new Promise(async (resolve) => { ? getRatingsInfo(region) : getArchivedRatingsInfo(region, season!)); - if (regionRatingsInfo.detail !== undefined) return noOngoingSeason; + if (regionRatingsInfo.detail !== undefined) + return t`bot.commands.ratings.body.no_ongoing_season`; const result = isCurrentSeason ? await getRatingsLeague(region, leagueIndex).then(async (result) => @@ -280,7 +329,10 @@ export const ratingsCommand = new Promise(async (resolve) => { position: firstPlayerIndex + index + 1, clan: clanData[player.id]?.clan?.tag, nickname: - clanData[index]?.account_name ?? `Deleted ${player.id}`, + clanData[index]?.account_name ?? + translate('bot.commands.ratings.body.deleted_player', [ + `${player.id}`, + ]), }) satisfies SimplifiedPlayer, ); }, @@ -294,23 +346,25 @@ export const ratingsCommand = new Promise(async (resolve) => { : await getArchivedRatingsMidnightLeaderboard(region, season!); players = result; - playersBefore = result ? result[0].position - 1 : 0; playersAfter = regionRatingsInfo.count - result[result.length - 1].position; titleName = `${ - leagueInfo ? leagueInfo.title : 'No Ongoing Season' - } - ${REGION_NAMES_SHORT[region]}`; + leagueInfo ? leagueInfo.title : '' + } - ${translate(`common.regions.short.${region}`)}`; titleImage = leagueInfo.big_icon.startsWith('http') ? leagueInfo.big_icon : `https:${leagueInfo.big_icon}`; - titleDescription = `Top ${limit} players`; + titleDescription = translate('bot.commands.ratings.body.top_players', [ + `${limit}`, + ]); } else { const { region, id } = await resolvePlayerFromCommand(interaction); regionRatingsInfo = isCurrentSeason ? await getRatingsInfo(region) : await getArchivedRatingsInfo(region, season!); - if (regionRatingsInfo.detail !== undefined) return noOngoingSeason; + if (regionRatingsInfo.detail !== undefined) + return t`bot.commands.ratings.body.no_ongoing_season`; const accountInfo = await getAccountInfo(region, id); const clan = (await getClanAccountInfo(region, id, ['clan']))?.clan; @@ -336,7 +390,10 @@ export const ratingsCommand = new Promise(async (resolve) => { if (playerIndex === -1) { throw new UserError( - `# ${markdownEscape(accountInfo.nickname)} didn't play ratings in season ${season}\nThis player did not participate in this season or did not get past calibration.`, + translate('bot.commands.ratings.body.no_participation', [ + markdownEscape(accountInfo.nickname), + `${season}`, + ]), ); } @@ -373,11 +430,8 @@ export const ratingsCommand = new Promise(async (resolve) => { titleImage = clan ? `https://wotblitz-gc.gcdn.co/icons/clanEmblems1x/clan-icon-v2-${clan.emblem_set_id}.png` : undefined; - titleName = neighbors - ? accountInfo.nickname - : `No Ongoing Season - ${REGION_NAMES_SHORT[region]}`; - titleDescription = 'Ratings neighbors'; - playersBefore = neighbors ? neighbors[0].position - 1 : 0; + titleName = neighbors ? accountInfo.nickname : ''; + titleDescription = t`bot.commands.ratings.body.neighbors`; playersAfter = regionRatingsInfo.count - neighbors[neighbors.length - 1].position; playerId = id; @@ -428,15 +482,12 @@ export const ratingsCommand = new Promise(async (resolve) => { {items && ( - {playersBefore > 0 && ( - - )} {items} {playersAfter > 0 && ( )} diff --git a/src/commands/searchClans.ts b/src/commands/searchClans.ts index 4b70afae5..3fe09224a 100644 --- a/src/commands/searchClans.ts +++ b/src/commands/searchClans.ts @@ -1,46 +1,68 @@ -import { SlashCommandBuilder } from 'discord.js'; +import { Locale } from 'discord.js'; import markdownEscape from 'markdown-escape'; -import { REGION_NAMES, Region } from '../constants/regions'; +import { Region } from '../constants/regions'; import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID'; import fetchBlitz from '../core/blitz/fetchBlitz'; import addClanChoices from '../core/discord/addClanChoices'; import addRegionChoices from '../core/discord/addRegionChoices'; +import { createLocalizedCommand } from '../core/discord/createLocalizedCommand'; import embedInfo from '../core/discord/embedInfo'; +import { localizationObject } from '../core/discord/localizationObject'; +import { translator } from '../core/localization/translator'; import { CommandRegistry } from '../events/interactionCreate'; import { ClanList } from '../types/clanList'; +const DEFAULT_LIMIT = 25; + export const searchClansCommand = new Promise((resolve) => { resolve({ inProduction: true, inPublic: true, - command: new SlashCommandBuilder() - .setName('search-clans') - .setDescription('Search clans') + command: createLocalizedCommand('search-clans') .addStringOption(addRegionChoices) .addStringOption((option) => addClanChoices(option).setAutocomplete(false), ) - .addIntegerOption((option) => - option - .setName('limit') - .setDescription('The size of the search result (default: 25)') + .addIntegerOption((option) => { + const { t, translate } = translator(Locale.EnglishUS); + + return option + .setName(t`bot.commands.search_clans.options.limit`) + .setNameLocalizations( + localizationObject('bot.commands.search_clans.options.limit'), + ) + .setDescription( + translate('bot.commands.search_clans.options.limit.description', [ + `${DEFAULT_LIMIT}`, + ]), + ) + .setDescriptionLocalizations( + localizationObject( + 'bot.commands.search_clans.options.limit.description', + [`${DEFAULT_LIMIT}`], + ), + ) .setMinValue(1) - .setMaxValue(100), - ), + .setMaxValue(100); + }), async handler(interaction) { + const { translate } = translator(interaction.locale); const server = interaction.options.getString('region') as Region; const clan = interaction.options.getString('clan')!; - const limit = interaction.options.getInteger('limit') ?? 25; + const limit = interaction.options.getInteger('limit') ?? DEFAULT_LIMIT; const clanList = await fetchBlitz( `https://api.wotblitz.${server}/wotb/clans/list/?application_id=${WARGAMING_APPLICATION_ID}&search=${clan}&limit=${limit}`, ); return embedInfo( - `Clan search for "${markdownEscape(clan)}" in ${REGION_NAMES[server]}`, + translate('bot.commands.search_clans.body.title', [ + markdownEscape(clan), + translate(`common.regions.normal.${server}`), + ]), clanList.length === 0 - ? 'No clans found.' + ? translate('bot.commands.search_clans.body.no_results') : `\`\`\`\n${clanList .map((clan) => `${clan.name} [${clan.tag}]`) .join('\n')}\n\`\`\``, diff --git a/src/commands/searchPlayers.ts b/src/commands/searchPlayers.ts index 11f642735..aa93d115b 100644 --- a/src/commands/searchPlayers.ts +++ b/src/commands/searchPlayers.ts @@ -1,6 +1,6 @@ -import { SlashCommandBuilder } from 'discord.js'; +import { Locale } from 'discord.js'; import markdownEscape from 'markdown-escape'; -import { REGION_NAMES, Region } from '../constants/regions'; +import { Region } from '../constants/regions'; import { WARGAMING_APPLICATION_ID } from '../constants/wargamingApplicationID'; import fetchBlitz from '../core/blitz/fetchBlitz'; import { @@ -9,25 +9,43 @@ import { } from '../core/blitz/searchPlayersAcrossRegions'; import addRegionChoices from '../core/discord/addRegionChoices'; import addUsernameChoices from '../core/discord/addUsernameChoices'; +import { createLocalizedCommand } from '../core/discord/createLocalizedCommand'; import embedInfo from '../core/discord/embedInfo'; +import { localizationObject } from '../core/discord/localizationObject'; +import { translator } from '../core/localization/translator'; import { CommandRegistry } from '../events/interactionCreate'; +const DEFAULT_LIMIT = 25; + export const searchPlayersCommand = new Promise((resolve) => { + const { t, translate } = translator(Locale.EnglishUS); + resolve({ inProduction: true, inPublic: true, - command: new SlashCommandBuilder() - .setName('search-players') - .setDescription('Search players in a Blitz server') + command: createLocalizedCommand('search-players') .addStringOption(addRegionChoices) .addStringOption((option) => addUsernameChoices(option).setAutocomplete(false).setRequired(true), ) .addIntegerOption((option) => option - .setName('limit') - .setDescription('The size of the search result (default: 25)') + .setName(t`bot.commands.search_players.options.limit`) + .setNameLocalizations( + localizationObject('bot.commands.search_players.options.limit'), + ) + .setDescription( + translate('bot.commands.search_players.options.limit.description', [ + `${DEFAULT_LIMIT}`, + ]), + ) + .setDescriptionLocalizations( + localizationObject( + 'bot.commands.search_players.options.limit.description', + [`${DEFAULT_LIMIT}`], + ), + ) .setMinValue(1) .setMaxValue(100), ), @@ -46,10 +64,13 @@ export const searchPlayersCommand = new Promise((resolve) => { : []; return embedInfo( - `Player search for "${markdownEscape(name)}" in ${REGION_NAMES[server]}`, + translate('bot.commands.search_players.body.title', [ + markdownEscape(trimmedSearch), + translate(`common.regions.normal.${server}`), + ]), `\`\`\`${ players.length === 0 - ? 'No players found.' + ? translate('bot.commands.search_players.body.no_results') : players.map((player) => player.nickname).join('\n') }\`\`\``, ); diff --git a/src/constants/leagues.ts b/src/constants/leagues.ts index 082886490..ee77accb4 100644 --- a/src/constants/leagues.ts +++ b/src/constants/leagues.ts @@ -1,7 +1,7 @@ export const LEAGUES = [ - { name: 'Diamond', minScore: 5000 }, - { name: 'Platinum', minScore: 4000 }, - { name: 'Gold', minScore: 3000 }, - { name: 'Silver', minScore: 2000 }, - { name: 'Bronze', minScore: 0 }, + { name: 'diamond', minScore: 5000 }, + { name: 'platinum', minScore: 4000 }, + { name: 'gold', minScore: 3000 }, + { name: 'silver', minScore: 2000 }, + { name: 'bronze', minScore: 0 }, ]; diff --git a/src/constants/regions.ts b/src/constants/regions.ts index d6e088235..85e84a7d0 100644 --- a/src/constants/regions.ts +++ b/src/constants/regions.ts @@ -3,21 +3,11 @@ export const REGIONS = ['com', 'eu', 'asia'] as const; export type Region = (typeof REGIONS)[number]; export type RegionSubdomain = 'na' | 'eu' | 'asia'; -export const REGION_NAMES: Record = { +/** + * @deprecated + */ +export const UNLOCALIZED_REGION_NAMES: Record = { com: 'North America', - eu: 'Europe', asia: 'Asia', -}; - -// https://planetarynames.wr.usgs.gov/Abbreviations -export const REGION_NAMES_SHORT: Record = { - com: 'NA', - eu: 'EU', - asia: 'ASIA', -}; - -export const REGION_SUBDOMAIN_NAMES: Record = { - na: 'North America', eu: 'Europe', - asia: 'Asia', }; diff --git a/src/core/discord/addRegionChoices.ts b/src/core/discord/addRegionChoices.ts index eb0042a5d..9e53ca323 100644 --- a/src/core/discord/addRegionChoices.ts +++ b/src/core/discord/addRegionChoices.ts @@ -1,14 +1,33 @@ -import { SlashCommandStringOption } from 'discord.js'; -import { REGION_NAMES } from '../../constants/regions'; +import { + APIApplicationCommandOptionChoice, + Locale, + SlashCommandStringOption, +} from 'discord.js'; +import { REGIONS } from '../../constants/regions'; +import { translator } from '../localization/translator'; +import { localizationObject } from './localizationObject'; export default function addRegionChoices(option: SlashCommandStringOption) { + const { t, translate } = translator(Locale.EnglishUS); + return option - .setName('region') - .setDescription('The Blitz region you are in') + .setName(t`bot.common.options.region`) + .setNameLocalizations(localizationObject('bot.common.options.region')) + .setDescription(t`bot.common.options.region.description`) + .setDescriptionLocalizations( + localizationObject('bot.common.options.region.description'), + ) .addChoices( - { name: REGION_NAMES.com, value: 'com' }, - { name: REGION_NAMES.eu, value: 'eu' }, - { name: REGION_NAMES.asia, value: 'asia' }, + ...REGIONS.map( + (region) => + ({ + value: region, + name: translate(`common.regions.normal.${region}`), + name_localizations: localizationObject( + `common.regions.normal.${region}`, + ), + }) satisfies APIApplicationCommandOptionChoice, + ), ) .setRequired(true); } diff --git a/src/core/discord/autocompleteClan.ts b/src/core/discord/autocompleteClan.ts index bbbd9a2de..142282ad2 100644 --- a/src/core/discord/autocompleteClan.ts +++ b/src/core/discord/autocompleteClan.ts @@ -3,8 +3,8 @@ import { AutocompleteInteraction, CacheType, } from 'discord.js'; -import { REGION_NAMES_SHORT } from '../../constants/regions'; import searchClansAcrossRegions from '../blitz/searchClansAcrossRegions'; +import { translator } from '../localization/translator'; export const DISCORD_CHOICES_MAX_NAME_SIZE = 25; export const OVERFLOW_SUFFIX = '…'; @@ -12,6 +12,7 @@ export const OVERFLOW_SUFFIX = '…'; export default async function autocompleteClan( interaction: AutocompleteInteraction, ) { + const { translate } = translator(interaction.locale); const focusedOption = interaction.options.getFocused(true); if (focusedOption.name !== 'clan') return; if (focusedOption.value.length < 2) return interaction.respond([]); @@ -20,7 +21,9 @@ export default async function autocompleteClan( await interaction.respond( clans ? clans.map((clan) => { - let name = `${clan.name} (${REGION_NAMES_SHORT[clan.region]})`; + let name = `${clan.name} (${translate( + `common.regions.short.${clan.region}`, + )})`; if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) { const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE; @@ -28,7 +31,9 @@ export default async function autocompleteClan( name = `${clan.name.slice( 0, clan.name.length - overSize - OVERFLOW_SUFFIX.length, - )}${OVERFLOW_SUFFIX} (${REGION_NAMES_SHORT[clan.region]})`; + )}${OVERFLOW_SUFFIX} (${translate( + `common.regions.short.${clan.region}`, + )})`; } return { diff --git a/src/core/discord/autocompleteUsername.ts b/src/core/discord/autocompleteUsername.ts index 333b51624..a41b3860c 100644 --- a/src/core/discord/autocompleteUsername.ts +++ b/src/core/discord/autocompleteUsername.ts @@ -3,8 +3,8 @@ import { AutocompleteInteraction, CacheType, } from 'discord.js'; -import { REGION_NAMES_SHORT } from '../../constants/regions'; import searchPlayersAcrossRegions from '../blitz/searchPlayersAcrossRegions'; +import { translator } from '../localization/translator'; import { DISCORD_CHOICES_MAX_NAME_SIZE, OVERFLOW_SUFFIX, @@ -13,6 +13,7 @@ import { export default async function autocompleteUsername( interaction: AutocompleteInteraction, ) { + const { translate } = translator(interaction.locale); const focusedOption = interaction.options.getFocused(true); if (focusedOption.name !== 'username') return; const players = await searchPlayersAcrossRegions(focusedOption.value); @@ -21,9 +22,9 @@ export default async function autocompleteUsername( await interaction.respond( players ? players.map((player) => { - let name = `${player.nickname} (${ - REGION_NAMES_SHORT[player.region] - })`; + let name = `${player.nickname} (${translate( + `common.regions.short.${player.region}`, + )})`; if (name.length > DISCORD_CHOICES_MAX_NAME_SIZE) { const overSize = name.length - DISCORD_CHOICES_MAX_NAME_SIZE; @@ -31,7 +32,9 @@ export default async function autocompleteUsername( name = `${player.nickname.slice( 0, player.nickname.length - overSize - OVERFLOW_SUFFIX.length, - )}${OVERFLOW_SUFFIX} (${REGION_NAMES_SHORT[player.region]})`; + )}${OVERFLOW_SUFFIX} (${translate( + `common.regions.short.${player.region}`, + )})`; } return { diff --git a/src/core/discord/localizationObject.ts b/src/core/discord/localizationObject.ts index 28f2f0e90..2a41e7963 100644 --- a/src/core/discord/localizationObject.ts +++ b/src/core/discord/localizationObject.ts @@ -2,13 +2,13 @@ import { Locale } from 'discord.js'; import { locales } from '../localization/strings'; import { translator } from '../localization/translator'; -export function localizationObject(path: string) { +export function localizationObject(path: string, literals?: string[]) { return locales.reduce>>( (localizations, locale) => { const { translate } = translator(locale); return { ...localizations, - [locale]: translate(path), + [locale]: translate(path, literals), }; }, {}, diff --git a/src/events/interactionCreate/index.ts b/src/events/interactionCreate/index.ts index 3008bf026..3f21ef942 100644 --- a/src/events/interactionCreate/index.ts +++ b/src/events/interactionCreate/index.ts @@ -101,8 +101,15 @@ export const COMMANDS_RAW: Promise[] = [ export const commands = Promise.allSettled(COMMANDS_RAW).then((rawCommands) => { return rawCommands.reduce>( - (commands, registry) => { - if (registry.status === 'rejected') return commands; + (commands, registry, index) => { + if (registry.status === 'rejected') { + console.warn( + `Command ${index} failed to load; skipping...`, + registry.reason, + ); + + return commands; + } if (isDev()) registry.value.command.setDefaultMemberPermissions(0); return { ...commands, [registry.value.command.name]: registry.value }; }, diff --git a/src/lang/en-US.json b/src/lang/en-US.json index 9f5868b84..fcc60b649 100644 --- a/src/lang/en-US.json +++ b/src/lang/en-US.json @@ -23,6 +23,27 @@ "researchable": "Tech tree", "premium": "Premium", "collector": "Collector" + }, + + "regions": { + "normal": { + "com": "North America", + "eu": "Europe", + "asia": "Asia" + }, + "short": { + "com": "NA", + "eu": "EU", + "asia": "ASIA" + } + }, + + "leagues": { + "diamond": "Diamond", + "platinum": "Platinum", + "gold": "Gold", + "silver": "Silver", + "bronze": "Bronze" } }, @@ -109,6 +130,11 @@ "$": "tree-type", "description": "Tree type" } + }, + + "region": { + "$": "region", + "description": "Region" } }, @@ -261,6 +287,105 @@ } } } + }, + + "link": { + "$": "link", + "description": "Links your Blitz and Discord account", + + "body": { + "accounts_linked": "Accounts linked", + "no_role_permissions": { + "$": "%s failed to verify", + "description": "I don't have the permission to change your manage roles." + } + }, + + "embed": { + "title": "Incorrect account?", + "description": "Use the command again and select a username from the search results." + } + }, + + "search_clans": { + "$": "search-clans", + "description": "Search clans", + + "options": { + "limit": { + "$": "limit", + "description": "The max number of clan results (default: %s)" + } + }, + + "body": { + "title": "Clan search for %s in region %s", + "no_results": "No clans found" + } + }, + + "search_players": { + "$": "search-players", + "description": "Search players", + + "options": { + "limit": { + "$": "limit", + "description": "The max number of player results (default: %s)" + } + }, + + "body": { + "title": "Player search for %s in region %s", + "no_results": "No players found" + } + }, + + "ratings": { + "$": "ratings", + "description": "Ratings battles statistics", + + "subcommands": { + "neighbors": { + "$": "neighbors", + "description": "Neighboring players on the leaderboard" + }, + + "league": { + "$": "league", + "description": "Top players of a league" + } + }, + + "options": { + "limit": { + "$": "limit", + "description": "How many neighbors to display (default: %s)" + }, + + "season": { + "$": "season", + "description": "Season number (default: current)", + + "choices": { + "current": "%s (current)" + } + }, + + "league": { + "$": "league", + "description": "The league to display" + } + }, + + "body": { + "top_players": "Top %s players", + "no_participation": "# %s didn't play ratings in season %s\n\nThis player did not participate in this season or did not get past calibration.", + "no_ongoing_season": "# No ongoing season\n\nThere isn't a currently running ratings season.", + "deleted_player": "Deleted %s", + "neighbors": "Ratings neighbors", + "more": "+%s more" + } } } }, From 19d2ad4cb2aec3e7e4c46a07ed22c29b7eff6c04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Thu, 7 Mar 2024 18:29:53 -0600 Subject: [PATCH 51/90] New translations en-us.json (Spanish) --- src/lang/es-ES.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lang/es-ES.json b/src/lang/es-ES.json index a58c85102..cc0c1dffe 100644 --- a/src/lang/es-ES.json +++ b/src/lang/es-ES.json @@ -11,11 +11,11 @@ "european": "Nación europea", "other": "Nación híbrida" }, - "tank_type_short": { - "lightTank": "Ligero", - "mediumTank": "Mediano", - "heavyTank": "Pesado", - "AT-SPG": "Cazacarro" + "tank_class_short": { + "lightTank": "Light", + "mediumTank": "Medium", + "heavyTank": "Heavy", + "AT-SPG": "TD" }, "tree_type": { "researchable": "Árbol tecnológico", @@ -111,9 +111,9 @@ "$": "nación", "description": "Nación" }, - "tank_type": { - "$": "tipo-tanque", - "description": "Tipo de tanque" + "tank_class": { + "$": "tank-class", + "description": "Tank class" }, "tree_type": { "$": "tree-type", From 53174db3519cbaa3380e3d70230412a8e59b8f0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=C3=A8sAbhi?= <43380238+tresabhi@users.noreply.github.com> Date: Thu, 7 Mar 2024 18:30:02 -0600 Subject: [PATCH 52/90] feat: rename tank types to tank classes --- scripts/buildAssets/definitions.ts | 4 +-- src/app/embeds/session/page.tsx | 2 +- .../[id]/components/Characteristics/index.tsx | 29 ++++++------------- .../tankopedia/[id]/components/Title.tsx | 2 +- .../TankSearch/components/Options/index.tsx | 8 ++--- .../components/TankSearch/index.tsx | 8 ++--- src/commands/breakdown.tsx | 2 +- src/commands/ownedTanks.tsx | 4 +-- src/components/Breakdown/components/Row.tsx | 4 +-- src/components/Tanks/components/Item.tsx | 8 ++--- src/core/blitzkrieg/filtersToDescription.ts | 2 +- src/core/blitzkrieg/tankDefinitions.ts | 10 +++---- src/core/discord/addFilterOptions.ts | 16 +++++----- src/core/discord/getFiltersFromButton.ts | 2 +- src/core/discord/getFiltersFromCommand.ts | 2 +- src/core/statistics/filterStats.ts | 2 +- src/lang/en-US.json | 8 ++--- src/stores/tankopedia.ts | 4 +-- 18 files changed, 53 insertions(+), 64 deletions(-) diff --git a/scripts/buildAssets/definitions.ts b/scripts/buildAssets/definitions.ts index 1dbd313a1..d73cf9208 100644 --- a/scripts/buildAssets/definitions.ts +++ b/scripts/buildAssets/definitions.ts @@ -2,7 +2,7 @@ import { readdir } from 'fs/promises'; import { parse as parsePath } from 'path'; import { Vector3Tuple } from 'three'; import { parse as parseYaml } from 'yaml'; -import { TankType } from '../../src/components/Tanks'; +import { TankClass } from '../../src/components/Tanks'; import { readXMLDVPL } from '../../src/core/blitz/readXMLDVPL'; import { readYAMLDVPL } from '../../src/core/blitz/readYAMLDVPL'; import { toUniqueId } from '../../src/core/blitz/toUniqueId'; @@ -488,7 +488,7 @@ export async function definitions(production: boolean) { ? 'premium' : 'researchable', tier: tank.level as Tier, - type: tankTags[0] as TankType, + class: tankTags[0] as TankClass, testing: tankTags.includes('testTank') ? true : undefined, price: tankPrice, camouflage: { diff --git a/src/app/embeds/session/page.tsx b/src/app/embeds/session/page.tsx index c4b81a6f9..85b766b22 100644 --- a/src/app/embeds/session/page.tsx +++ b/src/app/embeds/session/page.tsx @@ -311,7 +311,7 @@ export default function SessionPage({ naked = true }: SessionPageProps) { tankDefinitions?.name ?? `Unknown tank ${stats.tank_id}` } type="tank" - tankType={tankDefinitions?.type} + tankType={tankDefinitions?.class} treeType={tankDefinitions?.treeType} stats={session.customColumns.map((customColumn) => { const rowStat = rowStats[customColumn.display]; diff --git a/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx b/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx index dae69588d..a7b4b308c 100644 --- a/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx +++ b/src/app/tools/tankopedia/[id]/components/Characteristics/index.tsx @@ -136,12 +136,13 @@ export function Characteristics() { ]); const enginePowerCoefficient = coefficient( [ - hasEngineAccelerator && (tank.type === 'lightTank' || tank.type === 'mediumTank'), + hasEngineAccelerator && + (tank.class === 'lightTank' || tank.class === 'mediumTank'), 0.05, ], [ hasEngineAccelerator && - (tank.type === 'heavyTank' || tank.type === 'AT-SPG'), + (tank.class === 'heavyTank' || tank.class === 'AT-SPG'), 0.07, ], [hasEnginePowerBoost, 0.2], @@ -163,7 +164,7 @@ export function Characteristics() { const viewRangeCoefficient = coefficient([ hasImprovedOptics, - tank.type === 'AT-SPG' ? 0.05 : tank.type === 'heavyTank' ? 0.07 : 0.1, + tank.class === 'AT-SPG' ? 0.05 : tank.class === 'heavyTank' ? 0.07 : 0.1, ]) * coefficient([true, progressiveStat(commanderMastery)]); const fireChanceCoefficient = coefficient([hasProtectiveKit, -0.2]); @@ -180,38 +181,26 @@ export function Characteristics() { const camouflageSumMoving = sum( [ hasCamouflageNet, - tank.type === 'heavyTank' - ? 0.03 - : tank.type === 'AT-SPG' - ? 0.07 - : 0.05, + tank.class === 'heavyTank' ? 0.03 : tank.class === 'AT-SPG' ? 0.07 : 0.05, ], [ camouflage, - tank.type === 'AT-SPG' - ? 0.04 - : tank.type === 'heavyTank' - ? 0.03 - : 0.02, + tank.class === 'AT-SPG' ? 0.04 : tank.class === 'heavyTank' ? 0.03 : 0.02, ], ); const camouflageSumStill = sum( [ hasCamouflageNet, 2 * - (tank.type === 'heavyTank' + (tank.class === 'heavyTank' ? 0.03 - : tank.type === 'AT-SPG' + : tank.class === 'AT-SPG' ? 0.07 : 0.05), ], [ camouflage, - tank.type === 'AT-SPG' - ? 0.04 - : tank.type === 'heavyTank' - ? 0.03 - : 0.02, + tank.class === 'AT-SPG' ? 0.04 : tank.class === 'heavyTank' ? 0.03 : 0.02, ], ); diff --git a/src/app/tools/tankopedia/[id]/components/Title.tsx b/src/app/tools/tankopedia/[id]/components/Title.tsx index 24a8febde..22caff8ce 100644 --- a/src/app/tools/tankopedia/[id]/components/Title.tsx +++ b/src/app/tools/tankopedia/[id]/components/Title.tsx @@ -30,7 +30,7 @@ export function Title() { - {[...TANK_TYPES].map((type, index) => ( + {[...TANK_CLASSES].map((type, index) => (