-
Notifications
You must be signed in to change notification settings - Fork 0
🌐 feat(locales): add localization strings #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
38133ff
f476cf0
136d065
4aa5af4
e74c8f2
a29ecd0
fd9a5bb
8e1b0f3
42608f5
9e3cbfb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,37 +1,46 @@ | ||||||
| import { DiscordHono } from 'discord-hono'; | ||||||
| import ky from 'ky'; | ||||||
| import getLanguageFromServer from './utils/getLanguageFromServer'; | ||||||
| import getFileFromLanguage from './utils/getFileFromLanguage'; | ||||||
| import type { FileLanguage } from './types/fileLanguage'; | ||||||
|
|
||||||
| const app = new DiscordHono() | ||||||
| .command('help', async (c) => { | ||||||
| const helpMessage = `**Available Commands:** | ||||||
| - \`/someone [ignore-bots]\`: Ping a random member from the server. Optionally ignore bot users. | ||||||
| - \`/ping\`: Replies with the current ping. | ||||||
| - \`/help\`: Provides help information for available commands. | ||||||
|
|
||||||
| To use a command, type \`/\` followed by the command name. For example, to ping a random member, type \`/someone\`. You can add the optional parameter \`ignore-bots\` to exclude bot users from being selected. | ||||||
| -# Source code is available on [GitHub](https://github.com/notthebestdev/someoneback).`; | ||||||
| const guildId = c.interaction.guild?.id as string; | ||||||
| let lang: FileLanguage = getFileFromLanguage('en') as FileLanguage; | ||||||
| await getLanguageFromServer(guildId, c).then((language) => { | ||||||
| lang = getFileFromLanguage(language) as FileLanguage; | ||||||
| }); | ||||||
| const helpMessage = lang.HELP_MESSAGE; | ||||||
|
|
||||||
| return c.res(helpMessage); | ||||||
| }) | ||||||
| .command('someone', async (c) => { | ||||||
| // check if user has permission to mention everyone | ||||||
| const guildId = c.interaction.guild?.id; | ||||||
| const memberPermissions = c.interaction.member?.permissions; | ||||||
| if (!memberPermissions) return c.res('<a:crossmark:1454281378295451648> **Unable to verify permissions.**'); | ||||||
| if (!guildId) return c.res('<a:crossmark:1454281378295451648> **Guild not found.**'); | ||||||
| let lang: FileLanguage = getFileFromLanguage('en') as FileLanguage; | ||||||
| await getLanguageFromServer(guildId, c).then((language) => { | ||||||
| lang = getFileFromLanguage(language) as FileLanguage; | ||||||
| }); | ||||||
|
Comment on lines
+24
to
+26
|
||||||
| if (!memberPermissions) return c.res(lang.PERMISSIONS_ERROR); | ||||||
|
|
||||||
| const hasMentionEveryonePermission = BigInt(memberPermissions) & BigInt(0x20000); | ||||||
| if (!hasMentionEveryonePermission) { | ||||||
| return c.res('<a:crossmark:1454281378295451648> **You need the Mention Everyone permission to use this command.**'); | ||||||
| return c.res(lang.MENTION_EVERYONE_PERMISSION_MISSING); | ||||||
| } | ||||||
|
|
||||||
| // get guild id | ||||||
| const guildId = c.interaction.guild?.id; | ||||||
| if (!guildId) return c.res('<a:crossmark:1454281378295451648> **Guild not found.**'); | ||||||
| if (!guildId) return c.res(lang.GUILD_NOT_FOUND); | ||||||
|
Comment on lines
25
to
+35
|
||||||
|
|
||||||
| const env = c.env as { DISCORD_TOKEN?: string }; | ||||||
| const token = env.DISCORD_TOKEN || process.env.BOT_TOKEN; | ||||||
| if (!token) return c.res('<a:crossmark:1454281378295451648> **Bot token not found in env.**'); | ||||||
| if (!token) return c.res(lang.BOT_TOKEN_NOT_FOUND_ERROR); | ||||||
|
|
||||||
| // fetch members, limit to 1000 due to discord api limitation | ||||||
| const resp = await fetch(`https://discord.com/api/v10/guilds/${guildId}/members?limit=1000`, { | ||||||
| const resp = await ky.get(`https://discord.com/api/v10/guilds/${guildId}/members`, { | ||||||
| searchParams: { limit: '1000' }, | ||||||
| headers: { Authorization: `Bot ${token}` }, | ||||||
| }); | ||||||
| if (!resp.ok) return c.res(`Failed to fetch members: ${resp.status} ${resp.statusText}`); | ||||||
|
||||||
| if (!resp.ok) return c.res(`Failed to fetch members: ${resp.status} ${resp.statusText}`); | |
| if (!resp.ok) return c.res(`${resp.status} ${resp.statusText}`); |
Copilot
AI
Jan 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The language loading pattern is duplicated in three command handlers. Consider extracting this logic into a helper function or middleware to avoid code duplication and ensure consistent language loading behavior across all commands.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| PERMISSIONS_ERROR: '<a:crossmark:1454281378295451648> **Unable to verify permissions.**' | ||
| MENTION_EVERYONE_PERMISSION_MISSING: '<a:crossmark:1454281378295451648> **You need the Mention Everyone permission to use this command.**' | ||
| GUILD_NOT_FOUND_ERROR: '<a:crossmark:1454281378295451648> **Guild not found.**' | ||
| BOT_TOKEN_NOT_FOUND_ERROR: '<a:crossmark:1454281378295451648> **Bot token not found in .env**' | ||
| NO_MEMBERS_ERROR: '<a:crossmark:1454281378295451648> **No members found.**' | ||
| NO_MEMBERS_MATCH_FILTER_ERROR: '<a:crossmark:1454281378295451648> **No members match the filter (all results were bots or yourself).**' | ||
| USER_ID_NOT_FOUND_ERROR: '<a:crossmark:1454281378295451648> **User ID not found.**' | ||
| YOU_HAVE_BEEN_CHOSEN: '**<a:confetti:1437507874614939789> {{USER_ID}}, you have been chosen!**' | ||
| PING: "**<a:sparkles:1454282222210125959> Pong!** \n-# Latency: {{LATENCY}}ms" | ||
| GUILD_NOT_FOUND: '<a:crossmark:1454281378295451648> **Guild not found.**' | ||
| HELP_MESSAGE: | | ||
| **Available Commands:** | ||
| - `/someone [ignore-bots]`: Ping a random member from the server. Optionally ignore bot users. | ||
| - `/ping`: Replies with the current ping. | ||
| - `/help`: Provides help information for available commands. | ||
|
|
||
| To use a command, type `/` followed by the command name. For example, to ping a random member, type `/someone`. You can add the optional parameter `ignore-bots` to exclude bot users from being selected. | ||
| - Source code is available on [GitHub](https://github.com/notthebestdev/someoneback). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| PERMISSIONS_ERROR: '<a:crossmark:1454281378295451648> **Impossible de vérifier les permissions.**' | ||
| MENTION_EVERYONE_PERMISSION_MISSING: '<a:crossmark:1454281378295451648> **Vous devez avoir la permission Mentionner tout le monde pour utiliser cette commande.**' | ||
| GUILD_NOT_FOUND_ERROR: '<a:crossmark:1454281378295451648> **Serveur introuvable.**' | ||
| BOT_TOKEN_NOT_FOUND_ERROR: '<a:crossmark:1454281378295451648> **Jeton du bot introuvable dans .env**' | ||
| NO_MEMBERS_ERROR: '<a:crossmark:1454281378295451648> **Aucun membre trouvé.**' | ||
| NO_MEMBERS_MATCH_FILTER_ERROR: '<a:crossmark:1454281378295451648> **Aucun membre ne correspond au filtre (tous les résultats étaient des bots ou vous-même).**' | ||
| USER_ID_NOT_FOUND_ERROR: '<a:crossmark:1454281378295451648> **ID utilisateur introuvable.**' | ||
| YOU_HAVE_BEEN_CHOSEN: '**<a:confetti:1437507874614939789> {{USER_ID}}, vous avez été choisi !**' | ||
| PING: "**<a:sparkles:1454282222210125959> Pong !** \n-# Latence : {{LATENCY}}ms" | ||
| GUILD_NOT_FOUND: '<a:crossmark:1454281378295451648> **Serveur introuvable.**' | ||
| HELP_MESSAGE: | | ||
| **Commandes disponibles :** | ||
| - `/someone [ignore-bots]` : Mentionne un membre aléatoire du serveur. Optionnellement, ignore les utilisateurs bots. | ||
| - `/ping` : Répond avec la latence actuelle. | ||
| - `/help` : Fournit des informations d'aide sur les commandes disponibles. | ||
|
|
||
| Pour utiliser une commande, tapez `/` suivi du nom de la commande. Par exemple, pour mentionner un membre aléatoire, tapez `/someone`. Vous pouvez ajouter le paramètre optionnel `ignore-bots` pour exclure les bots de la sélection. | ||
| - Le code source est disponible sur [GitHub](https://github.com/notthebestdev/someoneback). |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| export interface FileLanguage { | ||
| PERMISSIONS_ERROR: string; | ||
| MENTION_EVERYONE_PERMISSION_MISSING: string; | ||
| GUILD_NOT_FOUND_ERROR: string; | ||
| BOT_TOKEN_NOT_FOUND_ERROR: string; | ||
| NO_MEMBERS_ERROR: string; | ||
| NO_MEMBERS_MATCH_FILTER_ERROR: string; | ||
| USER_ID_NOT_FOUND_ERROR: string; | ||
| YOU_HAVE_BEEN_CHOSEN: string; | ||
| PING: string; | ||
| GUILD_NOT_FOUND: string; | ||
| HELP_MESSAGE: string; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import yaml from 'yaml'; | ||
| import fs from 'fs'; | ||
| import path from 'path'; | ||
| import type { FileLanguage } from '../types/fileLanguage'; | ||
|
|
||
| export default function getFileFromLanguage(language: string): FileLanguage { | ||
| let fileName: string; | ||
| switch (language) { | ||
| case 'fr': | ||
| fileName = 'fr.yml'; | ||
| break; | ||
| default: | ||
| fileName = 'en.yml'; | ||
| } | ||
| const filePath = path.resolve(__dirname, 'locales', fileName); | ||
| const fileContents = fs.readFileSync(filePath, 'utf8'); | ||
| return yaml.parse(fileContents) as FileLanguage; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import type { CommandContext } from 'discord-hono'; | ||
| import ky from 'ky'; | ||
|
|
||
| export default async function getLanguageFromServer(serverId: string, c: CommandContext): Promise<string> { | ||
| // use ky as a workaround for custom endpoints not supported by client.rest | ||
| const guild = await ky.get(`https://discord.com/api/v9/guilds/templates/${serverId}`, { | ||
| headers: { | ||
| Authorization: `Bot ${c.env.DISCORD_TOKEN || process.env.DISCORD_TOKEN}`, | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| }); | ||
|
|
||
| if (!guild.ok) { | ||
| // request failed, fall back to default language | ||
| return 'en'; | ||
| } | ||
|
|
||
| // get serialized_source_guild.preferred_locale from successful response | ||
| const data = (await guid.json()) as { serialized_source_guild?: { preferred_locale?: string } }; | ||
| return data?.serialized_source_guild?.preferred_locale || 'en'; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The language loading happens after the first guild check. If
guildIdis null at line 22, the function returns before the language is loaded. Move theguildIdnull check after the language loading, or handle the error message using a default language. Currently, line 22 returns a hardcoded English error message even if the server uses French.