Skip to content

Commit

Permalink
refactor: Remove complex expressions from localisations, replacing th…
Browse files Browse the repository at this point in the history
…em with separate strings.
  • Loading branch information
vxern committed Jul 1, 2023
1 parent 48236c8 commit 30d2c7c
Show file tree
Hide file tree
Showing 12 changed files with 240 additions and 117 deletions.
64 changes: 43 additions & 21 deletions assets/localisations/commands/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,10 @@
"word.strings.fields.synonyms": "Synonyms",
"word.strings.fields.antonyms": "Antonyms",
"word.strings.fields.expressions": "Expressions",
"word.strings.definitionsOmitted": "Omitted {number | pluralise, one:\"definition\", two:\"definitions\"}. To display more results, enable the `{flag}` flag.",
"word.strings.definitionsOmitted": "Omitted {definitions}. To display more results, enable the `{flag}` flag.",
"word.strings.definitionsOmitted.definitions.one": "{one} definition",
"word.strings.definitionsOmitted.definitions.two": "{two} definitions",
"word.strings.definitionsOmitted.definitions.many": "{many} definitions",
"word.strings.page": "Page",
"word.strings.definitions": "Definitions",
"word.strings.nativeDefinitionsForWord": "Native definitions for '{word}'",
Expand Down Expand Up @@ -173,23 +176,50 @@
"purge.strings.indexing.title": "Indexing messages...",
"purge.strings.indexing.description": "The bot is indexing messages falling within the range you specified.",
"purge.strings.indexed.title": "Finished indexing",
"purge.strings.indexed.description.some": "The bot has indexed messages falling within the range you specified, and found **{number | pluralise, one:\"deletable message\", two:\"deletable messages\"}**.",
"purge.strings.indexed.description.some": "The bot has indexed messages falling within the range you specified, and found **{messages}**.",
"purge.strings.indexed.description.some.messages.one": "{one} deletable message",
"purge.strings.indexed.description.some.messages.two": "{two} deletable messages",
"purge.strings.indexed.description.some.messages.many": "{many} deletable messages",
"purge.strings.indexed.description.none": "The bot has indexed messages falling within the range you specified, and found no deletable messages.",
"purge.strings.indexed.description.tryDifferentQuery": "Try a different query.",
"purge.strings.indexed.description.tooMany": "The bot has indexed messages falling within the range you specified, and found **{number | pluralise, one:\"deletable message\", two:\"deletable messages\"}**, which is greater than the **maximum number of messages deletable in a given purge action ({maximum_deletable})**.",
"purge.strings.indexed.description.limited": "If you choose to continue, **{number | pluralise, one:\"message\", two:\"messages\"}** will be deleted instead.",
"purge.strings.indexed.description.tooMany": "The bot has indexed messages falling within the range you specified, and found **{messages}**, which is greater than the **maximum number of messages deletable in a given purge action ({maximum_deletable})**.",
"purge.strings.indexed.description.tooMany.messages.one": "{one} deletable message",
"purge.strings.indexed.description.tooMany.messages.two": "{two} deletable messages",
"purge.strings.indexed.description.tooMany.messages.many": "{many} deletable messages",
"purge.strings.indexed.description.limited": "If you choose to continue, **{messages}** will be deleted instead.",
"purge.strings.indexed.description.limited.messages.one": "{one} message",
"purge.strings.indexed.description.limited.messages.two": "{two} messages",
"purge.strings.indexed.description.limited.messages.many": "{many} messages",
"purge.strings.sureToPurge.title": "Sure to purge?",
"purge.strings.sureToPurge.description": "During purging, **{number | pluralise, one:\"message\", two:\"messages\"}** will be irrevocably deleted from {channel_mention}.",
"purge.strings.sureToPurge.description": "During purging, **{messages}** will be irrevocably deleted from {channel_mention}.",
"purge.strings.sureToPurge.description.messages.one": "{one} message",
"purge.strings.sureToPurge.description.messages.two": "{two} messages",
"purge.strings.sureToPurge.description.messages.many": "{many} messages",
"purge.strings.continue.title": "Continue?",
"purge.strings.continue.description": "During purging, **{number | pluralise, one:\"message\", two:\"messages\"}** will be irrevocably deleted from {channel_mention}, which is only a fraction of the {full_number | pluralise, one:\"message\", two:\"messages\"} found.",
"purge.strings.continue.description": "During purging, **{messages}** will be irrevocably deleted from {channel_mention}, which is only a fraction of the {all_messages} found.",
"purge.strings.continue.description.messages.one": "{one} message",
"purge.strings.continue.description.messages.two": "{two} messages",
"purge.strings.continue.description.messages.many": "{many} messages",
"purge.strings.continue.description.allMessages.one": "{one} message",
"purge.strings.continue.description.allMessages.two": "{two} messages",
"purge.strings.continue.description.allMessages.many": "{many} messages",
"purge.strings.purging.title": "Purging...",
"purge.strings.purging.description.purging": "{number | pluralise, one:\"message is being\", two:\"messages are being\"} purged from {channel_mention}.",
"purge.strings.purging.description.purging": "{messages} from {channel_mention}.",
"purge.strings.purging.description.purging.messages.one": "{one} message is being purged",
"purge.strings.purging.description.purging.messages.two": "{two} messages are being purged",
"purge.strings.purging.description.purging.messages.many": "{many} messages are being purged",
"purge.strings.purging.description.mayTakeTime": "This may take a while.",
"purge.strings.purging.description.onceComplete": "Once complete, you will be able to find a success message in the log channel.",
"purge.strings.purged.title": "Purged successfully",
"purge.strings.purged.description": "{number | pluralise, one:\"message has been\", two:\"messages have been\"} purged from {channel_mention}.",
"purge.strings.purged.description": "{messages} from {channel_mention}.",
"purge.strings.purged.description.messages.one": "{one} message has been purged",
"purge.strings.purged.description.messages.two": "{two} messages have been purged",
"purge.strings.purged.description.messages.many": "{many} messages have been purged",
"purge.strings.rangeTooBig.title": "Range too big",
"purge.strings.rangeTooBig.description.rangeTooBig": "While indexing the selected range of messages, the bot found more than **{number | pluralise, one:\"message\", two:\"messages\"}**, which is too big of a range.",
"purge.strings.rangeTooBig.description.rangeTooBig": "While indexing the selected range of messages, the bot found more than **{messages}**, which is too big of a range.",
"purge.strings.rangeTooBig.description.rangeTooBig.messages.one": "{one} message",
"purge.strings.rangeTooBig.description.rangeTooBig.messages.two": "{two} messages",
"purge.strings.rangeTooBig.description.rangeTooBig.messages.many": "{many} messages",
"purge.strings.rangeTooBig.description.trySmaller": "Try selecting a smaller range of messages to purge.",
"purge.strings.start": "Start message",
"purge.strings.end": "End message",
Expand All @@ -206,17 +236,6 @@
"report.strings.submitted.description": "Your report has been submitted. The report will be reviewed by the server staff, but you will not be notified directly about the outcome of a particular report.",
"report.strings.failed.title": "Failed to submit report",
"report.strings.failed.description": "Due to unknown reasons, submitting the report failed.",
"report.strings.invalidSpecifiers.title": "Users specified incorrectly",
"report.strings.invalidSpecifiers.description.invalidSpecifiers": "You have incorrectly specified which users to report.",
"report.strings.invalidSpecifiers.description.howToIdentify": "To identify a user, include their ID or tag. User identifiers must be separated using a comma.",
"report.strings.invalidSpecifiers.description.example": "Example of a valid expression:\n{example_expression}",
"report.strings.duplicateUser.title": "Specified user more than once",
"report.strings.duplicateUser.description.duplicateUser": "You have specified the same user more than once.",
"report.strings.duplicateUser.description.ensureNotDuplicate": "Before attempting to submit the report again, make sure each user is only mentioned once in the report.",
"report.strings.tooManyUsers.title": "Cannot report that many users",
"report.strings.tooManyUsers.description": "You have tried to report too many users at once. You can only report up to {number | pluralise, one:\"user\", two:\"users\"} at once.",
"report.strings.cannotReportSelf.title": "Reports cannot be for yourself",
"report.strings.cannotReportSelf.description": "You cannot submit a report against yourself.",
"report.strings.sureToCancel.title": "Sure to exit?",
"report.strings.sureToCancel.description": "Are you sure you want to stop submitting your report?",
"report.strings.tooMany.title": "Too many reports",
Expand Down Expand Up @@ -253,7 +272,10 @@
"warn.strings.failed.title": "Failed to warn user",
"warn.strings.failed.description": "Due to unknown reasons, warning the specified user failed.",
"warn.strings.warned.title": "User warned",
"warn.strings.warned.description": "User {user_mention} has been warned. They now have {number | pluralise, one:\"warning\", two:\"warnings\"}.",
"warn.strings.warned.description": "User {user_mention} has been warned. They now have {warnings}.",
"warn.strings.warned.description.warnings.one": "{number} warning",
"warn.strings.warned.description.warnings.two": "{number} warnings",
"warn.strings.warned.description.warnings.many": "{number} warnings",
"warn.strings.limitReached.title": "Warning limit reached",
"warn.strings.limitReached.description": "{user_mention} has reached the warning limit ({limit}).",
"warn.strings.limitSurpassed.title": "Warning limit surpassed",
Expand Down
104 changes: 62 additions & 42 deletions src/lib/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,30 +56,6 @@ type Event = keyof EventHandlers;

