Skip to content

Commit 8f58c38

Browse files
committed
feat: warning if changing to locale without translations
1 parent aa7adef commit 8f58c38

File tree

6 files changed

+58
-23
lines changed

6 files changed

+58
-23
lines changed

src/assets/translations/en-US.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@
7777
"invalidValue": "Invalid ${settingName} specified: `${newValue}`${invalidHintSuffix}",
7878
"success": "Successfully set the ${settingName} to `${newValue}`",
7979
"error": "Couldn't set the ${settingName} due to an error: ${err}",
80-
"localeInvalidHint": "Only the listed languages are supported."
80+
"localeInvalidHint": "Only the listed languages are supported.",
81+
"localeNotFoundWarning": "Translations for the locale `${localeName}` were not found.\nThe change has been saved because number formatting will still work, but all text will default to English.\n\nIf you would like to contribute to translations, please [join the support server.](${supportServerInviteUrl})"
8182
},
8283
"reset": {
8384
"confirm": "Are you sure you want to reset the configuration?",

src/commands/Config.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { capitalize } from "@lib/text.ts";
99
import localesJson from "@assets/locales.json" with { type: "json" };
1010
import type { Stringifiable } from "@src/types.ts";
1111
import { registerCommandsForGuild } from "@lib/registry.ts";
12-
import { getLocMap, tr, type TrKeyEn } from "@lib/translate.ts";
12+
import { getLocMap, getRegisteredLanguages, tr, type TrKeyEn } from "@lib/translate.ts";
1313
import { getEnvVar } from "@lib/env.ts";
1414

1515
//#region constants
@@ -275,6 +275,8 @@ export class ConfigCmd extends SlashCommand {
275275
int[int.deferred || int.replied ? "editReply" : "reply"](useEmbedify(tr.for(await ConfigCmd.getGuildLocale(int), "errors.guildCfgInaccessible"), Col.Error));
276276
}
277277

278+
//#region s:editCfgSett
279+
278280
/** Call to edit or view the passed configuration setting */
279281
public static async editConfigSetting<
280282
TCfgKey extends keyof GuildConfig,
@@ -311,12 +313,12 @@ export class ConfigCmd extends SlashCommand {
311313
if(!cfg)
312314
return await ConfigCmd.noConfigFound(int);
313315

314-
// TODO: check if new_value needs to be translated
315-
const newValue = opt.options?.[0]?.options?.find(o => o.name === "new_value")?.value as TCfgValue | undefined;
316+
const newValue = opt.options?.[0]?.options?.find(o => o.name === tr.for("en-US", "commands.config.names.subcmd.args.newValue"))?.value as TCfgValue | undefined;
316317
if(typeof newValue === "undefined") {
317318
const cfg = await em.findOne(GuildConfig, { id: int.guildId });
318319
if(!cfg)
319320
return await ConfigCmd.noConfigFound(int);
321+
320322
return int.editReply(useEmbedify(tr.for(locale, "commands.config.set.currentValue", {
321323
settingName: tr.for(locale, settingNameTrKey),
322324
newValue: getValueLabel(cfg[cfgProp] as TCfgValue, locale) ?? cfg[cfgProp],
@@ -337,6 +339,16 @@ export class ConfigCmd extends SlashCommand {
337339
if(cfgProp === "locale" && !this.global)
338340
await registerCommandsForGuild(int.guildId);
339341

342+
// if no translations exist for the new locale, warn the user
343+
const localeChanged = locale !== newValue;
344+
if(localeChanged && !getRegisteredLanguages().has(String(newValue)) && opt.options?.[0]?.name === getSCNames().locale){
345+
const loc = localesJson.find(({ code }) => code === newValue);
346+
return await int.editReply(useEmbedify(tr.for(locale, "commands.config.set.localeNotFoundWarning", {
347+
localeName: loc ? `${loc.name} (${loc.nativeName})` : newValue,
348+
supportServerInviteUrl: getEnvVar("SUPPORT_SERVER_INVITE_URL"),
349+
}), Col.Warning));
350+
}
351+
340352
return int.editReply(useEmbedify(tr.for(locale, "commands.config.set.success", {
341353
settingName: tr.for(locale, settingNameTrKey),
342354
newValue: getValueLabel(newValue, locale) ?? newValue,

src/lib/registry.ts

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,17 +88,23 @@ async function registerGuildCommands() {
8888

8989
/** Registers all commands for the specified guild in the Discord API - can also be called at runtime to add commands to new guilds */
9090
export async function registerCommandsForGuild(guildId: string) {
91-
const { signal, abort } = new AbortController(),
92-
timeout = setTimeout(() => abort(), 10_000),
93-
data = await rest.put(
94-
Routes.applicationGuildCommands(clientId, guildId),
95-
{
96-
body: [...cmdInstances.entries()].map(([, cmd]) => cmd.builderJson),
97-
signal,
98-
},
99-
);
100-
clearTimeout(timeout);
101-
return data;
91+
try {
92+
const ac = new AbortController(),
93+
{ signal, abort } = ac,
94+
timeout = setTimeout(() => abort(), 10_000),
95+
data = await rest.put(
96+
Routes.applicationGuildCommands(clientId, guildId),
97+
{
98+
body: [...cmdInstances.entries()].map(([, cmd]) => cmd.builderJson),
99+
signal,
100+
},
101+
);
102+
clearTimeout(timeout);
103+
return data;
104+
}
105+
catch(err) {
106+
console.error(`Error while registering commands for guild "${guildId}": ${err}`);
107+
}
102108
}
103109

104110
/** Registers all client events and their listeners */

src/lib/translate.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,11 @@ export async function initTranslations(): Promise<void> {
411411
tr.setFallbackLanguage(enName ?? defaultLocale);
412412
}
413413

414+
/** Returns all registered languages */
415+
export function getRegisteredLanguages() {
416+
return new Set(Object.keys(trans));
417+
}
418+
414419
//#region getLocMap
415420

416421
/** Returns a localization map for all locales where the given common translation key exists */

src/main.ts

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ async function init() {
4343
client.on(Events.Error, (err) => console.error(k.red("Client error:"), err));
4444

4545
process.on("unhandledRejection", (reason, promise) => {
46-
console.error(k.red("Unhandled rejection at:"), promise, k.red("\nRejection reason:"), reason);
46+
console.error(k.red("Unhandled Promise rejection:"), promise, k.red("\n\nRejection reason:"), reason);
4747
});
4848

4949
console.log(k.blue(`${client.user?.displayName ?? client.user?.username} is ready.\n`));
@@ -90,18 +90,23 @@ async function checkGuilds(client: Client) {
9090
const registeredGuilds = await em.findAll(GuildConfig);
9191
const tasks: Promise<unknown | void>[] = [];
9292

93-
for(const guild of [...client.guilds.cache.values()])
94-
!registeredGuilds.some(g => g.id === guild.id)
93+
for(const { id } of [...client.guilds.cache.values()])
94+
!registeredGuilds.find(g => g.id === id)
9595
&& tasks.push(
96-
em.persistAndFlush(new GuildConfig(guild.id)),
97-
registerCommandsForGuild(guild.id),
96+
em.persistAndFlush(new GuildConfig(id)),
97+
registerCommandsForGuild(id),
9898
);
9999

100100
for(const guild of registeredGuilds)
101101
if(!client.guilds.cache.has(guild.id))
102102
tasks.push(em.removeAndFlush(guild));
103103

104-
await Promise.allSettled(tasks);
104+
try {
105+
await Promise.allSettled(tasks);
106+
}
107+
catch(e) {
108+
console.error(k.red("Couldn't check guilds:"), e);
109+
}
105110
}
106111

107112
init();

src/models/GuildConfig.model.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,14 @@ export class GuildConfig {
1010
}
1111

1212
static async ensureExists(id: string) {
13-
const conf = await em.findOne(GuildConfig, { id });
14-
!conf && await em.persistAndFlush(new GuildConfig(id));
13+
let exists = false;
14+
try {
15+
exists = Boolean(await em.findOne(GuildConfig, { id }));
16+
}
17+
catch(e) {
18+
void e;
19+
}
20+
!exists && await em.persistAndFlush(new GuildConfig(id));
1521
}
1622

1723
@PrimaryKey({ type: "string", length: 24 })

0 commit comments

Comments
 (0)