From 05d776937c739a6c02f388686eb12c1bab08a21b Mon Sep 17 00:00:00 2001 From: mustafakyia Date: Mon, 19 Jan 2026 21:51:42 +0300 Subject: [PATCH 1/2] 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/2] 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 })();