type WithLanguage<T> = T & { language: Language };

type Cache = Readonly<{
guilds: Map<bigint, WithLanguage<Guild>>;
users: Map<bigint, User>;
members: Map<bigint, Member>;
channels: Map<bigint, Channel>;
messages: {
latest: Map<bigint, Message>;
previous: Map<bigint, Message>;
};
}>;

function createCache(): Cache {
return {
guilds: new Map(),
users: new Map(),
members: new Map(),
channels: new Map(),
messages: {
latest: new Map(),
previous: new Map(),
},
};
}

type Client = Readonly<{
metadata: {
environment: {
Expand All @@ -95,7 +71,16 @@ type Client = Readonly<{
supportedTranslationLanguages: SupportedLanguage[];
};
log: Record<"debug" | keyof typeof FancyLog, (...args: unknown[]) => void>;
cache: Cache;
cache: {
guilds: Map<bigint, WithLanguage<Guild>>;
users: Map<bigint, User>;
members: Map<bigint, Member>;
channels: Map<bigint, Channel>;
messages: {
latest: Map<bigint, Message>;
previous: Map<bigint, Message>;
};
};
database: Database;
commands: {
global: Command[];
Expand All @@ -116,18 +101,23 @@ type Client = Readonly<{
// The keys are user IDs, the values are command usage timestamps mapped by command IDs.
rateLimiting: Map<bigint, Map<bigint, number[]>>;
};
localisations: Map<string, Map<Language, (args: Record<string, unknown>) => string>>;
localisation: {
compilers: Record<Language, CompiledLocalisation>;
localisations: Map<string, Map<Language, (args: Record<string, unknown>) => string>>;
};
}>;

type CompiledLocalisation = ReturnType<typeof MessagePipe.MessagePipe>["compile"];

function createClient(
metadata: Client["metadata"],
features: Client["features"],
localisationsStatic: Map<string, Map<Language, string>>,
): Client {
const localisations = createLocalisations(localisationsStatic);
const localisation = createLocalisations(localisationsStatic);

const local = localiseCommands(localisations, commandTemplates.local);
const global = localiseCommands(localisations, commandTemplates.global);
const local = localiseCommands(localisation.localisations, commandTemplates.local);
const global = localiseCommands(localisation.localisations, commandTemplates.global);

const handlers = createCommandHandlers(commandTemplates.local);

Expand Down Expand Up @@ -156,12 +146,25 @@ function createClient(
cache: createCache(),
database: createDatabase(metadata.environment),
features,
localisations,
localisation,
commands: { local, global, handlers },
collectors: new Map(),
};
}

function createCache(): Client["cache"] {
return {
guilds: new Map(),
users: new Map(),
members: new Map(),
channels: new Map(),
messages: {
latest: new Map(),
previous: new Map(),
},
};
}

async function initialiseClient(
metadata: Client["metadata"],
features: Omit<Client["features"], "music">,
Expand Down Expand Up @@ -517,7 +520,10 @@ function withRateLimiting(handle: InteractionHandler): InteractionHandler {
};
}

function localiseCommands(localisations: Client["localisations"], commandTemplates: CommandTemplate[]): Command[] {
function localiseCommands(
localisations: Client["localisation"]["localisations"],
commandTemplates: CommandTemplate[],
): Command[] {
function localiseCommandOrOption(key: string): Pick<Command, LocalisationProperties> | undefined {
const optionName = key.split(".")?.at(-1);
if (optionName === undefined) {
Expand Down Expand Up @@ -982,20 +988,19 @@ function isSubcommand(option: InteractionDataOption): boolean {
return option.type === ApplicationCommandOptionTypes.SubCommand;
}

type CompiledLocalisation = ReturnType<typeof MessagePipe.MessagePipe>["compile"];

function createLocalisations(localisations: Map<string, Map<Language, string>>): Client["localisations"] {
const localisedCompilers = new Map<Language, CompiledLocalisation>();
function createLocalisations(localisationsRaw: Map<string, Map<Language, string>>): Client["localisation"] {
const compilersProvisional: Partial<Record<Language, CompiledLocalisation>> = {};
for (const [language, transformers] of Object.entries(localisationTransformers)) {
localisedCompilers.set(language as Language, MessagePipe.MessagePipe(transformers).compile);
compilersProvisional[language as Language] = MessagePipe.MessagePipe(transformers).compile;
}
const compilers = compilersProvisional as Record<Language, CompiledLocalisation>;

const result = new Map<string, Map<Language, (args: Record<string, unknown>) => string>>();
for (const [key, languages] of localisations.entries()) {
const localisations = new Map<string, Map<Language, (args: Record<string, unknown>) => string>>();
for (const [key, languages] of localisationsRaw.entries()) {
const functions = new Map<Language, (args: Record<string, unknown>) => string>();

for (const [language, string] of languages.entries()) {
const compile = localisedCompilers.get(language);
const compile = compilers[language];
if (compile === undefined) {
console.error(`Failed to compile localisation function for string key '${key}' in ${language}.`);
continue;
Expand All @@ -1004,17 +1009,19 @@ function createLocalisations(localisations: Map<string, Map<Language, string>>):
functions.set(language, compile(string));
}

result.set(key, functions);
localisations.set(key, functions);
}

return result;
return { compilers, localisations };
}

function localise(client: Client, key: string, locale: string | undefined): (args?: Record<string, unknown>) => string {
const language = (locale !== undefined ? getLanguageByLocale(locale as Locales) : undefined) ?? defaultLanguage;

const getLocalisation =
client.localisations.get(key)?.get(language) ?? client.localisations.get(key)?.get(defaultLanguage) ?? (() => key);
client.localisation.localisations.get(key)?.get(language) ??
client.localisation.localisations.get(key)?.get(defaultLanguage) ??
(() => key);

return (args) => {
const string = getLocalisation(args ?? {});
Expand Down Expand Up @@ -1047,6 +1054,18 @@ function toDiscordLocalisations(
return result;
}

function pluralise(client: Client, key: string, language: Language, number: number): string {
const compile = client.localisation.compilers[language];
const pluralised = compile(
`{number | pluralise, one:"${client.localisation.localisations.get(`${key}.one`)?.get(language)?.({
one: number,
})}", two:"${client.localisation.localisations.get(`${key}.two`)?.get(language)?.({
two: number,
})}", many:"${client.localisation.localisations.get(`${key}.many`)?.get(language)?.({ many: number })}"}`,
)({ number });
return pluralised;
}

function isServicing(client: Client, guildId: bigint): boolean {
const environment = configuration.guilds.environments[guildId.toString()];
return environment === client.metadata.environment.environment;
Expand All @@ -1064,5 +1083,6 @@ export {
localise,
resolveIdentifierToMembers,
resolveInteractionToMember,
pluralise,
};
export type { Client, Collector, CompiledLocalisation, WithLanguage };
23 changes: 19 additions & 4 deletions src/lib/commands/language/commands/word.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import constants from "../../../../constants.js";
import { code } from "../../../../formatting.js";
import { defaultLocale } from "../../../../types.js";
import { Client, localise } from "../../../client.js";
import { defaultLanguage, defaultLocale, getLanguageByLocale } from "../../../../types.js";
import { Client, localise, pluralise } from "../../../client.js";
import {
acknowledge,
createInteractionCollector,
Expand Down Expand Up @@ -642,7 +642,19 @@ function fitTextToFieldSize(client: Client, textParts: string[], locale: string
definitionsOmitted: localise(client, "word.strings.definitionsOmitted", locale),
};

const characterOverhead = strings.definitionsOmitted({ number: textParts.length, flag: "verbose" }).length + 20;
const language = getLanguageByLocale(locale) ?? defaultLanguage;

const characterOverhead =
strings.definitionsOmitted({
definitions: localise(
client,
"word.strings.definitionsOmitted",
locale,
)({
definitions: pluralise(client, "word.strings.definitionsOmitted.definitions", language, textParts.length),
}),
flag: "verbose",
}).length + 20;

const maxCharacterCount = verbose ? 2048 : 512;

Expand All @@ -662,7 +674,10 @@ function fitTextToFieldSize(client: Client, textParts: string[], locale: string

let fittedString = stringsToDisplay.join("\n");
if (stringsOmitted !== 0) {
fittedString += `\n*${strings.definitionsOmitted({ number: stringsOmitted, flag: "verbose" })}*`;
fittedString += `\n*${strings.definitionsOmitted({
definitions: pluralise(client, "word.strings.definitionsOmitted.definitions", language, stringsOmitted),
flag: "verbose",
})}*`;
}

return fittedString;
Expand Down
Loading

0 comments on commit 30d2c7c

Please sign in to comment.