From e0514dcbe07acd854704c32c01db4cea4264a3b9 Mon Sep 17 00:00:00 2001 From: Zack Campbell Date: Mon, 12 Jun 2017 01:59:27 -0500 Subject: [PATCH] Add Lang singleton, update help command to use localized command info --- src/client/Client.ts | 12 ++++- src/command/Command.ts | 2 - src/command/CommandLoader.ts | 16 ------ src/command/base/Help.ts | 17 +++++-- src/localization/Lang.ts | 84 +++++++++++++++++++++++++++++++ src/types/YAMDBFOptions.ts | 2 + test/commands/base/eval.lang.json | 5 ++ test/commands/base/help.lang.json | 6 +++ test/test_client.ts | 3 +- 9 files changed, 123 insertions(+), 24 deletions(-) create mode 100644 src/localization/Lang.ts create mode 100644 test/commands/base/eval.lang.json create mode 100644 test/commands/base/help.lang.json diff --git a/src/client/Client.ts b/src/client/Client.ts index 4a8b7ce1..eb12884a 100644 --- a/src/client/Client.ts +++ b/src/client/Client.ts @@ -33,6 +33,7 @@ import { StorageProviderConstructor } from '../types/StorageProviderConstructor' import { BaseCommandName } from '../types/BaseCommandName'; import { Logger, logger } from '../util/logger/Logger'; import { ListenerUtil } from '../util/ListenerUtil'; +import { Lang } from '../localization/Lang'; const { on, once, registerListeners } = ListenerUtil; @@ -49,6 +50,7 @@ export class Client extends Discord.Client public readonly name: string; public readonly commandsDir: string; public readonly owner: string | string[]; + public readonly defaultLang: string; public readonly statusText: string; public readonly readyText: string; public readonly unknownCommandError: boolean; @@ -102,6 +104,12 @@ export class Client extends Discord.Client */ this.commandsDir = path.resolve(options.commandsDir) || null; + /** + * Default language to use for localization + * @type {string} + */ + this.defaultLang = options.defaultLang || 'en_us'; + /** * Status text for the client * @type {string} @@ -212,11 +220,13 @@ export class Client extends Discord.Client if (!this.commandsDir && !this.passive) throw new Error('A directory from which to load commands must be provided via commandsDir'); if (!(this.owner instanceof Array)) throw new TypeError('Client config `owner` field must be an array of user ID strings.'); + Lang.createInstance(this); + // Load commands if (!this.passive) { this.loadCommand('all'); - this._commandLoader.loadLocalizations(); + Lang.loadCommandLocalizations(); } registerListeners(this); diff --git a/src/command/Command.ts b/src/command/Command.ts index e466bb01..c453e8f3 100644 --- a/src/command/Command.ts +++ b/src/command/Command.ts @@ -1,7 +1,6 @@ import { PermissionResolvable, Permissions, Message } from 'discord.js'; import { Client } from '../client/Client'; import { MiddlewareFunction } from '../types/MiddlewareFunction'; -import { LocalizedCommandInfo } from '../types/LocalizedCommandInfo'; import { CommandInfo } from '../types/CommandInfo'; import { RateLimiter } from './RateLimiter'; import { ArgOpts } from '../types/ArgOpts'; @@ -30,7 +29,6 @@ export class Command public overloads: string; public _classloc: string; - public translation?: { [name: string]: LocalizedCommandInfo }; public readonly _rateLimiter: RateLimiter; public readonly _middleware: MiddlewareFunction[]; diff --git a/src/command/CommandLoader.ts b/src/command/CommandLoader.ts index 4898c0b8..930ab22c 100644 --- a/src/command/CommandLoader.ts +++ b/src/command/CommandLoader.ts @@ -81,22 +81,6 @@ export class CommandLoader return true; } - /** - * Load any command localizations and assign them to commands - */ - public loadLocalizations(): void - { - for (const command of this._client.commands.values()) - { - let localizationFile: string = glob.sync(`${this._client.commandsDir}/**/${command.name}.lang.json`)[0]; - if (!localizationFile) continue; - let localizations: { [name: string]: LocalizedCommandInfo }; - try { localizations = require(localizationFile); } - catch (err) { continue; } - command.translation = localizations; - } - } - /** * Get the Command class from an attempted Command class import */ diff --git a/src/command/base/Help.ts b/src/command/base/Help.ts index 1f3f7063..32f9a7f9 100644 --- a/src/command/base/Help.ts +++ b/src/command/base/Help.ts @@ -1,6 +1,8 @@ +import { LocalizedCommandInfo } from '../../types/LocalizedCommandInfo'; import { Message } from '../../types/Message'; import { Util } from '../../util/Util'; import { Command } from '../Command'; +import { Lang } from '../../localization/Lang'; import { Collection, RichEmbed } from 'discord.js'; export default class extends Command @@ -20,6 +22,11 @@ export default class extends Command if (this.client.selfbot) message.delete(); const dm: boolean = message.channel.type !== 'text'; const mentionName: string = `@${this.client.user.tag}`; + const lang: string = dm ? this.client.defaultLang + : await message.guild.storage.settings.get('lang'); + + const cInfo: (command: Command) => LocalizedCommandInfo = + (command: Command) => Lang.getCommandInfo(command, lang); let command: Command; let output: string = ''; @@ -37,7 +44,7 @@ export default class extends Command const widest: number = usableCommands.map(c => c.name.length).reduce((a, b) => Math.max(a, b)); let commandList: string = usableCommands.map(c => - `${Util.padRight(c.name, widest + 1)}${c.guildOnly ? '*' : ' '}: ${c.desc}`).sort().join('\n'); + `${Util.padRight(c.name, widest + 1)}${c.guildOnly ? '*' : ' '}: ${cInfo(c).desc}`).sort().join('\n'); output = preText + commandList + postText; if (output.length >= 1024) @@ -63,14 +70,16 @@ export default class extends Command if (!command) output = `A command by that name could not be found or you do\n` + `not have permission to view it.`; - else output = '```ldif\n' + + const info: LocalizedCommandInfo = cInfo(command); + if (command) output = '```ldif\n' + (command.guildOnly ? '[Server Only]\n' : '') + (command.ownerOnly ? '[Owner Only]\n' : '') + `Command: ${command.name}\n` - + `Description: ${command.desc}\n` + + `Description: ${info.desc}\n` + (command.aliases.length > 0 ? `Aliases: ${command.aliases.join(', ')}\n` : '') + `Usage: ${command.usage}\n` - + (command.info ? `\n${command.info}` : '') + + (info.info ? `\n${info.info}` : '') + '\n```'; } diff --git a/src/localization/Lang.ts b/src/localization/Lang.ts new file mode 100644 index 00000000..030df5ae --- /dev/null +++ b/src/localization/Lang.ts @@ -0,0 +1,84 @@ +import { LocalizedCommandInfo } from '../types/LocalizedCommandInfo'; +import { Command } from '../command/Command'; +import { Client } from '../client/Client'; +import * as glob from 'glob'; + +/** + * Class for loading localization files and fetching localized values + */ +export class Lang +{ + private static _instance: Lang; + private client: Client; + private commandInfo: { [command: string]: { [lang: string]: LocalizedCommandInfo } }; + private constructor(client: Client) + { + if (Lang._instance) + throw new Error('Cannot create multiple instances of Logger singleton'); + + this.client = client; + this.commandInfo = {}; + } + + /** + * Create the singleton instance + */ + public static createInstance(client: Client): void + { + if (!Lang._instance) Lang._instance = new Lang(client); + } + + /** + * Load any command localizations and assign them to commands + */ + public static loadCommandLocalizations(): void + { + if (!Lang._instance) return; + + for (const command of Lang._instance.client.commands.values()) + { + let localizationFile: string = + glob.sync(`${Lang._instance.client.commandsDir}/**/${command.name}.lang.json`)[0]; + if (!localizationFile) continue; + let localizations: { [name: string]: LocalizedCommandInfo }; + try { localizations = require(localizationFile); } + catch (err) { continue; } + Lang._instance.commandInfo[command.name] = localizations; + } + } + + /** + * Get all available localization languages + * TODO: Extend this to scan language string files after + * adding support for those + */ + public static getLangs(): string[] + { + let langs: Set = new Set(); + for (const commandName of Object.keys(Lang._instance.commandInfo)) + for (const lang of Object.keys(Lang._instance.commandInfo[commandName])) + langs.add(lang); + + return Array.from(langs); + } + + /** + * Get localized command info, defaulting to the info + * given in the Command's constructor + */ + public static getCommandInfo(command: Command, lang: string): LocalizedCommandInfo + { + let desc: string, info: string; + if (!Lang._instance.commandInfo[command.name] + || (Lang._instance.commandInfo[command.name] + && !Lang._instance.commandInfo[command.name][lang])) + return { desc, info } = command; + + desc = Lang._instance.commandInfo[command.name][lang].desc; + info = Lang._instance.commandInfo[command.name][lang].info; + if (!desc) desc = command.desc; + if (!info) info = command.info; + + return { desc, info }; + } +} diff --git a/src/types/YAMDBFOptions.ts b/src/types/YAMDBFOptions.ts index b428857a..11c57601 100644 --- a/src/types/YAMDBFOptions.ts +++ b/src/types/YAMDBFOptions.ts @@ -6,6 +6,7 @@ * @property {string[]} [owner=[]] Can also be a single string
See: {@link Client#owner} * @property {string} [provider] See: {@link Client#provider} * @property {string} [commandsDir] See: {@link Client#commandsDir} + * @property {string} [defaultLang] See: {@link Client#defaultLang} * @property {string} [statusText=null] See: {@link Client#statusText} * @property {string} [readyText='Client ready!'] See: {@link Client#readyText} * @property {boolean} [unknownCommandError=true] See: {@link Client#unknownCommandError} @@ -28,6 +29,7 @@ export type YAMDBFOptions = { owner?: string | string[]; provider?: StorageProviderConstructor; commandsDir?: string; + defaultLang?: string; statusText?: string; readyText?: string; unknownCommandError?: boolean; diff --git a/test/commands/base/eval.lang.json b/test/commands/base/eval.lang.json new file mode 100644 index 00000000..52fcf4b1 --- /dev/null +++ b/test/commands/base/eval.lang.json @@ -0,0 +1,5 @@ +{ + "al_bhed": { + "desc": "Ajymiyda bnujetat Javascript luta" + } +} \ No newline at end of file diff --git a/test/commands/base/help.lang.json b/test/commands/base/help.lang.json new file mode 100644 index 00000000..b07eeb63 --- /dev/null +++ b/test/commands/base/help.lang.json @@ -0,0 +1,6 @@ +{ + "al_bhed": { + "desc": "Bnujetac ehvunsydeuh uh pud lussyhtc", + "info": "Femm DM pud lussyht ramb ehvunsydeuh du dra ican du gaab lmiddan tufh eh kiemt lryhhamc" + } +} \ No newline at end of file diff --git a/test/test_client.ts b/test/test_client.ts index 4055026f..8a1a2dc8 100644 --- a/test/test_client.ts +++ b/test/test_client.ts @@ -29,10 +29,11 @@ class Test extends Client token: config.token, owner: config.owner, commandsDir: './commands', + defaultLang: 'al_bhed', pause: true, logLevel: LogLevel.DEBUG, disableBase: Util.baseCommandNames - .filter(n => n !== 'help') + .filter(n => n !== 'help' && n !== 'eval') }); }