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