-
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?
Conversation
- Introduced various error messages and notifications - Enhanced user feedback for permissions and bot status
- Adds `ky` for making HTTP requests with improved syntax. - Adds `yaml` for parsing YAML configuration files. - Refactors member fetching to use `ky` for improved readability and conciseness.
- Introduces a utility function to retrieve localization files based on the specified language. - Supports French and defaults to English localization.
- Replaces double quotes with single quotes in the English locale file for consistency. - Corrects the file path for locale files and updates the type definition.
- Implements internationalization (i18n) for bot responses. - Uses YAML files to store translations. - Retrieves server language and uses it to display messages. - Adds `en.yml` with default messages.
- Removes an unused import to improve code cleanliness. - Eliminates `parse` from `yaml` as it is no longer needed.
- Adds the `nodejs_compat` compatibility flag to the Wrangler configuration. - Updates the generated types to include NodeJS process environment variables. - This allows the worker to access NodeJS-compatible APIs.
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.
Pull request overview
This PR adds internationalization (i18n) support to enable Discord bot commands to respond in the server's preferred language. The implementation includes language detection from Discord guilds and YAML-based localization files for English and French.
Key changes:
- Added utility functions to fetch server language from Discord API and load localization files
- Created YAML-based localization files for English and French translations
- Integrated localization into command handlers for
/help,/someone, and/pingcommands
Reviewed changes
Copilot reviewed 8 out of 10 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| wrangler.jsonc | Added nodejs_compat compatibility flag to enable Node.js APIs |
| worker-configuration.d.ts | Auto-generated type definitions updated to reflect the new compatibility flag |
| src/utils/getLanguageFromServer.ts | New utility to fetch guild language preference from Discord API |
| src/utils/getFileFromLanguage.ts | New utility to load YAML localization files based on language code |
| src/types/fileLanguage.ts | TypeScript interface defining the structure of localization strings |
| src/locales/fr.yml | French translations for all bot messages and errors |
| src/locales/en.yml | English translations for all bot messages and errors |
| src/index.ts | Updated command handlers to load and use localized strings |
| package.json | Added ky and yaml dependencies for HTTP requests and YAML parsing |
| package-lock.json | Lockfile updated with new dependency entries |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| import fs from 'fs'; | ||
| import path from 'path'; | ||
| import type { FileLanguage } from '../types/fileLanguage'; | ||
|
|
||
| export default function getFileFromLanguage(language: string): unknown { | ||
| 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'); |
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.
Using Node.js fs and path modules in a Cloudflare Workers environment will not work. Cloudflare Workers don't have access to the file system. You need to either bundle the YAML files as static assets or import them directly. Consider using Wrangler's asset binding or importing the YAML files at build time instead of reading them with fs.readFileSync at runtime.
| import fs from 'fs'; | |
| import path from 'path'; | |
| import type { FileLanguage } from '../types/fileLanguage'; | |
| export default function getFileFromLanguage(language: string): unknown { | |
| 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'); | |
| import type { FileLanguage } from '../types/fileLanguage'; | |
| import enYaml from '../locales/en.yml'; | |
| import frYaml from '../locales/fr.yml'; | |
| export default function getFileFromLanguage(language: string): unknown { | |
| let fileContents: string; | |
| switch (language) { | |
| case 'fr': | |
| fileContents = frYaml; | |
| break; | |
| default: | |
| fileContents = enYaml; | |
| } |
| // 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); |
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 guildId is checked and returned early on line 22, making this duplicate check on line 35 redundant and unreachable. The code comment "get guild id" is also misleading since the guildId was already retrieved earlier. Remove this redundant check.
| 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.**'); |
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 happens after the first guild check. If guildId is null at line 22, the function returns before the language is loaded. Move the guildId null 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.
| let lang: FileLanguage = getFileFromLanguage('en') as FileLanguage; | ||
| const guildId = c.interaction.guild?.id as string; | ||
| await getLanguageFromServer(guildId, c).then((language) => { | ||
| lang = getFileFromLanguage(language) as FileLanguage; | ||
| }); |
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.
| await getLanguageFromServer(guildId, c).then((language) => { | ||
| lang = getFileFromLanguage(language) as FileLanguage; | ||
| }); |
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.
Using .then() is unnecessary here since the code already uses await. You can simplify this to: const language = await getLanguageFromServer(guildId, c); lang = getFileFromLanguage(language) as FileLanguage;. This makes the code more readable and consistent with the async/await pattern used elsewhere.
| searchParams: { limit: '1000' }, | ||
| headers: { Authorization: `Bot ${token}` }, | ||
| }); | ||
| if (!resp.ok) return c.res(`Failed to fetch members: ${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.
Error responses should also be localized. This hardcoded error message "Failed to fetch members: {status} {statusText}" will always be in English, even if the server uses French. Consider adding this error message to the localization files.
| if (!resp.ok) return c.res(`Failed to fetch members: ${resp.status} ${resp.statusText}`); | |
| if (!resp.ok) return c.res(`${resp.status} ${resp.statusText}`); |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Warning
This PR is still an W.I.P