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] 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" } } }