From cb40910fa48eb44220e7e3ffc4428b222b177152 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20Oszcz=C4=99da?= Date: Sun, 10 Nov 2024 21:12:03 +0000 Subject: [PATCH 01/17] feat: Use the translation language for getting words rather than the localisation language. --- source/library/commands/fragments/autocomplete/language.ts | 4 +++- source/library/commands/handlers/context.ts | 2 +- source/library/commands/handlers/word.ts | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/source/library/commands/fragments/autocomplete/language.ts b/source/library/commands/fragments/autocomplete/language.ts index 801f06915..9ac583736 100644 --- a/source/library/commands/fragments/autocomplete/language.ts +++ b/source/library/commands/fragments/autocomplete/language.ts @@ -2,9 +2,11 @@ import { trim } from "logos:constants/formatting"; import type { Client } from "logos/client"; import { handleSimpleAutocomplete } from "logos/commands/fragments/autocomplete/simple"; +type LanguageType = keyof typeof constants.languages.languages; async function handleAutocompleteLanguage( client: Client, interaction: Logos.Interaction, + { type }: { type: LanguageType }, ): Promise { const strings = constants.contexts.autocompleteLanguage({ localise: client.localise.bind(client), @@ -18,7 +20,7 @@ async function handleAutocompleteLanguage( await handleSimpleAutocomplete(client, interaction, { query: interaction.parameters.language, - elements: constants.languages.languages.localisation, + elements: constants.languages.languages[type], getOption: (language) => ({ name: client.localise(constants.localisations.languages[language], interaction.locale)(), value: language, diff --git a/source/library/commands/handlers/context.ts b/source/library/commands/handlers/context.ts index c7e44c3fa..19a42a295 100644 --- a/source/library/commands/handlers/context.ts +++ b/source/library/commands/handlers/context.ts @@ -9,7 +9,7 @@ async function handleFindInContextAutocomplete( client: Client, interaction: Logos.Interaction, ): Promise { - await handleAutocompleteLanguage(client, interaction); + await handleAutocompleteLanguage(client, interaction, { type: "localisation" }); } async function handleFindInContext( diff --git a/source/library/commands/handlers/word.ts b/source/library/commands/handlers/word.ts index fc1f462ec..360450ff7 100644 --- a/source/library/commands/handlers/word.ts +++ b/source/library/commands/handlers/word.ts @@ -1,5 +1,5 @@ import { code, trim } from "logos:constants/formatting"; -import { isLocalisationLanguage } from "logos:constants/languages/localisation"; +import { isTranslationLanguage } from "logos:constants/languages/translation"; import { type PartOfSpeech, isUnknownPartOfSpeech } from "logos:constants/parts-of-speech"; import type { DefinitionField, DictionaryEntry, ExpressionField } from "logos/adapters/dictionaries/dictionary-entry"; import type { Client } from "logos/client"; @@ -11,7 +11,7 @@ async function handleFindWordAutocomplete( client: Client, interaction: Logos.Interaction, ): Promise { - await handleAutocompleteLanguage(client, interaction); + await handleAutocompleteLanguage(client, interaction, { type: "translation" }); } /** Allows the user to look up a word and get information about it. */ @@ -19,7 +19,7 @@ async function handleFindWord( client: Client, interaction: Logos.Interaction, ): Promise { - if (interaction.parameters.language !== undefined && !isLocalisationLanguage(interaction.parameters.language)) { + if (interaction.parameters.language !== undefined && !isTranslationLanguage(interaction.parameters.language)) { const strings = constants.contexts.invalidLanguage({ localise: client.localise, locale: interaction.locale }); client.error(interaction, { title: strings.title, description: strings.description }).ignore(); From faa2433c8883798dbe0cd044169dbdf4563b5a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20Oszcz=C4=99da?= Date: Sun, 10 Nov 2024 21:21:56 +0000 Subject: [PATCH 02/17] refactor: Move Discord-specific constants into their own file. --- source/constants/constants.ts | 7 ++----- source/constants/discord.ts | 10 ++++++++++ source/library/collectors.ts | 2 +- source/library/commands/handlers/music/history.ts | 2 +- source/library/commands/handlers/music/queue.ts | 2 +- source/library/commands/handlers/music/remove.ts | 2 +- source/library/services/role-indicators.ts | 2 +- source/library/stores/interactions.ts | 4 +++- .../stores/journalling/discord/message-update.ts | 6 ++++-- test/source/constants/discord.spec.ts | 9 +++++++++ 10 files changed, 33 insertions(+), 13 deletions(-) create mode 100644 source/constants/discord.ts create mode 100644 test/source/constants/discord.spec.ts diff --git a/source/constants/constants.ts b/source/constants/constants.ts index de1f9bcee..875328ee2 100644 --- a/source/constants/constants.ts +++ b/source/constants/constants.ts @@ -7,6 +7,7 @@ import database from "logos:constants/database"; import defaults from "logos:constants/defaults"; import dictionaries from "logos:constants/dictionaries"; import directories from "logos:constants/directories"; +import discord from "logos:constants/discord"; import emojis from "logos:constants/emojis"; import endpoints from "logos:constants/endpoints"; import gifs from "logos:constants/gifs"; @@ -39,16 +40,11 @@ const constants = Object.freeze({ MAXIMUM_DELETABLE_MESSAGES: 500, MAXIMUM_INDEXABLE_MESSAGES: 1000, MAXIMUM_CORRECTION_MESSAGE_LENGTH: 3072, - MAXIMUM_USERNAME_LENGTH: 32, MAXIMUM_VOLUME: 100, MAXIMUM_HISTORY_ENTRIES: 100, MAXIMUM_QUEUE_ENTRIES: 100, - MAXIMUM_EMBED_FIELD_LENGTH: 1024, - MAXIMUM_EMBED_DESCRIPTION_LENGTH: 3072, RESULTS_PER_PAGE: 10, STATUS_CYCLE_PERIOD: 10 * time.second, - // The 10 seconds are to account for potential network lag. - INTERACTION_TOKEN_EXPIRY: 15 * time.minute - 10 * time.second, SLOWMODE_COLLISION_TIMEOUT: 5 * time.second, AUTO_DELETE_MESSAGE_TIMEOUT: 10 * time.second, PICK_MISSING_WORD_CHOICES: 4, @@ -67,6 +63,7 @@ export default Object.freeze({ defaults, dictionaries, directories, + discord, emojis, endpoints, gifs, diff --git a/source/constants/discord.ts b/source/constants/discord.ts new file mode 100644 index 000000000..6493e5a3d --- /dev/null +++ b/source/constants/discord.ts @@ -0,0 +1,10 @@ +import time from "logos:constants/time"; + +export default Object.freeze({ + // The 10 seconds are to account for potential network lag. + INTERACTION_TOKEN_EXPIRY: 15 * time.minute - 10 * time.second, + MAXIMUM_USERNAME_LENGTH: 32, + MAXIMUM_EMBED_FIELD_LENGTH: 1024, + MAXIMUM_EMBED_DESCRIPTION_LENGTH: 3072, + MAXIMUM_AUTOCOMPLETE_CHOICES: 25, +} as const); diff --git a/source/library/collectors.ts b/source/library/collectors.ts index 0e7f917ab..16dbc883d 100644 --- a/source/library/collectors.ts +++ b/source/library/collectors.ts @@ -154,7 +154,7 @@ class InteractionCollector< super({ guildId, isSingle, - removeAfter: isPermanent ? undefined : constants.INTERACTION_TOKEN_EXPIRY, + removeAfter: isPermanent ? undefined : constants.discord.INTERACTION_TOKEN_EXPIRY, dependsOn, }); diff --git a/source/library/commands/handlers/music/history.ts b/source/library/commands/handlers/music/history.ts index 3427fde93..255f365b1 100644 --- a/source/library/commands/handlers/music/history.ts +++ b/source/library/commands/handlers/music/history.ts @@ -37,7 +37,7 @@ async function handleDisplayPlaybackHistory(client: Client, interaction: Logos.I setTimeout(() => { musicService.session.listings.off("history", refreshView); musicService.session.off("end", closeView); - }, constants.INTERACTION_TOKEN_EXPIRY); + }, constants.discord.INTERACTION_TOKEN_EXPIRY); await view.open(); } diff --git a/source/library/commands/handlers/music/queue.ts b/source/library/commands/handlers/music/queue.ts index c77ef56dc..532559b0c 100644 --- a/source/library/commands/handlers/music/queue.ts +++ b/source/library/commands/handlers/music/queue.ts @@ -37,7 +37,7 @@ async function handleDisplayPlaybackQueue(client: Client, interaction: Logos.Int setTimeout(() => { musicService.session.listings.off("queue", refreshView); musicService.session.off("end", closeView); - }, constants.INTERACTION_TOKEN_EXPIRY); + }, constants.discord.INTERACTION_TOKEN_EXPIRY); await view.open(); } diff --git a/source/library/commands/handlers/music/remove.ts b/source/library/commands/handlers/music/remove.ts index 15e53d9b8..e0a8ecfb5 100644 --- a/source/library/commands/handlers/music/remove.ts +++ b/source/library/commands/handlers/music/remove.ts @@ -70,7 +70,7 @@ async function handleRemoveSongListing(client: Client, interaction: Logos.Intera setTimeout(() => { musicService.session.listings.off("queue", refreshView); musicService.session.off("end", closeView); - }, constants.INTERACTION_TOKEN_EXPIRY); + }, constants.discord.INTERACTION_TOKEN_EXPIRY); await view.open(); } diff --git a/source/library/services/role-indicators.ts b/source/library/services/role-indicators.ts index c4322bdfa..3dd88ae57 100644 --- a/source/library/services/role-indicators.ts +++ b/source/library/services/role-indicators.ts @@ -114,7 +114,7 @@ class RoleIndicatorService extends LocalService { function getNicknameWithRoleIndicators(username: string, indicators: string[]): string { const indicatorsFormatted = indicators.join(constants.special.sigils.separator); const modification = `${constants.special.sigils.divider}${indicatorsFormatted}`; - const usernameSlice = username.slice(0, constants.MAXIMUM_USERNAME_LENGTH - modification.length); + const usernameSlice = username.slice(0, constants.discord.MAXIMUM_USERNAME_LENGTH - modification.length); return `${usernameSlice}${modification}`; } diff --git a/source/library/stores/interactions.ts b/source/library/stores/interactions.ts index 9f5e88fea..9627b2d78 100644 --- a/source/library/stores/interactions.ts +++ b/source/library/stores/interactions.ts @@ -254,12 +254,14 @@ class InteractionStore { } #registerMessage(interaction: Logos.Interaction, { messageId }: { messageId: bigint }): void { - setTimeout(() => this.#unregisterMessage(interaction), constants.INTERACTION_TOKEN_EXPIRY); + setTimeout(() => this.#unregisterMessage(interaction), constants.discord.INTERACTION_TOKEN_EXPIRY); this.#messages.set(interaction.token, messageId); } #unregisterMessage(interaction: Logos.Interaction): void { + // TODO(vxern): The timeout to unregister the message should be cleared immediately. + this.#messages.delete(interaction.token); } diff --git a/source/library/stores/journalling/discord/message-update.ts b/source/library/stores/journalling/discord/message-update.ts index 702ff5559..6f62f5f8b 100644 --- a/source/library/stores/journalling/discord/message-update.ts +++ b/source/library/stores/journalling/discord/message-update.ts @@ -24,11 +24,13 @@ const logger: EventLogger<"messageUpdate"> = (client, [message], { guildLocale } fields: [ { name: strings.fields.before, - value: codeMultiline(trim(oldMessage.content, constants.MAXIMUM_EMBED_FIELD_LENGTH - 6)), + value: codeMultiline( + trim(oldMessage.content, constants.discord.MAXIMUM_EMBED_FIELD_LENGTH - 6), + ), }, { name: strings.fields.after, - value: codeMultiline(trim(message.content, constants.MAXIMUM_EMBED_FIELD_LENGTH - 6)), + value: codeMultiline(trim(message.content, constants.discord.MAXIMUM_EMBED_FIELD_LENGTH - 6)), }, ], }, diff --git a/test/source/constants/discord.spec.ts b/test/source/constants/discord.spec.ts new file mode 100644 index 000000000..70696bf77 --- /dev/null +++ b/test/source/constants/discord.spec.ts @@ -0,0 +1,9 @@ +import { describe, it } from "bun:test"; +import discord from "logos:constants/discord"; +import { expect } from "chai"; + +describe("The discord object", () => { + it("is immutable.", () => { + expect(Object.isFrozen(discord)).to.be.true; + }); +}); From caf1f0d3fea966c3ee7cf233f15966418fc7fe57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20Oszcz=C4=99da?= Date: Sun, 10 Nov 2024 21:22:20 +0000 Subject: [PATCH 03/17] fix: `handleSimpleAutocomplete()` not limiting the number of autocomplete choices. --- source/library/commands/fragments/autocomplete/simple.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/source/library/commands/fragments/autocomplete/simple.ts b/source/library/commands/fragments/autocomplete/simple.ts index 54c33de97..9e153d5eb 100644 --- a/source/library/commands/fragments/autocomplete/simple.ts +++ b/source/library/commands/fragments/autocomplete/simple.ts @@ -12,7 +12,8 @@ async function handleSimpleAutocomplete( const queryLowercase = query.trim().toLowerCase(); const choices = elements .map((element) => getOption(element)) - .filter((choice) => choice.name.toLowerCase().includes(queryLowercase)); + .filter((choice) => choice.name.toLowerCase().includes(queryLowercase)) + .slice(0, constants.discord.MAXIMUM_AUTOCOMPLETE_CHOICES); client.respond(interaction, choices).ignore(); } From c8f6fdeb0a6ac806310800a56ad13791f2c6d312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20Oszcz=C4=99da?= Date: Sun, 10 Nov 2024 21:35:18 +0000 Subject: [PATCH 04/17] feat: Restore previous implementation for the time being. --- source/library/commands/handlers/word.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/source/library/commands/handlers/word.ts b/source/library/commands/handlers/word.ts index 360450ff7..7014c45a2 100644 --- a/source/library/commands/handlers/word.ts +++ b/source/library/commands/handlers/word.ts @@ -1,5 +1,5 @@ import { code, trim } from "logos:constants/formatting"; -import { isTranslationLanguage } from "logos:constants/languages/translation"; +import { isLearningLanguage } from "logos:constants/languages/learning"; import { type PartOfSpeech, isUnknownPartOfSpeech } from "logos:constants/parts-of-speech"; import type { DefinitionField, DictionaryEntry, ExpressionField } from "logos/adapters/dictionaries/dictionary-entry"; import type { Client } from "logos/client"; @@ -11,7 +11,7 @@ async function handleFindWordAutocomplete( client: Client, interaction: Logos.Interaction, ): Promise { - await handleAutocompleteLanguage(client, interaction, { type: "translation" }); + await handleAutocompleteLanguage(client, interaction, { type: "localisation" }); } /** Allows the user to look up a word and get information about it. */ @@ -19,7 +19,7 @@ async function handleFindWord( client: Client, interaction: Logos.Interaction, ): Promise { - if (interaction.parameters.language !== undefined && !isTranslationLanguage(interaction.parameters.language)) { + if (interaction.parameters.language !== undefined && !isLearningLanguage(interaction.parameters.language)) { const strings = constants.contexts.invalidLanguage({ localise: client.localise, locale: interaction.locale }); client.error(interaction, { title: strings.title, description: strings.description }).ignore(); From eebd46db2d9f0736da5f4eef807270dd488b851b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20Oszcz=C4=99da?= Date: Sat, 23 Nov 2024 19:39:09 +0000 Subject: [PATCH 05/17] fix: Simplify code and fix Lingvanex languages missing from array. --- source/constants/languages.ts | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/source/constants/languages.ts b/source/constants/languages.ts index 9004e5723..890206c5f 100644 --- a/source/constants/languages.ts +++ b/source/constants/languages.ts @@ -13,14 +13,23 @@ import { languages as translationLanguages, } from "logos:constants/languages/translation"; +function getLanguages(languagesByPlatform: Record): T[] { + const languages = Object.entries(languagesByPlatform) + .reduce((result, [_, languages]) => { + result.push(...languages); + + return result; + }, []) + .sort((a, b) => a.localeCompare(b, "en", { sensitivity: "base" })); + + return [...new Set(languages)]; +} + const languages = Object.freeze({ languages: { - localisation: [ - ...new Set([...localisationLanguages.discord, ...localisationLanguages.logos]), - ].sort((a, b) => a.localeCompare(b, "en", { sensitivity: "base" })), - translation: [ - ...new Set([...translationLanguages.deepl, ...translationLanguages.google]), - ].sort((a, b) => a.localeCompare(b, "en", { sensitivity: "base" })), + all: [], + localisation: getLanguages(localisationLanguages as unknown as Record), + translation: getLanguages(translationLanguages as unknown as Record), }, locales: { discord: Object.values(localisationLanguageToLocale.discord) as DiscordLocale[], From 66de53d289bf2970dd6893c225aac6d36bbd7ee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20Oszcz=C4=99da?= Date: Sat, 23 Nov 2024 20:19:14 +0000 Subject: [PATCH 06/17] fix: Lingvanex languages not being taken into account when checking for translation language. --- source/constants/languages/translation.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/source/constants/languages/translation.ts b/source/constants/languages/translation.ts index 5bee3e1db..ba725e616 100644 --- a/source/constants/languages/translation.ts +++ b/source/constants/languages/translation.ts @@ -609,8 +609,20 @@ const localeToLanguage = { lingvanex: Object.mirror(languageToLocale.lingvanex), }; +function isDeepLLanguage(language: string): language is DeepLLanguage { + return language in languageToLocale.deepl; +} + +function isGoogleTranslateLanguage(language: string): language is GoogleTranslateLanguage { + return language in languageToLocale.google; +} + +function isLingvanexLanguage(language: string): language is LingvanexLanguage { + return language in languageToLocale.lingvanex; +} + function isTranslationLanguage(language: string): language is TranslationLanguage { - return language in languageToLocale.deepl || language in languageToLocale.google; + return isDeepLLanguage(language) || isGoogleTranslateLanguage(language) || isLingvanexLanguage(language); } function isDeepLLocale(locale: string): locale is DeepLLocale { From 13a61b7b3d9127764aadc3422a1903c87d09d3fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20Oszcz=C4=99da?= Date: Sat, 23 Nov 2024 20:21:15 +0000 Subject: [PATCH 07/17] feat: Make `isDiscordLanguage()` and `isLogosLanguage()` `O(1)` rather than `O(n)`. --- source/constants/languages/localisation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/constants/languages/localisation.ts b/source/constants/languages/localisation.ts index a263cf739..a28501a45 100644 --- a/source/constants/languages/localisation.ts +++ b/source/constants/languages/localisation.ts @@ -98,11 +98,11 @@ type LogosLocale = (typeof languageToLocale.logos)[keyof typeof languageToLocale type LocalisationLocale = LogosLocale; function isDiscordLanguage(language: string): language is DiscordLanguage { - return (languages.discord as readonly string[]).includes(language); + return language in languageToLocale.discord; } function isLogosLanguage(language: string): language is LogosLanguage { - return (languages.logos as readonly string[]).includes(language); + return language in languageToLocale.logos; } function isLocalisationLanguage(language: string): language is LocalisationLanguage { From 589fa3a16768a9b7c3f0d6d69fa41320bdf4f7e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20Oszcz=C4=99da?= Date: Sat, 23 Nov 2024 20:23:06 +0000 Subject: [PATCH 08/17] feat: Add `isDetectionLanguage()`. --- source/constants/languages/detection.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/source/constants/languages/detection.ts b/source/constants/languages/detection.ts index f7705b87b..84676741c 100644 --- a/source/constants/languages/detection.ts +++ b/source/constants/languages/detection.ts @@ -980,6 +980,28 @@ const localeToLanguage = { eld: Object.mirror(languageToLocale.eld), }; +function isCLDLanguage(language: string): language is CLDLanguage { + return language in languageToLocale.cld; +} + +function isTinyLDLanguage(language: string): language is TinyLDLanguage { + return language in languageToLocale.tinyld; +} + +function isFastTextLanguage(language: string): language is FastTextLanguage { + return language in languageToLocale.fasttext; +} + +function isELDLanguage(language: string): language is ELDLanguage { + return language in languageToLocale.eld; +} + +function isDetectionLanguage(language: string): language is DetectionLanguage { + return ( + isCLDLanguage(language) || isTinyLDLanguage(language) || isFastTextLanguage(language) || isELDLanguage(language) + ); +} + function isCLDLocale(locale: string): locale is CLDLocale { return locale in localeToLanguage.cld; } @@ -1013,6 +1035,7 @@ function getELDLanguageByLocale(locale: ELDLocale): ELDLanguage { } export { + isDetectionLanguage, getTinyLDLanguageByLocale, isTinyLDLocale, isCLDLocale, @@ -1021,6 +1044,7 @@ export { getFastTextLanguageByLocale, isELDLocale, getELDLanguageByLocale, + languages, }; export type { Detector, From a42dabf067a4a32e612cb46d2afc90ef31ce7ed9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20Oszcz=C4=99da?= Date: Sat, 23 Nov 2024 20:23:33 +0000 Subject: [PATCH 09/17] feat: Export `languages`. --- source/constants/languages/feature.ts | 2 +- source/constants/languages/learning.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/source/constants/languages/feature.ts b/source/constants/languages/feature.ts index cceb8d199..52a9ad50b 100644 --- a/source/constants/languages/feature.ts +++ b/source/constants/languages/feature.ts @@ -21,5 +21,5 @@ function isFeatureLanguage(language: string): language is FeatureLanguage { return (languages as readonly string[]).includes(language); } -export { isFeatureLanguage }; +export { isFeatureLanguage, languages }; export type { FeatureLanguage }; diff --git a/source/constants/languages/learning.ts b/source/constants/languages/learning.ts index 1e3c19b3f..2b410d389 100644 --- a/source/constants/languages/learning.ts +++ b/source/constants/languages/learning.ts @@ -5,8 +5,11 @@ import { getLocalisationLocaleByLanguage, isLocalisationLanguage, isLocalisationLocale, + languages as localisationLanguages, } from "logos:constants/languages/localisation"; +const languages = localisationLanguages; + type LearningLanguage = LocalisationLanguage; function isLearningLanguage(language: string): language is LearningLanguage { @@ -33,5 +36,5 @@ function getWiktionaryLanguageName(language: LearningLanguage): string { return (wiktionaryLanguageNames as Record)[language] ?? language; } -export { isLearningLanguage, isLearningLocale, getLocaleByLearningLanguage, getWiktionaryLanguageName }; +export { isLearningLanguage, isLearningLocale, getLocaleByLearningLanguage, getWiktionaryLanguageName, languages }; export type { LearningLanguage }; From f87eac595cde7a6c12ba42d059c6aa870ae81b82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20Oszcz=C4=99da?= Date: Sat, 23 Nov 2024 22:46:04 +0000 Subject: [PATCH 10/17] fix: Checks. --- source/constants/languages/localisation.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source/constants/languages/localisation.ts b/source/constants/languages/localisation.ts index a28501a45..09bcc4ecf 100644 --- a/source/constants/languages/localisation.ts +++ b/source/constants/languages/localisation.ts @@ -106,7 +106,7 @@ function isLogosLanguage(language: string): language is LogosLanguage { } function isLocalisationLanguage(language: string): language is LocalisationLanguage { - return isLogosLanguage(language) ?? isDiscordLanguage(language); + return isLogosLanguage(language) || isDiscordLanguage(language); } function isDiscordLocale(locale: string): locale is DiscordLocale { @@ -118,7 +118,7 @@ function isLogosLocale(locale: string): locale is LogosLocale { } function isLocalisationLocale(locale: string): locale is LocalisationLocale { - return isLogosLocale(locale) ?? isDiscordLocale(locale); + return isLogosLocale(locale) || isDiscordLocale(locale); } function getDiscordLocaleByLanguage(language: DiscordLanguage): DiscordLocale { From 59b6ed179fb7c7eae2e97012472aaf83db352542 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20Oszcz=C4=99da?= Date: Sun, 24 Nov 2024 01:17:40 +0000 Subject: [PATCH 11/17] misc: Fix circular dependency by moving functions into another file. --- source/constants/languages.ts | 27 +++++++++---------------- source/constants/languages/languages.ts | 17 ++++++++++++++++ 2 files changed, 26 insertions(+), 18 deletions(-) create mode 100644 source/constants/languages/languages.ts diff --git a/source/constants/languages.ts b/source/constants/languages.ts index 890206c5f..b86c4e707 100644 --- a/source/constants/languages.ts +++ b/source/constants/languages.ts @@ -1,6 +1,6 @@ -import type { DetectionLanguage } from "logos:constants/languages/detection"; -import type { FeatureLanguage } from "logos:constants/languages/feature"; -import type { LearningLanguage } from "logos:constants/languages/learning"; +import { languages as detectionLanguages, type DetectionLanguage } from "logos:constants/languages/detection"; +import { languages as featureLanguages, type FeatureLanguage } from "logos:constants/languages/feature"; +import { languages as learningLanguages, type LearningLanguage } from "logos:constants/languages/learning"; import { type DiscordLocale, type LocalisationLanguage, @@ -12,24 +12,15 @@ import { isTranslationLanguage, languages as translationLanguages, } from "logos:constants/languages/translation"; - -function getLanguages(languagesByPlatform: Record): T[] { - const languages = Object.entries(languagesByPlatform) - .reduce((result, [_, languages]) => { - result.push(...languages); - - return result; - }, []) - .sort((a, b) => a.localeCompare(b, "en", { sensitivity: "base" })); - - return [...new Set(languages)]; -} +import { collectLanguages, sortLanguages } from "logos:constants/languages/languages"; const languages = Object.freeze({ languages: { - all: [], - localisation: getLanguages(localisationLanguages as unknown as Record), - translation: getLanguages(translationLanguages as unknown as Record), + detection: sortLanguages(collectLanguages(detectionLanguages)), + feature: sortLanguages([...featureLanguages]), + learning: sortLanguages([...learningLanguages]), + localisation: sortLanguages(collectLanguages(localisationLanguages)), + translation: sortLanguages(collectLanguages(translationLanguages)), }, locales: { discord: Object.values(localisationLanguageToLocale.discord) as DiscordLocale[], diff --git a/source/constants/languages/languages.ts b/source/constants/languages/languages.ts new file mode 100644 index 000000000..a319a8606 --- /dev/null +++ b/source/constants/languages/languages.ts @@ -0,0 +1,17 @@ +import type { Language } from "logos:constants/languages"; + +function sortLanguages(languages: T[]) { + return languages.sort((a, b) => a.localeCompare(b, "en", { sensitivity: "base" })); +} + +function collectLanguages(languagesByPlatform: Record): T[] { + const languages = Object.entries(languagesByPlatform).reduce((result, [_, languages]) => { + result.push(...languages); + + return result; + }, []); + + return [...new Set(languages)]; +} + +export { sortLanguages, collectLanguages }; From c1d39d59a67bf4d4bab617395b551002e16cc408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20Oszcz=C4=99da?= Date: Sun, 24 Nov 2024 01:34:34 +0000 Subject: [PATCH 12/17] feat: Extend `LearningLanguage`. --- source/constants/languages/learning.ts | 372 +++++++++++++++++- .../constants/languages/learning.spec.ts | 15 +- 2 files changed, 360 insertions(+), 27 deletions(-) diff --git a/source/constants/languages/learning.ts b/source/constants/languages/learning.ts index 2b410d389..d32973f8f 100644 --- a/source/constants/languages/learning.ts +++ b/source/constants/languages/learning.ts @@ -1,27 +1,316 @@ import type { WithBaseLanguage } from "logos:constants/languages"; import { - type Locale, + type DetectionLanguage, + languages as detectionLanguages, + isDetectionLanguage, +} from "logos:constants/languages/detection"; +import { + type FeatureLanguage, + languages as featureLanguages, + isFeatureLanguage, +} from "logos:constants/languages/feature"; +import { collectLanguages } from "logos:constants/languages/languages"; +import { type LocalisationLanguage, - getLocalisationLocaleByLanguage, isLocalisationLanguage, - isLocalisationLocale, languages as localisationLanguages, } from "logos:constants/languages/localisation"; +import { + type TranslationLanguage, + isTranslationLanguage, + languages as translationLanguages, +} from "logos:constants/languages/translation"; -const languages = localisationLanguages; +type LearningLanguage = DetectionLanguage | FeatureLanguage | LocalisationLanguage | TranslationLanguage; -type LearningLanguage = LocalisationLanguage; +const languages = new Set([ + ...collectLanguages(detectionLanguages), + ...featureLanguages, + ...collectLanguages(localisationLanguages), + ...collectLanguages(translationLanguages), +] satisfies LearningLanguage[]); -function isLearningLanguage(language: string): language is LearningLanguage { - return isLocalisationLanguage(language); -} +const languageToLocale = Object.freeze({ + Abkhazian: "abk", + Afar: "aar", + Afrikaans: "afr", + Akan: "aka", + Albanian: "sqi", + Amharic: "amh", + Arabic: "ara", + "Armenian/Eastern": "hye", + Assamese: "asm", + Aymara: "aym", + Azerbaijani: "aze", + Bashkir: "bak", + Basque: "eus", + Belarusian: "bel", + Bengali: "ben", + Bihari: "bih", + Bislama: "bis", + Breton: "bre", + Bulgarian: "bul", + Burmese: "mya", + Catalan: "cat", + Cherokee: "chr", + Chewa: "nya", + "Chinese/Simplified": "zho", + "Chinese/Traditional": "zho", + Corsican: "cos", + "Creole/Haitian": "hat", + "Creole/Mauritian": "mfe", + "Creole/SierraLeone": "kri", + "CzechoSlovak/Czech": "ces", + "CzechoSlovak/Slovak": "slk", + Danish: "dan", + Dholuo: "luo", + Dutch: "nld", + Dzongkha: "dzo", + English: "eng", + Esperanto: "epo", + Estonian: "est", + Ewe: "ewe", + Faroese: "fao", + Fijian: "fij", + "Filipino/Cebuano": "ceb", + "Filipino/Kapampangan": "pam", + "Filipino/Tagalog": "tgl", + "Filipino/Waray": "war", + Finnish: "fin", + French: "fra", + Frisian: "fry", + Ga: "gaa", + Galician: "glg", + Ganda: "lug", + Georgian: "kat", + German: "deu", + Greek: "ell", + Greenlandic: "kal", + Guarani: "grn", + Gujarati: "guj", + Hausa: "hau", + Hawaiian: "haw", + Hebrew: "heb", + Hindi: "hin", + Hmong: "hmn", + Hungarian: "hun", + Icelandic: "isl", + Igbo: "ibo", + Indonesian: "ind", + Interlingua: "ina", + Interlingue: "ile", + Inuktitut: "iku", + Inupiak: "ipk", + Irish: "gle", + Italian: "ita", + Japanese: "jpn", + Javanese: "jav", + Kannada: "kan", + Kashmiri: "kas", + Kazakh: "kaz", + Khasi: "kha", + Khmer: "khm", + Korean: "kor", + Kurdish: "kur", + Kyrgyz: "kir", + Lao: "lao", + Latin: "lat", + Latvian: "lav", + Limbu: "lif", + Lingala: "lin", + Lithuanian: "lit", + Lozi: "loz", + LubaLulua: "lua", + Luxembourgish: "ltz", + Macedonian: "mkd", + Malagasy: "mlg", + Malay: "msa", + Malayalam: "mal", + Maldivian: "div", + Maltese: "mlt", + Manx: "glv", + Maori: "mri", + Marathi: "mar", + Mongolian: "mon", + Nauru: "nau", + Nepali: "nep", + Newar: "new", + "Norwegian/Bokmal": "nob", + "Norwegian/Nynorsk": "nno", + Occitan: "oci", + Odia: "ory", + Oromo: "orm", + Ossetian: "oss", + Pashto: "pus", + Pedi: "nso", + Persian: "fas", + Polish: "pol", + "Portuguese/European": "por", + Punjabi: "pan", + Quechua: "que", + Rajasthani: "raj", + RhaetoRomance: "roh", + Romanian: "ron", + Russian: "rus", + "RwandaRundi/Kinyarwanda": "kin", + "RwandaRundi/Kirundi": "run", + Samoan: "smo", + Sango: "sag", + Sanskrit: "san", + Scots: "sco", + ScottishGaelic: "gla", + "SerboCroatian/Bosnian": "bos", + "SerboCroatian/Croatian": "hrv", + "SerboCroatian/Montenegrin": "cnr", + "SerboCroatian/Serbian": "srp", + Seselwa: "crs", + Shona: "sna", + Sindhi: "snd", + Sinhala: "sin", + Slovenian: "slv", + Somali: "som", + "Sotho/Southern": "sot", + Spanish: "spa", + Sundanese: "sun", + Swahili: "swa", + Swazi: "ssw", + Swedish: "swe", + Syriac: "syr", + Tajik: "tgk", + Tamil: "tam", + Tatar: "tat", + Telugu: "tel", + Thai: "tha", + Tibetan: "bod", + Tigrinya: "tir", + Tonga: "ton", + Tsonga: "tso", + Tswana: "tsn", + Tumbuka: "tum", + Turkish: "tur", + Turkmen: "tuk", + Twi: "twi", + Ukrainian: "ukr", + Urdu: "urd", + Uyghur: "uig", + Uzbek: "uzb", + Venda: "ven", + Vietnamese: "vie", + Volapuk: "vol", + Welsh: "cym", + Wolof: "wol", + Xhosa: "xho", + Yiddish: "yid", + Yoruba: "yor", + Zhuang: "zha", + Zulu: "zul", + Berber: "ber", + Klingon: "tlh", + "Arabic/Egyptian": "arz", + Aragonese: "arg", + Armenian: "hye", + Asturian: "ast", + Avar: "ava", + "Azerbaijani/Southern": "azb", + Bavarian: "bar", + "Bikol/Central": "bcl", + Bishnupriya: "bpy", + "Buriat/Russian": "bua", + Chavacano: "cbk", + Chechen: "che", + Chinese: "zho", + "Chinese/Wu": "wuu", + "Chinese/Yue": "yue", + Chuvash: "chv", + Cornish: "cor", + Dotyali: "dty", + "Emiliano-Romagnolo": "rgn", + Erzya: "myv", + "Filipino/Ilocano": "ilo", + "Frisian/Northern": "frr", + "Frisian/Western": "fry", + "German/Low": "nds", + "German/Swiss": "gsw", + "Hindi/Fijian": "fij", + Ido: "ido", + "Italian/Neapolitan": "nap", + "Italian/Venetian": "vec", + Kalmyk: "xal", + "Karachay-Balkar": "krc", + Komi: "kom", + "Konkani/Goan": "gom", + "Kurdish/Northern": "kmr", + Lezgian: "lez", + Limburgish: "lim", + Lojban: "jbo", + Lombard: "lmo", + "Luri/Northern": "lrc", + Maithili: "mai", + "Mari/Eastern": "mhr", + "Mari/Western": "mrj", + Mazanderani: "mzn", + Minangkabau: "min", + Mingrelian: "xmf", + Mirandese: "mwl", + Nahuatl: "nhe", + Norwegian: "nor", + Pfaelzisch: "pfl", + Piedmontese: "pms", + Portuguese: "por", + "Punjabi/Western": "pan", + Romansh: "roh", + Rusyn: "rue", + Sardinian: "srd", + SerboCroatian: "hbs", + Sicilian: "scn", + "Sorbian/Lower": "dsb", + "Sorbian/Upper": "hsb", + Tuvinian: "tyv", + Uighur: "uig", + Veps: "vep", + Vlaams: "vls", + Walloon: "wln", + Yakut: "sah", + Zaza: "zza", + "English/American": "eng", + "English/British": "eng", + "Armenian/Western": "hyw", + Silesian: "szl", + "Portuguese/Brazilian": "por", + Bambara: "bam", + Bhojpuri: "bho", + Dogri: "dgo", + Filipino: "fil", + Konkani: "knn", + "Kurdish/Sorani": "ckb", + Meitei: "mni", + Mizo: "lus", + "Sotho/Northern": "nso", + "Arabic/Emirati": "ara", + "Arabic/SaudiArabian": "ara", + "Catalan/Valencian": "cat", + "English/Australian": "eng", + "French/Canadian": "fra", + "French/French": "fra", + "Portuguese/Portuguese": "por", + "Spanish/Mexican": "spa", + "Spanish/Spanish": "spa", + "Spanish/American": "spa", +} as const satisfies Record); + +type LearningLocale = (typeof languageToLocale)[keyof typeof languageToLocale]; -function isLearningLocale(locale: string): locale is Locale { - return isLocalisationLocale(locale); +function isLearningLanguage(language: string): language is LearningLanguage { + return ( + isDetectionLanguage(language) || + isFeatureLanguage(language) || + isLocalisationLanguage(language) || + isTranslationLanguage(language) + ); } -function getLocaleByLearningLanguage(language: LearningLanguage): Locale { - return getLocalisationLocaleByLanguage(language); +function getLearningLocaleByLanguage(language: LearningLanguage): LearningLocale { + return languageToLocale[language]; } const wiktionaryLanguageNames = Object.freeze({ @@ -30,11 +319,66 @@ const wiktionaryLanguageNames = Object.freeze({ "Norwegian/Bokmal": "Norwegian Bokmål", "Armenian/Western": "Armenian", "Armenian/Eastern": "Armenian", + "Chinese/Simplified": "Chinese", + "Chinese/Traditional": "Chinese", + "Creole/Haitian": "Haitian Creole", + "Creole/Mauritian": "Mauritian Creole", + "Creole/SierraLeone": "Krio", + "CzechoSlovak/Czech": "Czech", + "CzechoSlovak/Slovak": "Slovak", + "Filipino/Cebuano": "Cebuano", + "Filipino/Kapampangan": "Kapampangan", + "Filipino/Tagalog": "Tagalog", + "Filipino/Waray": "Waray-Waray", + "Norwegian/Nynorsk": "Norwegian Nynorsk", + "Portuguese/European": "Portuguese", + "RwandaRundi/Kinyarwanda": "Rwanda-Rundi", + "RwandaRundi/Kirundi": "Rwanda-Rundi", + "SerboCroatian/Bosnian": "Serbo-Croatian", + "SerboCroatian/Croatian": "Serbo-Croatian", + "SerboCroatian/Montenegrin": "Serbo-Croatian", + "SerboCroatian/Serbian": "Serbo-Croatian", + "Sotho/Southern": "Sotho", + "Arabic/Egyptian": "Arabic", + "Azerbaijani/Southern": "Azerbaijani", + "Bikol/Central": "Bikol Central", + "Buriat/Russian": "Buryat", + "Chinese/Wu": "Wu", + "Chinese/Yue": "Cantonese", + "Filipino/Ilocano": "Ilocano", + "Frisian/Northern": "North Frisian", + "Frisian/Western": "West Frisian", + "German/Low": "Low German", + "German/Swiss": "Alemannic German", + "Hindi/Fijian": "Fijian", + "Italian/Neapolitan": "Neapolitan", + "Italian/Venetian": "Venetan", + "Konkani/Goan": "Konkani", + "Kurdish/Northern": "Northern Kurdish", + "Luri/Northern": "Northern Luri", + "Mari/Eastern": "Eastern Mari", + "Mari/Western": "Western Mari", + "Punjabi/Western": "Lahnda", + "Sorbian/Lower": "Lower Sorbian", + "Sorbian/Upper": "Upper Sorbian", + "Portuguese/Brazilian": "Portuguese", + "Kurdish/Sorani": "Central Kurdish", + "Sotho/Northern": "Northern Sotho", + "Arabic/Emirati": "Arabic", + "Arabic/SaudiArabian": "Arabic", + "Catalan/Valencian": "Catalan", + "English/Australian": "English", + "French/Canadian": "French", + "French/French": "French", + "Portuguese/Portuguese": "Portuguese", + "Spanish/Mexican": "Spanish", + "Spanish/Spanish": "Spanish", + "Spanish/American": "Spanish", } satisfies Record, string>); function getWiktionaryLanguageName(language: LearningLanguage): string { return (wiktionaryLanguageNames as Record)[language] ?? language; } -export { isLearningLanguage, isLearningLocale, getLocaleByLearningLanguage, getWiktionaryLanguageName, languages }; -export type { LearningLanguage }; +export { isLearningLanguage, getWiktionaryLanguageName, languages, getLearningLocaleByLanguage }; +export type { LearningLanguage, LearningLocale }; diff --git a/test/source/constants/languages/learning.spec.ts b/test/source/constants/languages/learning.spec.ts index 0179dcd67..1942c75c4 100644 --- a/test/source/constants/languages/learning.spec.ts +++ b/test/source/constants/languages/learning.spec.ts @@ -1,9 +1,9 @@ import { describe, it } from "bun:test"; -import { getWiktionaryLanguageName, isLearningLanguage, isLearningLocale } from "logos:constants/languages/learning"; +import { getWiktionaryLanguageName, isLearningLanguage } from "logos:constants/languages/learning"; import { getLogosLocaleByLanguage } from "logos:constants/languages/localisation"; import { expect } from "chai"; -describe("isLanguage()", () => { +describe("isLearningLanguage()", () => { it("returns true if the passed language is a supported learning language.", () => { expect(isLearningLanguage("Polish")).to.be.true; expect(isLearningLanguage("Russian")).to.be.true; @@ -14,17 +14,6 @@ describe("isLanguage()", () => { }); }); -describe("isLocale()", () => { - it("returns true if the passed locale is a supported learning locale.", () => { - expect(isLearningLocale("pol")).to.be.true; - expect(isLearningLocale("rus")).to.be.true; - }); - - it("returns false if the passed locale is not a supported learning locale.", () => { - expect(isLearningLocale("this-is-not-a-supported-learning-locale")).to.be.false; - }); -}); - describe("getLocaleByLanguage()", () => { it("returns the language corresponding to the passed learning locale.", () => { expect(getLogosLocaleByLanguage("English/British")).to.equal("eng-GB"); From 9f5b7d19866708ff0e83b6ae1bd880bcd0b0a722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20Oszcz=C4=99da?= Date: Sun, 24 Nov 2024 01:35:52 +0000 Subject: [PATCH 13/17] feat: Add more languages to dictionary map. --- source/constants/dictionaries.ts | 246 ++++++++++++++++++ source/constants/languages.ts | 8 +- .../dictionaries/adapters/wiktionary.ts | 1 + source/library/collectors.ts | 6 +- source/library/commands/handlers/context.ts | 6 +- source/library/commands/handlers/game.ts | 4 +- source/library/commands/handlers/word.ts | 2 +- source/library/stores/volatile.ts | 11 +- 8 files changed, 266 insertions(+), 18 deletions(-) diff --git a/source/constants/dictionaries.ts b/source/constants/dictionaries.ts index 495488b40..72993c1ba 100644 --- a/source/constants/dictionaries.ts +++ b/source/constants/dictionaries.ts @@ -40,6 +40,252 @@ const dictionariesByLanguage = Object.freeze({ Spanish: ["wiktionary"], Swedish: ["wiktionary"], Turkish: ["wiktionary"], + Abkhazian: ["wiktionary"], + Afar: ["wiktionary"], + Afrikaans: ["wiktionary"], + Akan: ["wiktionary"], + Albanian: ["wiktionary"], + Amharic: ["wiktionary"], + Arabic: ["wiktionary"], + Assamese: ["wiktionary"], + Aymara: ["wiktionary"], + Azerbaijani: ["wiktionary"], + Bashkir: ["wiktionary"], + Basque: ["wiktionary"], + Belarusian: ["wiktionary"], + Bengali: ["wiktionary"], + Bihari: ["wiktionary"], + Bislama: ["wiktionary"], + Breton: ["wiktionary"], + Bulgarian: ["wiktionary"], + Burmese: ["wiktionary"], + Catalan: ["wiktionary"], + Cherokee: ["wiktionary"], + Chewa: ["wiktionary"], + "Chinese/Simplified": ["wiktionary"], + "Chinese/Traditional": ["wiktionary"], + Corsican: ["wiktionary"], + "Creole/Haitian": ["wiktionary"], + "Creole/Mauritian": ["wiktionary"], + "Creole/SierraLeone": ["wiktionary"], + "CzechoSlovak/Czech": ["wiktionary"], + "CzechoSlovak/Slovak": ["wiktionary"], + Dholuo: ["wiktionary"], + Dzongkha: ["wiktionary"], + English: ["wiktionary"], + Esperanto: ["wiktionary"], + Estonian: ["wiktionary"], + Ewe: ["wiktionary"], + Faroese: ["wiktionary"], + Fijian: ["wiktionary"], + "Filipino/Cebuano": ["wiktionary"], + "Filipino/Kapampangan": ["wiktionary"], + "Filipino/Tagalog": ["wiktionary"], + "Filipino/Waray": ["wiktionary"], + Frisian: ["wiktionary"], + Ga: ["wiktionary"], + Galician: ["wiktionary"], + Ganda: ["wiktionary"], + Georgian: ["wiktionary"], + Greenlandic: ["wiktionary"], + Guarani: ["wiktionary"], + Gujarati: ["wiktionary"], + Hausa: ["wiktionary"], + Hawaiian: ["wiktionary"], + Hebrew: ["wiktionary"], + Hindi: ["wiktionary"], + Hmong: ["wiktionary"], + Icelandic: ["wiktionary"], + Igbo: ["wiktionary"], + Indonesian: ["wiktionary"], + Interlingua: ["wiktionary"], + Interlingue: ["wiktionary"], + Inuktitut: ["wiktionary"], + Inupiak: ["wiktionary"], + Irish: ["wiktionary"], + Italian: ["wiktionary"], + Japanese: ["wiktionary"], + Javanese: ["wiktionary"], + Kannada: ["wiktionary"], + Kashmiri: ["wiktionary"], + Kazakh: ["wiktionary"], + Khasi: ["wiktionary"], + Khmer: ["wiktionary"], + Korean: ["wiktionary"], + Kurdish: ["wiktionary"], + Kyrgyz: ["wiktionary"], + Lao: ["wiktionary"], + Latin: ["wiktionary"], + Latvian: ["wiktionary"], + Limbu: ["wiktionary"], + Lingala: ["wiktionary"], + Lithuanian: ["wiktionary"], + Lozi: ["wiktionary"], + LubaLulua: ["wiktionary"], + Luxembourgish: ["wiktionary"], + Macedonian: ["wiktionary"], + Malagasy: ["wiktionary"], + Malay: ["wiktionary"], + Malayalam: ["wiktionary"], + Maldivian: ["wiktionary"], + Maltese: ["wiktionary"], + Manx: ["wiktionary"], + Maori: ["wiktionary"], + Marathi: ["wiktionary"], + Mongolian: ["wiktionary"], + Nauru: ["wiktionary"], + Nepali: ["wiktionary"], + Newar: ["wiktionary"], + "Norwegian/Nynorsk": ["wiktionary"], + Occitan: ["wiktionary"], + Odia: ["wiktionary"], + Oromo: ["wiktionary"], + Ossetian: ["wiktionary"], + Pashto: ["wiktionary"], + Pedi: ["wiktionary"], + Persian: ["wiktionary"], + "Portuguese/European": ["wiktionary"], + Punjabi: ["wiktionary"], + Quechua: ["wiktionary"], + Rajasthani: ["wiktionary"], + RhaetoRomance: ["wiktionary"], + "RwandaRundi/Kinyarwanda": ["wiktionary"], + "RwandaRundi/Kirundi": ["wiktionary"], + Samoan: ["wiktionary"], + Sango: ["wiktionary"], + Sanskrit: ["wiktionary"], + Scots: ["wiktionary"], + ScottishGaelic: ["wiktionary"], + "SerboCroatian/Bosnian": ["wiktionary"], + "SerboCroatian/Croatian": ["wiktionary"], + "SerboCroatian/Montenegrin": ["wiktionary"], + "SerboCroatian/Serbian": ["wiktionary"], + Seselwa: ["wiktionary"], + Shona: ["wiktionary"], + Sindhi: ["wiktionary"], + Sinhala: ["wiktionary"], + Slovenian: ["wiktionary"], + Somali: ["wiktionary"], + "Sotho/Southern": ["wiktionary"], + Sundanese: ["wiktionary"], + Swahili: ["wiktionary"], + Swazi: ["wiktionary"], + Syriac: ["wiktionary"], + Tajik: ["wiktionary"], + Tamil: ["wiktionary"], + Tatar: ["wiktionary"], + Telugu: ["wiktionary"], + Thai: ["wiktionary"], + Tibetan: ["wiktionary"], + Tigrinya: ["wiktionary"], + Tonga: ["wiktionary"], + Tsonga: ["wiktionary"], + Tswana: ["wiktionary"], + Tumbuka: ["wiktionary"], + Turkmen: ["wiktionary"], + Twi: ["wiktionary"], + Ukrainian: ["wiktionary"], + Urdu: ["wiktionary"], + Uyghur: ["wiktionary"], + Uzbek: ["wiktionary"], + Venda: ["wiktionary"], + Vietnamese: ["wiktionary"], + Volapuk: ["wiktionary"], + Welsh: ["wiktionary"], + Wolof: ["wiktionary"], + Xhosa: ["wiktionary"], + Yiddish: ["wiktionary"], + Yoruba: ["wiktionary"], + Zhuang: ["wiktionary"], + Zulu: ["wiktionary"], + Berber: ["wiktionary"], + Klingon: ["wiktionary"], + "Arabic/Egyptian": ["wiktionary"], + Aragonese: ["wiktionary"], + Armenian: ["wiktionary"], + Asturian: ["wiktionary"], + Avar: ["wiktionary"], + "Azerbaijani/Southern": ["wiktionary"], + Bavarian: ["wiktionary"], + "Bikol/Central": ["wiktionary"], + Bishnupriya: ["wiktionary"], + "Buriat/Russian": ["wiktionary"], + Chavacano: ["wiktionary"], + Chechen: ["wiktionary"], + Chinese: ["wiktionary"], + "Chinese/Wu": ["wiktionary"], + "Chinese/Yue": ["wiktionary"], + Chuvash: ["wiktionary"], + Cornish: ["wiktionary"], + Dotyali: ["wiktionary"], + "Emiliano-Romagnolo": ["wiktionary"], + Erzya: ["wiktionary"], + "Filipino/Ilocano": ["wiktionary"], + "Frisian/Northern": ["wiktionary"], + "Frisian/Western": ["wiktionary"], + "German/Low": ["wiktionary"], + "German/Swiss": ["wiktionary"], + "Hindi/Fijian": ["wiktionary"], + Ido: ["wiktionary"], + "Italian/Neapolitan": ["wiktionary"], + "Italian/Venetian": ["wiktionary"], + Kalmyk: ["wiktionary"], + "Karachay-Balkar": ["wiktionary"], + Komi: ["wiktionary"], + "Konkani/Goan": ["wiktionary"], + "Kurdish/Northern": ["wiktionary"], + Lezgian: ["wiktionary"], + Limburgish: ["wiktionary"], + Lojban: ["wiktionary"], + Lombard: ["wiktionary"], + "Luri/Northern": ["wiktionary"], + Maithili: ["wiktionary"], + "Mari/Eastern": ["wiktionary"], + "Mari/Western": ["wiktionary"], + Mazanderani: ["wiktionary"], + Minangkabau: ["wiktionary"], + Mingrelian: ["wiktionary"], + Mirandese: ["wiktionary"], + Nahuatl: ["wiktionary"], + Norwegian: ["wiktionary"], + Pfaelzisch: ["wiktionary"], + Piedmontese: ["wiktionary"], + Portuguese: ["wiktionary"], + "Punjabi/Western": ["wiktionary"], + Romansh: ["wiktionary"], + Rusyn: ["wiktionary"], + Sardinian: ["wiktionary"], + SerboCroatian: ["wiktionary"], + Sicilian: ["wiktionary"], + "Sorbian/Lower": ["wiktionary"], + "Sorbian/Upper": ["wiktionary"], + Tuvinian: ["wiktionary"], + Uighur: ["wiktionary"], + Veps: ["wiktionary"], + Vlaams: ["wiktionary"], + Walloon: ["wiktionary"], + Yakut: ["wiktionary"], + Zaza: ["wiktionary"], + "Portuguese/Brazilian": ["wiktionary"], + Bambara: ["wiktionary"], + Bhojpuri: ["wiktionary"], + Dogri: ["wiktionary"], + Filipino: ["wiktionary"], + Konkani: ["wiktionary"], + "Kurdish/Sorani": ["wiktionary"], + Meitei: ["wiktionary"], + Mizo: ["wiktionary"], + "Sotho/Northern": ["wiktionary"], + "Arabic/Emirati": ["wiktionary"], + "Arabic/SaudiArabian": ["wiktionary"], + "Catalan/Valencian": ["wiktionary"], + "English/Australian": ["wiktionary"], + "French/Canadian": ["wiktionary"], + "French/French": ["wiktionary"], + "Portuguese/Portuguese": ["wiktionary"], + "Spanish/Mexican": ["wiktionary"], + "Spanish/Spanish": ["wiktionary"], + "Spanish/American": ["wiktionary"], } satisfies Record as Record); export default Object.freeze({ languages: dictionariesByLanguage }); diff --git a/source/constants/languages.ts b/source/constants/languages.ts index b86c4e707..47bc78642 100644 --- a/source/constants/languages.ts +++ b/source/constants/languages.ts @@ -1,6 +1,7 @@ -import { languages as detectionLanguages, type DetectionLanguage } from "logos:constants/languages/detection"; -import { languages as featureLanguages, type FeatureLanguage } from "logos:constants/languages/feature"; -import { languages as learningLanguages, type LearningLanguage } from "logos:constants/languages/learning"; +import { type DetectionLanguage, languages as detectionLanguages } from "logos:constants/languages/detection"; +import { type FeatureLanguage, languages as featureLanguages } from "logos:constants/languages/feature"; +import { collectLanguages, sortLanguages } from "logos:constants/languages/languages"; +import { type LearningLanguage, languages as learningLanguages } from "logos:constants/languages/learning"; import { type DiscordLocale, type LocalisationLanguage, @@ -12,7 +13,6 @@ import { isTranslationLanguage, languages as translationLanguages, } from "logos:constants/languages/translation"; -import { collectLanguages, sortLanguages } from "logos:constants/languages/languages"; const languages = Object.freeze({ languages: { diff --git a/source/library/adapters/dictionaries/adapters/wiktionary.ts b/source/library/adapters/dictionaries/adapters/wiktionary.ts index 421a2665c..a5a975a5f 100644 --- a/source/library/adapters/dictionaries/adapters/wiktionary.ts +++ b/source/library/adapters/dictionaries/adapters/wiktionary.ts @@ -43,6 +43,7 @@ class WiktionaryAdapter extends DictionaryAdapter { results = await Wiktionary.get(lemma, { lemmaLanguage: targetLanguageWiktionary, userAgent: constants.USER_AGENT, + followRedirect: true, }); } catch (error) { this.client.log.error(error, `The request for lemma "${lemma}" failed.`); diff --git a/source/library/collectors.ts b/source/library/collectors.ts index 16dbc883d..864f07d1c 100644 --- a/source/library/collectors.ts +++ b/source/library/collectors.ts @@ -1,5 +1,5 @@ import { isAutocomplete, isSubcommand, isSubcommandGroup } from "logos:constants/interactions"; -import { type LearningLanguage, getLocaleByLearningLanguage } from "logos:constants/languages/learning"; +import { type LearningLanguage, getLearningLocaleByLanguage } from "logos:constants/languages/learning"; import { getDiscordLanguageByLocale, getLocalisationLocaleByLanguage } from "logos:constants/languages/localisation"; import type { PromiseOr } from "logos:core/utilities"; import type { Client } from "logos/client"; @@ -296,12 +296,12 @@ class InteractionCollector< const targetLanguage = guildDocument.languages.target; const learningLanguage = this.#determineLearningLanguage(guildDocument, member) ?? targetLanguage; - const learningLocale = getLocaleByLearningLanguage(learningLanguage); + const learningLocale = getLearningLocaleByLanguage(learningLanguage); const guildLanguage = guildDocument.isTargetLanguageOnlyChannel(interaction.channelId!.toString()) ? targetLanguage : guildDocument.languages.localisation; - const guildLocale = getLocalisationLocaleByLanguage(guildLanguage); + const guildLocale = getLearningLocaleByLanguage(guildLanguage); const featureLanguage = guildDocument.languages.feature; if (!isAutocomplete(interaction)) { diff --git a/source/library/commands/handlers/context.ts b/source/library/commands/handlers/context.ts index 19a42a295..3b9cb4302 100644 --- a/source/library/commands/handlers/context.ts +++ b/source/library/commands/handlers/context.ts @@ -1,4 +1,4 @@ -import { getLocaleByLearningLanguage } from "logos:constants/languages/learning"; +import { getLearningLocaleByLanguage } from "logos:constants/languages/learning"; import { isLocalisationLanguage } from "logos:constants/languages/localisation"; import { shuffle } from "ioredis/built/utils"; import type { Client } from "logos/client"; @@ -29,7 +29,7 @@ async function handleFindInContext( await client.postponeReply(interaction, { visible: interaction.parameters.show }); const learningLanguage = interaction.parameters.language ?? interaction.learningLanguage; - const learningLocale = getLocaleByLearningLanguage(learningLanguage); + const learningLocale = getLearningLocaleByLanguage(learningLanguage); const segmenter = new Intl.Segmenter(learningLocale, { granularity: "word" }); const lemmas = Array.from(segmenter.segment(interaction.parameters.phrase)) @@ -37,7 +37,7 @@ async function handleFindInContext( .map((data) => data.segment); const lemmaUses = await client.volatile?.searchForLemmaUses({ lemmas, - learningLocale: learningLocale, + learningLocale, caseSensitive: interaction.parameters["case-sensitive"], }); if (lemmaUses === undefined || lemmaUses.sentencePairs.length === 0) { diff --git a/source/library/commands/handlers/game.ts b/source/library/commands/handlers/game.ts index f2d997502..77fbd8300 100644 --- a/source/library/commands/handlers/game.ts +++ b/source/library/commands/handlers/game.ts @@ -1,5 +1,5 @@ import { capitalise } from "logos:constants/formatting"; -import type { Locale } from "logos:constants/languages/localisation"; +import type { LearningLocale } from "logos:constants/languages/learning"; import * as levenshtein from "fastest-levenshtein"; import type { Client } from "logos/client"; import { InteractionCollector } from "logos/collectors"; @@ -316,7 +316,7 @@ interface SentenceSelection { async function getSentenceSelection( client: Client, - { learningLocale }: { learningLocale: Locale }, + { learningLocale }: { learningLocale: LearningLocale }, ): Promise { const sentencePairs = await client.volatile!.getRandomSentencePairs({ learningLocale, diff --git a/source/library/commands/handlers/word.ts b/source/library/commands/handlers/word.ts index 7014c45a2..d5fd2d6b1 100644 --- a/source/library/commands/handlers/word.ts +++ b/source/library/commands/handlers/word.ts @@ -11,7 +11,7 @@ async function handleFindWordAutocomplete( client: Client, interaction: Logos.Interaction, ): Promise { - await handleAutocompleteLanguage(client, interaction, { type: "localisation" }); + await handleAutocompleteLanguage(client, interaction, { type: "learning" }); } /** Allows the user to look up a word and get information about it. */ diff --git a/source/library/stores/volatile.ts b/source/library/stores/volatile.ts index 14151c1df..d744048e5 100644 --- a/source/library/stores/volatile.ts +++ b/source/library/stores/volatile.ts @@ -1,3 +1,4 @@ +import type { LearningLocale } from "logos:constants/languages/learning"; import type { Locale } from "logos:constants/languages/localisation"; import Redis from "ioredis"; import type { Client } from "logos/client"; @@ -92,7 +93,7 @@ class VolatileStore { async getSentencePairs({ sentenceIds, learningLocale, - }: { sentenceIds: string[]; learningLocale: Locale }): Promise { + }: { sentenceIds: string[]; learningLocale: LearningLocale }): Promise { const encodedPairs: SentencePairEncoded[] = []; for (const sentenceId of sentenceIds) { const pairEncoded = await this.redis.get( @@ -116,7 +117,7 @@ class VolatileStore { async getRandomSentencePairs({ learningLocale, count, - }: { learningLocale: Locale; count: number }): Promise { + }: { learningLocale: LearningLocale; count: number }): Promise { const pipeline = this.redis.pipeline(); for (const _ of new Array(count).keys()) { pipeline.srandmember(constants.keys.redis.sentencePairIndex({ locale: learningLocale })); @@ -143,7 +144,7 @@ class VolatileStore { lemmas, learningLocale, caseSensitive = false, - }: { lemmas: string[]; learningLocale: Locale; caseSensitive?: boolean }): Promise { + }: { lemmas: string[]; learningLocale: LearningLocale; caseSensitive?: boolean }): Promise { if (caseSensitive) { return this.#searchForLemmaUsesCaseSensitive({ lemmas, learningLocale }); } @@ -154,7 +155,7 @@ class VolatileStore { async #searchForLemmaUsesCaseSensitive({ lemmas, learningLocale, - }: { lemmas: string[]; learningLocale: Locale }): Promise { + }: { lemmas: string[]; learningLocale: LearningLocale }): Promise { const keys = lemmas.map((lemma) => constants.keys.redis.lemmaUseIndex({ locale: learningLocale, lemma })); let sentenceIds: string[]; @@ -172,7 +173,7 @@ class VolatileStore { async #searchForLemmaUsesCaseInsensitive({ lemmas, learningLocale, - }: { lemmas: string[]; learningLocale: Locale }): Promise { + }: { lemmas: string[]; learningLocale: LearningLocale }): Promise { const lemmaFormKeys = lemmas.map((lemma) => constants.keys.redis.lemmaFormIndex({ locale: learningLocale, lemma }), ); From 91c582d111cb9fce865a87dd0f7ba53af3749c38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20Oszcz=C4=99da?= Date: Sun, 24 Nov 2024 01:42:21 +0000 Subject: [PATCH 14/17] fix: Encode URI component. --- source/constants/endpoints.ts | 7 ++++--- source/constants/links.ts | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/source/constants/endpoints.ts b/source/constants/endpoints.ts index 6d50b50fa..74373c15e 100644 --- a/source/constants/endpoints.ts +++ b/source/constants/endpoints.ts @@ -11,14 +11,15 @@ export default Object.freeze({ translate: "https://lingvanex-translate.p.rapidapi.com/translate", }, wordnik: { - relatedWords: (lemma: string) => `https://api.wordnik.com/v4/word.json/${lemma}/relatedWords`, + relatedWords: (lemma: string) => + `https://api.wordnik.com/v4/word.json/${encodeURIComponent(lemma)}/relatedWords`, }, wordsApi: { host: "wordsapiv1.p.rapidapi.com", - word: (lemma: string) => `https://wordsapiv1.p.rapidapi.com/words/${lemma}`, + word: (lemma: string) => `https://wordsapiv1.p.rapidapi.com/words/${encodeURIComponent(lemma)}`, }, dicolink: { host: "dicolink.p.rapidapi.com", - definitions: (lemma: string) => `https://dicolink.p.rapidapi.com/mot/${lemma}/definitions`, + definitions: (lemma: string) => `https://dicolink.p.rapidapi.com/mot/${encodeURIComponent(lemma)}/definitions`, }, } as const); diff --git a/source/constants/links.ts b/source/constants/links.ts index b262b6264..3f59f93c0 100644 --- a/source/constants/links.ts +++ b/source/constants/links.ts @@ -3,7 +3,7 @@ export default Object.freeze({ dexonlineDefinition: (lemma: string) => `https://dexonline.ro/definitie/${encodeURIComponent(lemma)}`, wiktionaryDefinition: (lemma: string, language: string) => `https://en.wiktionary.org/wiki/${encodeURIComponent(lemma)}#${encodeURIComponent(language)}`, - wordnikDefinitionLink: (lemma: string) => `https://wordnik.com/words/${lemma}`, + wordnikDefinitionLink: (lemma: string) => `https://wordnik.com/words/${encodeURIComponent(lemma)}`, wordsAPIDefinition: () => "https://wordsapi.com", dicolinkDefinition: (lemma: string) => `https://dicolink.com/mots/${encodeURIComponent(lemma)}`, } as const satisfies Record string>); From efb89fc577e2d7ec872de572e27bae2fd295a4d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20Oszcz=C4=99da?= Date: Sun, 24 Nov 2024 01:55:35 +0000 Subject: [PATCH 15/17] feat: Upgrade version of `wiktionary-scraper`. --- bun.lockb | Bin 104730 -> 104708 bytes package.json | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/bun.lockb b/bun.lockb index e93718739946aeb69ad51aefc19b9b6349d0bbd3..113ee149a3e3035ff8ec2b74ee076da9287a5243 100755 GIT binary patch delta 9244 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 delta 9334 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 Date: Sun, 24 Nov 2024 10:31:42 +0000 Subject: [PATCH 16/17] chore: Add TODO. --- source/library/commands/handlers/context.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/source/library/commands/handlers/context.ts b/source/library/commands/handlers/context.ts index 3b9cb4302..151d4f487 100644 --- a/source/library/commands/handlers/context.ts +++ b/source/library/commands/handlers/context.ts @@ -9,6 +9,7 @@ async function handleFindInContextAutocomplete( client: Client, interaction: Logos.Interaction, ): Promise { + // TODO(vxern): `context` should support more languages. await handleAutocompleteLanguage(client, interaction, { type: "localisation" }); } From 9a6a2ec0ff872d5cf18b0dd73bcf6ff3707b74d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dorian=20Oszcz=C4=99da?= Date: Sun, 24 Nov 2024 10:43:17 +0000 Subject: [PATCH 17/17] feat: Update lockfile. --- bun.lockb | Bin 104708 -> 109676 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/bun.lockb b/bun.lockb index 113ee149a3e3035ff8ec2b74ee076da9287a5243..0259af7b176de2fbee97e0d17b92ade4ebe39c2e 100755 GIT binary patch delta 23297 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 delta 20038 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 T8wsy(