From 05d776937c739a6c02f388686eb12c1bab08a21b Mon Sep 17 00:00:00 2001 From: mustafakyia Date: Mon, 19 Jan 2026 21:51:42 +0300 Subject: [PATCH 1/4] add: i18n --- locales/en-US.json | 9 ++++++ package-lock.json | 50 +++++++----------------------- package.json | 2 +- src/structures/Client.ts | 11 +++++++ src/structures/Context.ts | 35 +++++++++++++++------ src/structures/I18n.ts | 64 +++++++++++++++++++++++++++++++++++++++ test-i18n.js | 17 +++++++++++ types/client.d.ts | 6 ++++ 8 files changed, 145 insertions(+), 49 deletions(-) create mode 100644 locales/en-US.json create mode 100644 src/structures/I18n.ts create mode 100644 test-i18n.js diff --git a/locales/en-US.json b/locales/en-US.json new file mode 100644 index 0000000..99c3836 --- /dev/null +++ b/locales/en-US.json @@ -0,0 +1,9 @@ +{ + "hello": "Hello World!", + "greeting": "Hello {user}!", + "commands": { + "ping": { + "desc": "Pong!" + } + } +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d01e2cc..113f494 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "devDependencies": { "@swc/cli": "^0.7.10", "@types/lodash": "^4.17.23", - "@types/node": "^25.0.8", + "@types/node": "^25.0.9", "husky": "^9.1.7", "libnpmpack": "^9.0.12", "oxfmt": "^0.24.0", @@ -46,7 +46,6 @@ "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.1.tgz", "integrity": "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@discordjs/formatters": "^0.6.2", "@discordjs/util": "^1.2.0", @@ -68,7 +67,6 @@ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=16.11.0" } @@ -78,7 +76,6 @@ "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz", "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "discord-api-types": "^0.38.33" }, @@ -94,7 +91,6 @@ "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.0.tgz", "integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", @@ -118,7 +114,6 @@ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=18" }, @@ -131,7 +126,6 @@ "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz", "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==", "license": "Apache-2.0", - "peer": true, "dependencies": { "discord-api-types": "^0.38.33" }, @@ -147,7 +141,6 @@ "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", "license": "Apache-2.0", - "peer": true, "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.1", @@ -171,7 +164,6 @@ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=18" }, @@ -1244,7 +1236,6 @@ "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", "license": "MIT", - "peer": true, "engines": { "node": ">=v14.0.0", "npm": ">=7.0.0" @@ -1255,7 +1246,6 @@ "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" @@ -1269,7 +1259,6 @@ "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=v14.0.0", "npm": ">=7.0.0" @@ -1466,7 +1455,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=10" } @@ -1484,7 +1472,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=10" } @@ -1502,7 +1489,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -1520,7 +1506,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -1538,7 +1523,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -1556,7 +1540,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -1574,7 +1557,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=10" } @@ -1592,7 +1574,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=10" } @@ -1610,7 +1591,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=10" } @@ -1628,7 +1608,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=10" } @@ -1645,6 +1624,7 @@ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz", "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.8.0" } @@ -1655,7 +1635,6 @@ "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", "dev": true, "license": "Apache-2.0", - "peer": true, "dependencies": { "@swc/counter": "^0.1.3" } @@ -1754,9 +1733,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.8.tgz", - "integrity": "sha512-powIePYMmC3ibL0UJ2i2s0WIbq6cg6UyVFQxSCpaPxxzAaziRfimGivjdF943sSGV6RADVbk0Nvlm5P/FB44Zg==", + "version": "25.0.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", + "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", "license": "MIT", "dependencies": { "undici-types": "~7.16.0" @@ -1767,7 +1746,6 @@ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "license": "MIT", - "peer": true, "dependencies": { "@types/node": "*" } @@ -1777,7 +1755,6 @@ "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==", "license": "MIT", - "peer": true, "engines": { "node": ">=v14.0.0", "npm": ">=7.0.0" @@ -2359,7 +2336,6 @@ "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.37.tgz", "integrity": "sha512-Cv47jzY1jkGkh5sv0bfHYqGgKOWO1peOrGMkDFM4UmaGMOTgOW8QSexhvixa9sVOiz8MnVOBryWYyw/CEVhj7w==", "license": "MIT", - "peer": true, "workspaces": [ "scripts/actions/documentation" ] @@ -2492,8 +2468,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fast-fifo": { "version": "1.3.2", @@ -3092,8 +3067,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/lowercase-keys": { "version": "3.0.0", @@ -3122,8 +3096,7 @@ "version": "1.12.1", "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.12.1.tgz", "integrity": "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/make-fetch-happen": { "version": "15.0.3", @@ -3688,6 +3661,7 @@ "integrity": "sha512-fGYb7z/cljC0Rjtbxh7mIe8vtF/M9TShLvniwc2rdcqNG3Z9g3nM01cr2kWRb1DZdbY4/kItvIsrV4uhaMifyQ==", "dev": true, "license": "MIT", + "peer": true, "bin": { "tsgolint": "bin/tsgolint.js" }, @@ -3810,6 +3784,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -4451,8 +4426,7 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/tslib": { "version": "2.8.1", @@ -4518,7 +4492,6 @@ "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.17" } @@ -4641,7 +4614,6 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "license": "MIT", - "peer": true, "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index 4160ea0..5f2b459 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "devDependencies": { "@swc/cli": "^0.7.10", "@types/lodash": "^4.17.23", - "@types/node": "^25.0.8", + "@types/node": "^25.0.9", "husky": "^9.1.7", "libnpmpack": "^9.0.12", "oxfmt": "^0.24.0", diff --git a/src/structures/Client.ts b/src/structures/Client.ts index c7ef552..b10f929 100644 --- a/src/structures/Client.ts +++ b/src/structures/Client.ts @@ -13,11 +13,14 @@ import { merge } from "lodash"; import { clearClient, setClient } from "../context"; import { getPrefix } from "../utils/util"; import { Logger } from "../utils/logger/Logger"; +import { I18n } from "./I18n"; + const defaultOpts: Omit = { paths: { events: "events", commands: "commands", + locales: "locales", }, autoRegisterCommands: true, }; @@ -29,6 +32,7 @@ export class Client< public commands: Collection; public aliases: Collection>; public readonly prefix: string | false; + public readonly i18n: I18n; declare public options: Omit & { intents: IntentsBitField; @@ -40,6 +44,13 @@ export class Client< this.commands = new Collection(); this.aliases = new Collection(); this.prefix = getPrefix(this.options.prefix ?? { enabled: false }); + this.i18n = new I18n(this.options.i18n?.defaultLocale, this.logger); + + if (this.options.paths?.locales) { + this.i18n.loadLocales( + path.join(getProjectRoot(), this.options.paths.locales) + ); + } if (this.options.paths?.events) { this.loadFiles( diff --git a/src/structures/Context.ts b/src/structures/Context.ts index d7484de..3fa7e65 100644 --- a/src/structures/Context.ts +++ b/src/structures/Context.ts @@ -1,10 +1,14 @@ -import { Message, User, ChatInputCommandInteraction } from "discord.js"; +import { + Message, + User, + ChatInputCommandInteraction, +} from "discord.js"; import { Client } from "../structures/Client"; type ContextPayload = T extends ChatInputCommandInteraction - ? { interaction: T; args?: string[] } - : { message: T; args?: string[] }; + ? { interaction: T; args?: string[] } + : { message: T; args?: string[] }; export class Context { public readonly client: Client; @@ -22,14 +26,16 @@ export class Context { } } + public isInteraction(): this is Context { - return this.data instanceof ChatInputCommandInteraction; + return "user" in this.data; } public isMessage(): this is Context { - return this.data instanceof Message; + return "author" in this.data; } + public get author(): User | null { if (this.isInteraction()) { return this.data.user; @@ -40,22 +46,33 @@ export class Context { return null; } - toJSON() { + public t(key: string, args?: Record) { + let locale = this.client.i18n.defaultLocale; + + if (this.isInteraction()) { + locale = this.data.locale; + } + + return this.client.i18n.t(locale, key, args); + } + + + public toJSON() { const { data, args, author } = this; if (this.isInteraction()) { return { kind: "interaction" as const, interaction: data, - author: author, + author, }; } return { kind: "message" as const, message: data as Message, - args: args, - author: author, + args, + author, }; } } diff --git a/src/structures/I18n.ts b/src/structures/I18n.ts new file mode 100644 index 0000000..7008bc7 --- /dev/null +++ b/src/structures/I18n.ts @@ -0,0 +1,64 @@ +import { Logger } from "../utils/logger/Logger"; +import { getFiles } from "../utils/Files"; +import { get } from "lodash"; + +export class I18n { + public locales: Map> = new Map(); + public defaultLocale: string; + private logger: Logger; + + constructor(defaultLocale: string = "en-US", logger: Logger) { + this.defaultLocale = defaultLocale; + this.logger = logger; + } + + public async loadLocales(dir: string) { + if (!require("fs").existsSync(dir)) { + this.logger.warn(`Locales directory not found: ${dir}`); + return; + } + + const files = getFiles(dir); + + for (const file of files) { + try { + const localeName = file.split(/[\\/]/).pop()?.split(".")[0]; + if (!localeName) continue; + + delete require.cache[require.resolve(file)]; + const content = require(file); + this.locales.set(localeName, content); + this.logger.debug(`Loaded locale: ${localeName}`); + } catch (error) { + this.logger.error(`Error loading locale ${file}:`, error); + } + } + + this.logger.info(`Loaded ${this.locales.size} locales.`); + } + + public t(locale: string, key: string, args?: Record): string { + const lang = this.locales.get(locale) || this.locales.get(this.defaultLocale); + + if (!lang) return key; + + let value = get(lang, key); + + if (!value && locale !== this.defaultLocale) { + const defaultLang = this.locales.get(this.defaultLocale); + value = get(defaultLang, key); + } + + if (!value) return key; + + if (typeof value !== "string") return value; + + if (args) { + for (const [k, v] of Object.entries(args)) { + value = value.replace(new RegExp(`{${k}}`, "g"), String(v)); + } + } + + return value; + } +} diff --git a/test-i18n.js b/test-i18n.js new file mode 100644 index 0000000..7224230 --- /dev/null +++ b/test-i18n.js @@ -0,0 +1,17 @@ +const { I18n } = require('./dist/structures/I18n'); +const { Logger } = require('./dist/utils/logger/Logger'); +const path = require('path'); + +const logger = new Logger(); +const i18n = new I18n('en-US', logger); + +(async () => { + console.log('Loading locales...'); + await i18n.loadLocales(path.join(__dirname, 'locales')); + + console.log('Testing translations:'); + console.log('hello:', i18n.t('en-US', 'hello')); + console.log('greeting:', i18n.t('en-US', 'greeting', { user: 'Developer' })); + console.log('nested:', i18n.t('en-US', 'commands.ping.desc')); + console.log('fallback:', i18n.t('tr-TR', 'hello')); // Should fallback to en-US +})(); diff --git a/types/client.d.ts b/types/client.d.ts index 4e1055d..578a2b1 100644 --- a/types/client.d.ts +++ b/types/client.d.ts @@ -4,6 +4,7 @@ import { LoggerOptions } from "../src/utils/logger/Logger"; export interface FrameworkPaths { events?: string; commands?: string; + locales?: string; } export type PrefixOptions = @@ -11,9 +12,14 @@ export type PrefixOptions = | { enabled: false } | string; +export interface I18nOptions { + defaultLocale?: string; +} + export interface FrameworkOptions extends ClientOptions { logger?: LoggerOptions; prefix?: PrefixOptions; paths?: FrameworkPaths; autoRegisterCommands?: boolean; + i18n?: I18nOptions; } From b69401c7e08f639ce2be3caf695c028f4ed2cead Mon Sep 17 00:00:00 2001 From: mustafakyia Date: Mon, 19 Jan 2026 21:53:17 +0300 Subject: [PATCH 2/4] format files --- locales/en-US.json | 16 +++---- src/structures/Client.ts | 1 - src/structures/Context.ts | 13 ++---- src/structures/I18n.ts | 89 ++++++++++++++++++++------------------- test-i18n.js | 22 +++++----- 5 files changed, 67 insertions(+), 74 deletions(-) diff --git a/locales/en-US.json b/locales/en-US.json index 99c3836..ed96d9f 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -1,9 +1,9 @@ { - "hello": "Hello World!", - "greeting": "Hello {user}!", - "commands": { - "ping": { - "desc": "Pong!" - } - } -} \ No newline at end of file + "hello": "Hello World!", + "greeting": "Hello {user}!", + "commands": { + "ping": { + "desc": "Pong!" + } + } +} diff --git a/src/structures/Client.ts b/src/structures/Client.ts index b10f929..51c9c77 100644 --- a/src/structures/Client.ts +++ b/src/structures/Client.ts @@ -15,7 +15,6 @@ import { getPrefix } from "../utils/util"; import { Logger } from "../utils/logger/Logger"; import { I18n } from "./I18n"; - const defaultOpts: Omit = { paths: { events: "events", diff --git a/src/structures/Context.ts b/src/structures/Context.ts index 3fa7e65..3df99db 100644 --- a/src/structures/Context.ts +++ b/src/structures/Context.ts @@ -1,14 +1,10 @@ -import { - Message, - User, - ChatInputCommandInteraction, -} from "discord.js"; +import { Message, User, ChatInputCommandInteraction } from "discord.js"; import { Client } from "../structures/Client"; type ContextPayload = T extends ChatInputCommandInteraction - ? { interaction: T; args?: string[] } - : { message: T; args?: string[] }; + ? { interaction: T; args?: string[] } + : { message: T; args?: string[] }; export class Context { public readonly client: Client; @@ -26,7 +22,6 @@ export class Context { } } - public isInteraction(): this is Context { return "user" in this.data; } @@ -35,7 +30,6 @@ export class Context { return "author" in this.data; } - public get author(): User | null { if (this.isInteraction()) { return this.data.user; @@ -56,7 +50,6 @@ export class Context { return this.client.i18n.t(locale, key, args); } - public toJSON() { const { data, args, author } = this; diff --git a/src/structures/I18n.ts b/src/structures/I18n.ts index 7008bc7..d57dbd0 100644 --- a/src/structures/I18n.ts +++ b/src/structures/I18n.ts @@ -3,62 +3,63 @@ import { getFiles } from "../utils/Files"; import { get } from "lodash"; export class I18n { - public locales: Map> = new Map(); - public defaultLocale: string; - private logger: Logger; + public locales: Map> = new Map(); + public defaultLocale: string; + private logger: Logger; - constructor(defaultLocale: string = "en-US", logger: Logger) { - this.defaultLocale = defaultLocale; - this.logger = logger; - } + constructor(defaultLocale: string = "en-US", logger: Logger) { + this.defaultLocale = defaultLocale; + this.logger = logger; + } - public async loadLocales(dir: string) { - if (!require("fs").existsSync(dir)) { - this.logger.warn(`Locales directory not found: ${dir}`); - return; - } + public async loadLocales(dir: string) { + if (!require("fs").existsSync(dir)) { + this.logger.warn(`Locales directory not found: ${dir}`); + return; + } - const files = getFiles(dir); + const files = getFiles(dir); - for (const file of files) { - try { - const localeName = file.split(/[\\/]/).pop()?.split(".")[0]; - if (!localeName) continue; + for (const file of files) { + try { + const localeName = file.split(/[\\/]/).pop()?.split(".")[0]; + if (!localeName) continue; - delete require.cache[require.resolve(file)]; - const content = require(file); - this.locales.set(localeName, content); - this.logger.debug(`Loaded locale: ${localeName}`); - } catch (error) { - this.logger.error(`Error loading locale ${file}:`, error); - } - } + delete require.cache[require.resolve(file)]; + const content = require(file); + this.locales.set(localeName, content); + this.logger.debug(`Loaded locale: ${localeName}`); + } catch (error) { + this.logger.error(`Error loading locale ${file}:`, error); + } + } - this.logger.info(`Loaded ${this.locales.size} locales.`); - } + this.logger.info(`Loaded ${this.locales.size} locales.`); + } - public t(locale: string, key: string, args?: Record): string { - const lang = this.locales.get(locale) || this.locales.get(this.defaultLocale); + public t(locale: string, key: string, args?: Record): string { + const lang = + this.locales.get(locale) || this.locales.get(this.defaultLocale); - if (!lang) return key; + if (!lang) return key; - let value = get(lang, key); + let value = get(lang, key); - if (!value && locale !== this.defaultLocale) { - const defaultLang = this.locales.get(this.defaultLocale); - value = get(defaultLang, key); - } + if (!value && locale !== this.defaultLocale) { + const defaultLang = this.locales.get(this.defaultLocale); + value = get(defaultLang, key); + } - if (!value) return key; + if (!value) return key; - if (typeof value !== "string") return value; + if (typeof value !== "string") return value; - if (args) { - for (const [k, v] of Object.entries(args)) { - value = value.replace(new RegExp(`{${k}}`, "g"), String(v)); - } - } + if (args) { + for (const [k, v] of Object.entries(args)) { + value = value.replace(new RegExp(`{${k}}`, "g"), String(v)); + } + } - return value; - } + return value; + } } diff --git a/test-i18n.js b/test-i18n.js index 7224230..55f70ad 100644 --- a/test-i18n.js +++ b/test-i18n.js @@ -1,17 +1,17 @@ -const { I18n } = require('./dist/structures/I18n'); -const { Logger } = require('./dist/utils/logger/Logger'); -const path = require('path'); +const { I18n } = require("./dist/structures/I18n"); +const { Logger } = require("./dist/utils/logger/Logger"); +const path = require("path"); const logger = new Logger(); -const i18n = new I18n('en-US', logger); +const i18n = new I18n("en-US", logger); (async () => { - console.log('Loading locales...'); - await i18n.loadLocales(path.join(__dirname, 'locales')); + console.log("Loading locales..."); + await i18n.loadLocales(path.join(__dirname, "locales")); - console.log('Testing translations:'); - console.log('hello:', i18n.t('en-US', 'hello')); - console.log('greeting:', i18n.t('en-US', 'greeting', { user: 'Developer' })); - console.log('nested:', i18n.t('en-US', 'commands.ping.desc')); - console.log('fallback:', i18n.t('tr-TR', 'hello')); // Should fallback to en-US + console.log("Testing translations:"); + console.log("hello:", i18n.t("en-US", "hello")); + console.log("greeting:", i18n.t("en-US", "greeting", { user: "Developer" })); + console.log("nested:", i18n.t("en-US", "commands.ping.desc")); + console.log("fallback:", i18n.t("tr-TR", "hello")); // Should fallback to en-US })(); From c0f18abe1ee99aa3c0268d35a19988905b9f6187 Mon Sep 17 00:00:00 2001 From: vrdons Date: Sun, 25 Jan 2026 11:46:52 +0300 Subject: [PATCH 3/4] use i18next module --- .github/workflows/changelog.yml | 44 -------- examples/basic_client/index.js | 29 ++++- examples/basic_client/locales/en-US/test.json | 3 + locales/en-US.json | 9 -- package-lock.json | 104 +++++++++++++++--- package.json | 2 + src/events/interaction.ts | 10 +- src/events/message.ts | 4 +- src/index.ts | 1 + src/structures/Client.ts | 41 +++---- src/structures/Context.ts | 31 ++++-- src/structures/I18n.ts | 65 ----------- src/utils/logger/Logger.ts | 17 +++ src/utils/util.ts | 3 +- test-i18n.js | 17 --- types/client.d.ts | 9 +- 16 files changed, 192 insertions(+), 197 deletions(-) delete mode 100644 .github/workflows/changelog.yml create mode 100644 examples/basic_client/locales/en-US/test.json delete mode 100644 locales/en-US.json delete mode 100644 src/structures/I18n.ts delete mode 100644 test-i18n.js diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml deleted file mode 100644 index 7f44b74..0000000 --- a/.github/workflows/changelog.yml +++ /dev/null @@ -1,44 +0,0 @@ -name: Generate Changelog - -on: - schedule: - - cron: "0 3 * * *" - workflow_dispatch: - -jobs: - changelog: - name: Generate changelog - runs-on: ubuntu-latest - permissions: - contents: write - - steps: - - name: Checkout main - uses: actions/checkout@v4 - with: - fetch-depth: 0 - ref: main - - - name: Generate changelog - uses: orhun/git-cliff-action@v4 - with: - config: cliff.toml - args: --verbose - output_file: CHANGELOG.md - env: - GITHUB_REPO: ${{ github.repository }} - - - name: Checkout or create changelog branch - run: | - git fetch origin - git checkout -B changelog origin/changelog || git checkout -B changelog - git checkout main -- CHANGELOG.md - - - name: Commit changelog - run: | - git config user.name 'github-actions[bot]' - git config user.email 'github-actions[bot]@users.noreply.github.com' - git add CHANGELOG.md - git diff --cached --quiet && exit 0 - git commit -m "Update changelog from main" - git push origin changelog --force diff --git a/examples/basic_client/index.js b/examples/basic_client/index.js index f62745f..dbe6839 100644 --- a/examples/basic_client/index.js +++ b/examples/basic_client/index.js @@ -1,12 +1,31 @@ +const i18next = require("i18next"); const arox = require("../../dist/index"); +const backend = require("i18next-fs-backend"); +const path = require("node:path"); +const { LogLevel } = require("../../dist/utils/logger/ILogger"); + +const myinstance = i18next.createInstance({ + supportedLngs: ["en-US", "tr"], + fallbackLng: "en-US", + defaultNS: "translation", + ns: ["translation", "test"], + backend: { + loadPath: path.join(__dirname, "locales/{{lng}}/{{ns}}.json"), + }, +}); +myinstance.use(backend); const client = new arox.Client({ intents: 37376, prefix: { enabled: true, prefix: "a!" }, logger: { - depth: 0, + level: LogLevel.Trace, }, autoRegisterCommands: false, + i18n: myinstance, + interpolation: { + escapeValue: false, + }, }); arox.setClient(client); @@ -20,12 +39,12 @@ arox.clearClient(); command .onMessage(function (ctx) { - const { message } = ctx; - void message.reply("Çalışıyom ulan şurda rahat bırak beni"); + const { message, t, author } = ctx; + void message.reply(t("test:hello", { user: author.username })); }) .onInteraction(function (ctx) { - const { interaction } = ctx; - void interaction.reply("Çalışıyom ulan şurda rahat bırak beni"); + const { interaction, t, author } = ctx; + void interaction.reply(t("test:hello", { user: author.username })); }); async function init() { diff --git a/examples/basic_client/locales/en-US/test.json b/examples/basic_client/locales/en-US/test.json new file mode 100644 index 0000000..f66301f --- /dev/null +++ b/examples/basic_client/locales/en-US/test.json @@ -0,0 +1,3 @@ +{ + "hello": "Hello {{user}}" +} diff --git a/locales/en-US.json b/locales/en-US.json deleted file mode 100644 index ed96d9f..0000000 --- a/locales/en-US.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "hello": "Hello World!", - "greeting": "Hello {user}!", - "commands": { - "ping": { - "desc": "Pong!" - } - } -} diff --git a/package-lock.json b/package-lock.json index 113f494..9a17d14 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,8 @@ "@swc/helpers": "^0.5.18", "colorette": "^2.0.20", "fast-glob": "^3.3.3", + "i18next": "^25.8.0", + "i18next-fs-backend": "^2.6.1", "lodash": "^4.17.21" }, "devDependencies": { @@ -30,6 +32,15 @@ "discord.js": ">=14.25.1" } }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@borewit/text-codec": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.1.tgz", @@ -46,6 +57,7 @@ "resolved": "https://registry.npmjs.org/@discordjs/builders/-/builders-1.13.1.tgz", "integrity": "sha512-cOU0UDHc3lp/5nKByDxkmRiNZBpdp0kx55aarbiAfakfKJHlxv/yFW1zmIqCAmwH5CRlrH9iMFKJMpvW4DPB+w==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@discordjs/formatters": "^0.6.2", "@discordjs/util": "^1.2.0", @@ -67,6 +79,7 @@ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-1.5.3.tgz", "integrity": "sha512-SVb428OMd3WO1paV3rm6tSjM4wC+Kecaa1EUGX7vc6/fddvw/6lg90z4QtCqm21zvVe92vMMDt9+DkIvjXImQQ==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=16.11.0" } @@ -76,6 +89,7 @@ "resolved": "https://registry.npmjs.org/@discordjs/formatters/-/formatters-0.6.2.tgz", "integrity": "sha512-y4UPwWhH6vChKRkGdMB4odasUbHOUwy7KL+OVwF86PvT6QVOwElx+TiI1/6kcmcEe+g5YRXJFiXSXUdabqZOvQ==", "license": "Apache-2.0", + "peer": true, "dependencies": { "discord-api-types": "^0.38.33" }, @@ -91,6 +105,7 @@ "resolved": "https://registry.npmjs.org/@discordjs/rest/-/rest-2.6.0.tgz", "integrity": "sha512-RDYrhmpB7mTvmCKcpj+pc5k7POKszS4E2O9TYc+U+Y4iaCP+r910QdO43qmpOja8LRr1RJ0b3U+CqVsnPqzf4w==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@discordjs/collection": "^2.1.1", "@discordjs/util": "^1.1.1", @@ -114,6 +129,7 @@ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=18" }, @@ -126,6 +142,7 @@ "resolved": "https://registry.npmjs.org/@discordjs/util/-/util-1.2.0.tgz", "integrity": "sha512-3LKP7F2+atl9vJFhaBjn4nOaSWahZ/yWjOvA4e5pnXkt2qyXRCHLxoBQy81GFtLGCq7K9lPm9R517M1U+/90Qg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "discord-api-types": "^0.38.33" }, @@ -141,6 +158,7 @@ "resolved": "https://registry.npmjs.org/@discordjs/ws/-/ws-1.2.3.tgz", "integrity": "sha512-wPlQDxEmlDg5IxhJPuxXr3Vy9AjYq5xCvFWGJyD7w7Np8ZGu+Mc+97LCoEc/+AYCo2IDpKioiH0/c/mj5ZR9Uw==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@discordjs/collection": "^2.1.0", "@discordjs/rest": "^2.5.1", @@ -164,6 +182,7 @@ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-2.1.1.tgz", "integrity": "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=18" }, @@ -1236,6 +1255,7 @@ "resolved": "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.5.5.tgz", "integrity": "sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==", "license": "MIT", + "peer": true, "engines": { "node": ">=v14.0.0", "npm": ">=7.0.0" @@ -1246,6 +1266,7 @@ "resolved": "https://registry.npmjs.org/@sapphire/shapeshift/-/shapeshift-4.0.0.tgz", "integrity": "sha512-d9dUmWVA7MMiKobL3VpLF8P2aeanRTu6ypG2OIaEv/ZHH/SUQ2iHOVyi5wAPjQ+HmnMuL0whK9ez8I/raWbtIg==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "lodash": "^4.17.21" @@ -1259,6 +1280,7 @@ "resolved": "https://registry.npmjs.org/@sapphire/snowflake/-/snowflake-3.5.3.tgz", "integrity": "sha512-jjmJywLAFoWeBi1W7994zZyiNWPIiqRRNAmSERxyg93xRGzNYvGjlZ0gR6x0F4gPRi2+0O6S71kOZYyr3cxaIQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=v14.0.0", "npm": ">=7.0.0" @@ -1455,6 +1477,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=10" } @@ -1472,6 +1495,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=10" } @@ -1489,6 +1513,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -1506,6 +1531,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -1523,6 +1549,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -1540,6 +1567,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -1557,6 +1585,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=10" } @@ -1574,6 +1603,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=10" } @@ -1591,6 +1621,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=10" } @@ -1608,6 +1639,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=10" } @@ -1624,7 +1656,6 @@ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz", "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.8.0" } @@ -1635,6 +1666,7 @@ "integrity": "sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==", "dev": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "@swc/counter": "^0.1.3" } @@ -1746,6 +1778,7 @@ "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "license": "MIT", + "peer": true, "dependencies": { "@types/node": "*" } @@ -1755,6 +1788,7 @@ "resolved": "https://registry.npmjs.org/@vladfrangu/async_event_emitter/-/async_event_emitter-2.4.7.tgz", "integrity": "sha512-Xfe6rpCTxSxfbswi/W/Pz7zp1WWSNn4A0eW4mLkQUewCrXXtMj31lCg+iQyTkh/CkusZSq9eDflu7tjEDXUY6g==", "license": "MIT", + "peer": true, "engines": { "node": ">=v14.0.0", "npm": ">=7.0.0" @@ -2336,6 +2370,7 @@ "resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.38.37.tgz", "integrity": "sha512-Cv47jzY1jkGkh5sv0bfHYqGgKOWO1peOrGMkDFM4UmaGMOTgOW8QSexhvixa9sVOiz8MnVOBryWYyw/CEVhj7w==", "license": "MIT", + "peer": true, "workspaces": [ "scripts/actions/documentation" ] @@ -2468,7 +2503,8 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/fast-fifo": { "version": "1.3.2", @@ -2806,6 +2842,43 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/i18next": { + "version": "25.8.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.8.0.tgz", + "integrity": "sha512-urrg4HMFFMQZ2bbKRK7IZ8/CTE7D8H4JRlAwqA2ZwDRFfdd0K/4cdbNNLgfn9mo+I/h9wJu61qJzH7jCFAhUZQ==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + }, + "peerDependencies": { + "typescript": "^5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/i18next-fs-backend": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/i18next-fs-backend/-/i18next-fs-backend-2.6.1.tgz", + "integrity": "sha512-eYWTX7QT7kJ0sZyCPK6x1q+R63zvNKv2D6UdbMf15A8vNb2ZLyw4NNNZxPFwXlIv/U+oUtg8SakW6ZgJZcoqHQ==", + "license": "MIT" + }, "node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -3058,16 +3131,17 @@ } }, "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", "license": "MIT" }, "node_modules/lodash.snakecase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz", "integrity": "sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/lowercase-keys": { "version": "3.0.0", @@ -3096,7 +3170,8 @@ "version": "1.12.1", "resolved": "https://registry.npmjs.org/magic-bytes.js/-/magic-bytes.js-1.12.1.tgz", "integrity": "sha512-ThQLOhN86ZkJ7qemtVRGYM+gRgR8GEXNli9H/PMvpnZsE44Xfh3wx9kGJaldg314v85m+bFW6WBMaVHJc/c3zA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/make-fetch-happen": { "version": "15.0.3", @@ -3661,7 +3736,6 @@ "integrity": "sha512-fGYb7z/cljC0Rjtbxh7mIe8vtF/M9TShLvniwc2rdcqNG3Z9g3nM01cr2kWRb1DZdbY4/kItvIsrV4uhaMifyQ==", "dev": true, "license": "MIT", - "peer": true, "bin": { "tsgolint": "bin/tsgolint.js" }, @@ -3784,7 +3858,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -4309,9 +4382,9 @@ } }, "node_modules/tar": { - "version": "7.5.3", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.3.tgz", - "integrity": "sha512-ENg5JUHUm2rDD7IvKNFGzyElLXNjachNLp6RaGf4+JOgxXHkqA+gq81ZAMCUmtMtqBsoU62lcp6S27g1LCYGGQ==", + "version": "7.5.6", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz", + "integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { @@ -4426,7 +4499,8 @@ "version": "6.0.4", "resolved": "https://registry.npmjs.org/ts-mixer/-/ts-mixer-6.0.4.tgz", "integrity": "sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tslib": { "version": "2.8.1", @@ -4453,7 +4527,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -4492,6 +4566,7 @@ "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.3.tgz", "integrity": "sha512-gBLkYIlEnSp8pFbT64yFgGE6UIB9tAkhukC23PmMDCe5Nd+cRqKxSjw5y54MK2AZMgZfJWMaNE4nYUHgi1XEOw==", "license": "MIT", + "peer": true, "engines": { "node": ">=18.17" } @@ -4614,6 +4689,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", "integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, diff --git a/package.json b/package.json index 5f2b459..03328a3 100644 --- a/package.json +++ b/package.json @@ -38,6 +38,8 @@ "@swc/helpers": "^0.5.18", "colorette": "^2.0.20", "fast-glob": "^3.3.3", + "i18next": "^25.8.0", + "i18next-fs-backend": "^2.6.1", "lodash": "^4.17.21" }, "devDependencies": { diff --git a/src/events/interaction.ts b/src/events/interaction.ts index 0f6832b..c1aca2a 100644 --- a/src/events/interaction.ts +++ b/src/events/interaction.ts @@ -10,13 +10,17 @@ new EventBuilder(Events.InteractionCreate, false).onExecute( if (!command || !command.supportsSlash) { await interaction.reply({ content: "Command not found or disabled.", - ephemeral: true, + flags: "Ephemeral", }); return; } try { const ctx = new Context(context.client, { interaction }); + ctx.locale = interaction.locale; + context.logger.debug( + `${ctx.author?.tag ?? "Unknown"} used ${command.name}(interaction)` + ); if (command._onInteraction) await command._onInteraction(ctx.toJSON()); } catch (error) { context.client.logger.error( @@ -26,12 +30,12 @@ new EventBuilder(Events.InteractionCreate, false).onExecute( if (interaction.replied || interaction.deferred) { await interaction.followUp({ content: "There was an error while executing this command!", - ephemeral: true, + flags: "Ephemeral", }); } else { await interaction.reply({ content: "There was an error while executing this command!", - ephemeral: true, + flags: "Ephemeral", }); } } diff --git a/src/events/message.ts b/src/events/message.ts index 7b92263..7668f8f 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -1,7 +1,7 @@ import { Events } from "discord.js"; import { EventBuilder } from "../structures/Event"; import { Context } from "../structures/Context"; -import { deleteMessage } from "../utils/util"; +import { deleteMessageAfterSent } from "../utils/util"; new EventBuilder( Events.MessageCreate, @@ -31,7 +31,7 @@ new EventBuilder( content: "Command not found or disabled.", allowedMentions: { repliedUser: false }, }) - .then(deleteMessage); + .then(deleteMessageAfterSent); return; } diff --git a/src/index.ts b/src/index.ts index 1e433b9..18d2643 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,4 +5,5 @@ export * from "./structures/Event"; export * from "./structures/Argument"; export * from "./utils/logger/Logger"; export * from "./context"; + export const version = "[VI]{{version}}[/VI]"; diff --git a/src/structures/Client.ts b/src/structures/Client.ts index 51c9c77..6208e2e 100644 --- a/src/structures/Client.ts +++ b/src/structures/Client.ts @@ -12,15 +12,11 @@ import { FrameworkOptions } from "#types/client.js"; import { merge } from "lodash"; import { clearClient, setClient } from "../context"; import { getPrefix } from "../utils/util"; -import { Logger } from "../utils/logger/Logger"; -import { I18n } from "./I18n"; +import { I18nLoggerAdapter, Logger } from "../utils/logger/Logger"; +import { i18n } from "i18next"; const defaultOpts: Omit = { - paths: { - events: "events", - commands: "commands", - locales: "locales", - }, + includePaths: ["events", "commands"], autoRegisterCommands: true, }; @@ -31,7 +27,7 @@ export class Client< public commands: Collection; public aliases: Collection>; public readonly prefix: string | false; - public readonly i18n: I18n; + public i18n: i18n | undefined; declare public options: Omit & { intents: IntentsBitField; @@ -43,18 +39,17 @@ export class Client< this.commands = new Collection(); this.aliases = new Collection(); this.prefix = getPrefix(this.options.prefix ?? { enabled: false }); - this.i18n = new I18n(this.options.i18n?.defaultLocale, this.logger); - - if (this.options.paths?.locales) { - this.i18n.loadLocales( - path.join(getProjectRoot(), this.options.paths.locales) - ); + if (this.options.i18n) { + this.i18n = this.options.i18n; + this.i18n.use(new I18nLoggerAdapter(this.logger)); } - if (this.options.paths?.events) { - this.loadFiles( - path.join(getProjectRoot(), this.options.paths?.events) - ).catch((error) => this.logger.error("Error loading events:", error)); + if (this.options.includePaths) { + for (const p of this.options.includePaths) { + this.loadDir(path.join(getProjectRoot(), p)).catch((error) => + this.logger.error("Error loading events:", error) + ); + } } setClient(this); @@ -66,10 +61,16 @@ export class Client< clearClient(); } } + override async login(token?: string) { + if (this.i18n) { + await this.i18n.init(); + } + return super.login(token); + } - async loadFiles(dir: string) { + async loadDir(dir: string) { if (!require("fs").existsSync(dir)) { - this.logger.warn(`Directory not found: ${dir}`); + this.logger.debug(`Directory not found: ${dir}`); return; } const files = getFiles(dir); diff --git a/src/structures/Context.ts b/src/structures/Context.ts index 3df99db..7dc091f 100644 --- a/src/structures/Context.ts +++ b/src/structures/Context.ts @@ -1,4 +1,4 @@ -import { Message, User, ChatInputCommandInteraction } from "discord.js"; +import { Message, User, ChatInputCommandInteraction, Locale } from "discord.js"; import { Client } from "../structures/Client"; type ContextPayload = @@ -7,12 +7,14 @@ type ContextPayload = : { message: T; args?: string[] }; export class Context { - public readonly client: Client; public readonly args: string[]; public readonly data: T; + public locale: `${Locale}`; - constructor(client: Client, payload: ContextPayload) { - this.client = client; + constructor( + public readonly client: Client, + payload: ContextPayload + ) { this.args = payload.args ?? []; if ("interaction" in payload) { @@ -40,24 +42,30 @@ export class Context { return null; } - public t(key: string, args?: Record) { - let locale = this.client.i18n.defaultLocale; - - if (this.isInteraction()) { - locale = this.data.locale; + public t(key: string, args?: Record): string { + if (!this.client.i18n) { + throw new Error("i18n is not initalized"); } + let locale = + this.locale ?? + (Array.isArray(this.client.i18n.options.fallbackLng) + ? this.client.i18n.options.fallbackLng[0] + : this.client.i18n.options.fallbackLng); + + const t = this.client.i18n.getFixedT(locale); - return this.client.i18n.t(locale, key, args); + return t(key, args) as string; } public toJSON() { - const { data, args, author } = this; + const { data, args, author, t } = this; if (this.isInteraction()) { return { kind: "interaction" as const, interaction: data, author, + t: t.bind(this), }; } @@ -66,6 +74,7 @@ export class Context { message: data as Message, args, author, + t: t.bind(this), }; } } diff --git a/src/structures/I18n.ts b/src/structures/I18n.ts deleted file mode 100644 index d57dbd0..0000000 --- a/src/structures/I18n.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Logger } from "../utils/logger/Logger"; -import { getFiles } from "../utils/Files"; -import { get } from "lodash"; - -export class I18n { - public locales: Map> = new Map(); - public defaultLocale: string; - private logger: Logger; - - constructor(defaultLocale: string = "en-US", logger: Logger) { - this.defaultLocale = defaultLocale; - this.logger = logger; - } - - public async loadLocales(dir: string) { - if (!require("fs").existsSync(dir)) { - this.logger.warn(`Locales directory not found: ${dir}`); - return; - } - - const files = getFiles(dir); - - for (const file of files) { - try { - const localeName = file.split(/[\\/]/).pop()?.split(".")[0]; - if (!localeName) continue; - - delete require.cache[require.resolve(file)]; - const content = require(file); - this.locales.set(localeName, content); - this.logger.debug(`Loaded locale: ${localeName}`); - } catch (error) { - this.logger.error(`Error loading locale ${file}:`, error); - } - } - - this.logger.info(`Loaded ${this.locales.size} locales.`); - } - - public t(locale: string, key: string, args?: Record): string { - const lang = - this.locales.get(locale) || this.locales.get(this.defaultLocale); - - if (!lang) return key; - - let value = get(lang, key); - - if (!value && locale !== this.defaultLocale) { - const defaultLang = this.locales.get(this.defaultLocale); - value = get(defaultLang, key); - } - - if (!value) return key; - - if (typeof value !== "string") return value; - - if (args) { - for (const [k, v] of Object.entries(args)) { - value = value.replace(new RegExp(`{${k}}`, "g"), String(v)); - } - } - - return value; - } -} diff --git a/src/utils/logger/Logger.ts b/src/utils/logger/Logger.ts index 037f4ad..a21c640 100644 --- a/src/utils/logger/Logger.ts +++ b/src/utils/logger/Logger.ts @@ -6,6 +6,7 @@ import type { Color } from "colorette"; import { Timestamp } from "@sapphire/timestamp"; import type { ILogger } from "./ILogger"; import { LogLevel } from "./ILogger"; +import { LoggerModule } from "i18next"; export class Logger implements ILogger { public level: LogLevel; @@ -390,3 +391,19 @@ export enum LoggerStyleBackground { CyanBright = "bgCyanBright", WhiteBright = "bgWhiteBright", } + +export class I18nLoggerAdapter implements LoggerModule { + public readonly type = "logger"; + constructor(private readonly logger: Logger) {} + log(...args: unknown[]): void { + this.logger.debug("[i18next]", ...args); + } + + warn(...args: unknown[]): void { + this.logger.warn("[i18next]", ...args); + } + + error(...args: unknown[]): void { + this.logger.error("[i18next]", ...args); + } +} diff --git a/src/utils/util.ts b/src/utils/util.ts index 99b31b3..5b52c0d 100644 --- a/src/utils/util.ts +++ b/src/utils/util.ts @@ -1,7 +1,7 @@ import { PrefixOptions } from "#types/client.js"; import { InteractionResponse, Message } from "discord.js"; -export function deleteMessage( +export function deleteMessageAfterSent( message: Message | InteractionResponse, time = 15_000 ) { @@ -12,6 +12,7 @@ export function deleteMessage( }, time); }); } + export function getPrefix(opts: PrefixOptions): string | false { if (typeof opts === "string") { return opts; diff --git a/test-i18n.js b/test-i18n.js deleted file mode 100644 index 55f70ad..0000000 --- a/test-i18n.js +++ /dev/null @@ -1,17 +0,0 @@ -const { I18n } = require("./dist/structures/I18n"); -const { Logger } = require("./dist/utils/logger/Logger"); -const path = require("path"); - -const logger = new Logger(); -const i18n = new I18n("en-US", logger); - -(async () => { - console.log("Loading locales..."); - await i18n.loadLocales(path.join(__dirname, "locales")); - - console.log("Testing translations:"); - console.log("hello:", i18n.t("en-US", "hello")); - console.log("greeting:", i18n.t("en-US", "greeting", { user: "Developer" })); - console.log("nested:", i18n.t("en-US", "commands.ping.desc")); - console.log("fallback:", i18n.t("tr-TR", "hello")); // Should fallback to en-US -})(); diff --git a/types/client.d.ts b/types/client.d.ts index 578a2b1..6f49dac 100644 --- a/types/client.d.ts +++ b/types/client.d.ts @@ -1,5 +1,6 @@ import { ClientOptions } from "discord.js"; import { LoggerOptions } from "../src/utils/logger/Logger"; +import { i18n } from "i18next"; export interface FrameworkPaths { events?: string; @@ -12,14 +13,10 @@ export type PrefixOptions = | { enabled: false } | string; -export interface I18nOptions { - defaultLocale?: string; -} - export interface FrameworkOptions extends ClientOptions { logger?: LoggerOptions; prefix?: PrefixOptions; - paths?: FrameworkPaths; autoRegisterCommands?: boolean; - i18n?: I18nOptions; + includePaths: string[]; + i18n?: i18n; } From 82fa9576e65a0fe7881b4130a1c4131f93ac6cdb Mon Sep 17 00:00:00 2001 From: vrdons Date: Sun, 25 Jan 2026 12:33:23 +0300 Subject: [PATCH 4/4] update paths --- examples/basic_client/index.js | 14 ++++++---- package.json | 5 +++- src/context.ts | 2 +- src/events/interaction.ts | 11 ++++---- src/events/message.ts | 5 ++-- src/events/ready.ts | 2 +- src/index.ts | 8 ++---- src/structures/{ => builder}/Argument.ts | 10 +++++++ src/structures/{ => builder}/Command.ts | 34 +++++++++--------------- src/structures/{ => builder}/Context.ts | 19 ++++++------- src/structures/{ => builder}/Event.ts | 9 +++---- src/structures/builder/index.ts | 4 +++ src/structures/{ => core}/Client.ts | 34 +++++++++++++----------- src/structures/core/index.ts | 1 + src/structures/index.ts | 2 ++ src/utils/index.ts | 3 +++ 16 files changed, 89 insertions(+), 74 deletions(-) rename src/structures/{ => builder}/Argument.ts (72%) rename src/structures/{ => builder}/Command.ts (80%) rename src/structures/{ => builder}/Context.ts (79%) rename src/structures/{ => builder}/Event.ts (91%) create mode 100644 src/structures/builder/index.ts rename src/structures/{ => core}/Client.ts (90%) create mode 100644 src/structures/core/index.ts create mode 100644 src/structures/index.ts create mode 100644 src/utils/index.ts diff --git a/examples/basic_client/index.js b/examples/basic_client/index.js index dbe6839..4df8075 100644 --- a/examples/basic_client/index.js +++ b/examples/basic_client/index.js @@ -12,6 +12,9 @@ const myinstance = i18next.createInstance({ backend: { loadPath: path.join(__dirname, "locales/{{lng}}/{{ns}}.json"), }, + interpolation: { + escapeValue: false, + }, }); myinstance.use(backend); @@ -23,9 +26,6 @@ const client = new arox.Client({ }, autoRegisterCommands: false, i18n: myinstance, - interpolation: { - escapeValue: false, - }, }); arox.setClient(client); @@ -40,11 +40,15 @@ arox.clearClient(); command .onMessage(function (ctx) { const { message, t, author } = ctx; - void message.reply(t("test:hello", { user: author.username })); + void message.reply( + t("test:hello", { user: author?.username ?? "Unknown" }) + ); }) .onInteraction(function (ctx) { const { interaction, t, author } = ctx; - void interaction.reply(t("test:hello", { user: author.username })); + void interaction.reply( + t("test:hello", { user: author?.username ?? "Unknown" }) + ); }); async function init() { diff --git a/package.json b/package.json index 03328a3..2ea4a66 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,10 @@ "main": "dist/index.js", "types": "dist/index.d.ts", "imports": { - "#types/*": "./types/*" + "#types/*": "./types/*", + "#utils": "./src/utils/index.ts", + "#structures": "./src/structures/index.ts", + "#ctx": "./src/context.ts" }, "scripts": { "prepare": "node scripts/prepareHusky.js", diff --git a/src/context.ts b/src/context.ts index d4eaf63..3994d7b 100644 --- a/src/context.ts +++ b/src/context.ts @@ -1,4 +1,4 @@ -import { Client } from "./structures/Client"; +import { Client } from "#structures"; // Birden fazla client olursa hata çıkartabilir ama aklıma gelen tek şey bu export let currentClient: Client | null = null; diff --git a/src/events/interaction.ts b/src/events/interaction.ts index c1aca2a..9ca5710 100644 --- a/src/events/interaction.ts +++ b/src/events/interaction.ts @@ -1,6 +1,5 @@ -import { Events } from "discord.js"; -import { EventBuilder } from "../structures/Event"; -import { Context } from "../structures/Context"; +import { Events, MessageFlags } from "discord.js"; +import { EventBuilder, Context } from "#structures"; new EventBuilder(Events.InteractionCreate, false).onExecute( async function (context, interaction) { @@ -10,7 +9,7 @@ new EventBuilder(Events.InteractionCreate, false).onExecute( if (!command || !command.supportsSlash) { await interaction.reply({ content: "Command not found or disabled.", - flags: "Ephemeral", + flags: MessageFlags.Ephemeral, }); return; } @@ -30,12 +29,12 @@ new EventBuilder(Events.InteractionCreate, false).onExecute( if (interaction.replied || interaction.deferred) { await interaction.followUp({ content: "There was an error while executing this command!", - flags: "Ephemeral", + flags: MessageFlags.Ephemeral, }); } else { await interaction.reply({ content: "There was an error while executing this command!", - flags: "Ephemeral", + flags: MessageFlags.Ephemeral, }); } } diff --git a/src/events/message.ts b/src/events/message.ts index 7668f8f..39c78e2 100644 --- a/src/events/message.ts +++ b/src/events/message.ts @@ -1,7 +1,6 @@ import { Events } from "discord.js"; -import { EventBuilder } from "../structures/Event"; -import { Context } from "../structures/Context"; -import { deleteMessageAfterSent } from "../utils/util"; +import { EventBuilder, Context } from "#structures"; +import { deleteMessageAfterSent } from "#utils"; new EventBuilder( Events.MessageCreate, diff --git a/src/events/ready.ts b/src/events/ready.ts index 06317f5..f1f8504 100644 --- a/src/events/ready.ts +++ b/src/events/ready.ts @@ -1,5 +1,5 @@ import { Events } from "discord.js"; -import { EventBuilder } from "../structures/Event"; +import { EventBuilder } from "#structures"; new EventBuilder(Events.ClientReady).onExecute(async function (context) { if (context.client.options.autoRegisterCommands) { diff --git a/src/index.ts b/src/index.ts index 18d2643..4a8c0e9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,5 @@ -export * from "./structures/Client"; -export * from "./structures/Command"; -export * from "./structures/Context"; -export * from "./structures/Event"; -export * from "./structures/Argument"; +export * from "#structures"; export * from "./utils/logger/Logger"; -export * from "./context"; +export * from "#ctx"; export const version = "[VI]{{version}}[/VI]"; diff --git a/src/structures/Argument.ts b/src/structures/builder/Argument.ts similarity index 72% rename from src/structures/Argument.ts rename to src/structures/builder/Argument.ts index 991aa20..bf1c28c 100644 --- a/src/structures/Argument.ts +++ b/src/structures/builder/Argument.ts @@ -25,6 +25,16 @@ export class Argument { } public toJSON(): ApplicationCommandOptionData { + const choicesAllowedTypes = [ + ApplicationCommandOptionType.String, + ApplicationCommandOptionType.Integer, + ApplicationCommandOptionType.Number, + ]; + if (this.choices && !choicesAllowedTypes.includes(this.type)) { + throw new Error( + `Choices are not allowed for option type ${ApplicationCommandOptionType[this.type]} (${this.type})` + ); + } return { name: this.name, description: this.description, diff --git a/src/structures/Command.ts b/src/structures/builder/Command.ts similarity index 80% rename from src/structures/Command.ts rename to src/structures/builder/Command.ts index 27a42ed..cd2ca3d 100644 --- a/src/structures/Command.ts +++ b/src/structures/builder/Command.ts @@ -3,12 +3,16 @@ import { ChatInputCommandInteraction, Message, } from "discord.js"; -import { Context } from "./Context"; -import { Client } from "./Client"; +import { Context, Client } from "#structures"; import { Argument } from "./Argument"; -import { currentClient } from "../context"; +import { currentClient } from "#ctx"; import { MaybePromise } from "#types/extra.js"; -import { Logger } from "../utils/logger/Logger"; +import { Logger } from "#utils"; + +type MessageContext = NonNullable["toJSON"]>>; +type InteractionContext = NonNullable< + ReturnType["toJSON"]> +>; export interface CommandOptions { name: string; @@ -29,12 +33,8 @@ export class CommandBuilder { public readonly options: ApplicationCommandOptionData[]; private _supportsSlash: boolean; private _supportsPrefix: boolean; - public _onMessage?: ( - ctx: NonNullable["toJSON"]>> - ) => MaybePromise; - public _onInteraction?: ( - ctx: NonNullable["toJSON"]>> - ) => MaybePromise; + public _onMessage?: (ctx: MessageContext) => MaybePromise; + public _onInteraction?: (ctx: InteractionContext) => MaybePromise; public get supportsSlash() { return this._supportsSlash && this._onInteraction; @@ -99,22 +99,12 @@ export class CommandBuilder { this.logger.debug(`Loaded Command ${this.name}`); } - onMessage( - func: ( - ctx: NonNullable["toJSON"]>> - ) => MaybePromise - ) { + onMessage(func: (ctx: MessageContext) => MaybePromise) { this._onMessage = func; return this; } - onInteraction( - func: ( - ctx: NonNullable< - ReturnType["toJSON"]> - > - ) => MaybePromise - ) { + onInteraction(func: (ctx: InteractionContext) => MaybePromise) { this._onInteraction = func; return this; } diff --git a/src/structures/Context.ts b/src/structures/builder/Context.ts similarity index 79% rename from src/structures/Context.ts rename to src/structures/builder/Context.ts index 7dc091f..9dba783 100644 --- a/src/structures/Context.ts +++ b/src/structures/builder/Context.ts @@ -1,5 +1,5 @@ import { Message, User, ChatInputCommandInteraction, Locale } from "discord.js"; -import { Client } from "../structures/Client"; +import { Client } from "#structures"; type ContextPayload = T extends ChatInputCommandInteraction @@ -9,7 +9,7 @@ type ContextPayload = export class Context { public readonly args: string[]; public readonly data: T; - public locale: `${Locale}`; + public locale?: `${Locale}`; constructor( public readonly client: Client, @@ -25,11 +25,11 @@ export class Context { } public isInteraction(): this is Context { - return "user" in this.data; + return this.data instanceof ChatInputCommandInteraction; } public isMessage(): this is Context { - return "author" in this.data; + return this.data instanceof Message; } public get author(): User | null { @@ -44,13 +44,14 @@ export class Context { public t(key: string, args?: Record): string { if (!this.client.i18n) { - throw new Error("i18n is not initalized"); + throw new Error("i18n is not initialized"); } let locale = this.locale ?? (Array.isArray(this.client.i18n.options.fallbackLng) ? this.client.i18n.options.fallbackLng[0] - : this.client.i18n.options.fallbackLng); + : this.client.i18n.options.fallbackLng) ?? + "en"; const t = this.client.i18n.getFixedT(locale); @@ -58,14 +59,14 @@ export class Context { } public toJSON() { - const { data, args, author, t } = this; + const { data, args, author } = this; if (this.isInteraction()) { return { kind: "interaction" as const, interaction: data, author, - t: t.bind(this), + t: this.t.bind(this), }; } @@ -74,7 +75,7 @@ export class Context { message: data as Message, args, author, - t: t.bind(this), + t: (this as Context).t.bind(this), }; } } diff --git a/src/structures/Event.ts b/src/structures/builder/Event.ts similarity index 91% rename from src/structures/Event.ts rename to src/structures/builder/Event.ts index af5aa98..7dfd4c1 100644 --- a/src/structures/Event.ts +++ b/src/structures/builder/Event.ts @@ -1,8 +1,8 @@ import { ClientEvents } from "discord.js"; import { MaybePromise } from "#types/extra.js"; -import { currentClient } from "../context"; -import { Client } from "./Client"; -import { Logger } from "../utils/logger/Logger"; +import { currentClient } from "#ctx"; +import { Client } from "#structures"; +import { Logger } from "#utils"; type EventArgs = ClientEvents[K]; type EventHandler = ( @@ -41,8 +41,6 @@ export class EventBuilder { this.handler = _handler; this.register(); } - - this.logger.debug(`Loaded Event ${String(this.name)}`); } private register(): void { @@ -55,6 +53,7 @@ export class EventBuilder { } this.bound = true; + this.logger.debug(`Loaded Event ${String(this.name)}`); } public onExecute(func: EventHandler) { diff --git a/src/structures/builder/index.ts b/src/structures/builder/index.ts new file mode 100644 index 0000000..81fad25 --- /dev/null +++ b/src/structures/builder/index.ts @@ -0,0 +1,4 @@ +export * from "./Argument"; +export * from "./Command"; +export * from "./Context"; +export * from "./Event"; diff --git a/src/structures/Client.ts b/src/structures/core/Client.ts similarity index 90% rename from src/structures/Client.ts rename to src/structures/core/Client.ts index 6208e2e..0da8eb9 100644 --- a/src/structures/Client.ts +++ b/src/structures/core/Client.ts @@ -5,15 +5,20 @@ import { Routes, IntentsBitField, } from "discord.js"; -import { CommandBuilder } from "./Command"; +import { CommandBuilder } from "#structures"; import path from "path"; -import { getFiles, getProjectRoot } from "../utils/Files"; +import { + getFiles, + getProjectRoot, + getPrefix, + I18nLoggerAdapter, + Logger, +} from "#utils"; import { FrameworkOptions } from "#types/client.js"; import { merge } from "lodash"; -import { clearClient, setClient } from "../context"; -import { getPrefix } from "../utils/util"; -import { I18nLoggerAdapter, Logger } from "../utils/logger/Logger"; +import { clearClient, setClient } from "#ctx"; import { i18n } from "i18next"; +import { existsSync } from "fs"; const defaultOpts: Omit = { includePaths: ["events", "commands"], @@ -44,14 +49,6 @@ export class Client< this.i18n.use(new I18nLoggerAdapter(this.logger)); } - if (this.options.includePaths) { - for (const p of this.options.includePaths) { - this.loadDir(path.join(getProjectRoot(), p)).catch((error) => - this.logger.error("Error loading events:", error) - ); - } - } - setClient(this); try { require("../events/ready"); @@ -62,14 +59,21 @@ export class Client< } } override async login(token?: string) { - if (this.i18n) { + if (this.options.includePaths) { + for (const p of this.options.includePaths) { + this.loadDir(path.join(getProjectRoot(), p)).catch((error) => + this.logger.error("Error loading events:", error) + ); + } + } + if (this.i18n && !this.i18n.isInitialized) { await this.i18n.init(); } return super.login(token); } async loadDir(dir: string) { - if (!require("fs").existsSync(dir)) { + if (!existsSync(dir)) { this.logger.debug(`Directory not found: ${dir}`); return; } diff --git a/src/structures/core/index.ts b/src/structures/core/index.ts new file mode 100644 index 0000000..ccf222b --- /dev/null +++ b/src/structures/core/index.ts @@ -0,0 +1 @@ +export * from "./Client"; diff --git a/src/structures/index.ts b/src/structures/index.ts new file mode 100644 index 0000000..4c8e81f --- /dev/null +++ b/src/structures/index.ts @@ -0,0 +1,2 @@ +export * from "./core"; +export * from "./builder"; diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 0000000..a6f4449 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,3 @@ +export * from "./util"; +export * from "./Files"; +export * from "./logger/Logger";