Skip to content

Commit

Permalink
Add custom .lang file type parsing, replace .json for localization files
Browse files Browse the repository at this point in the history
  • Loading branch information
zajrik committed Jun 16, 2017
1 parent 8d51703 commit 05a51d4
Show file tree
Hide file tree
Showing 11 changed files with 157 additions and 36 deletions.
3 changes: 3 additions & 0 deletions gulpfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ gulp.task('build', () => {
gulp.src('./src/**/*.json')
.pipe(gulp.dest('bin/'));

gulp.src('./src/**/*.lang')
.pipe(gulp.dest('bin/'));

return tsCompile.js
.pipe(gulp_sourcemaps.write({
sourceRoot: file => path.relative(path.join(file.cwd, file.path), file.base)
Expand Down
6 changes: 3 additions & 3 deletions src/command/base/Help.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ export default class extends Command
const info: LocalizedCommandInfo = cInfo(command);
const res: LangResourceFunction = Lang.createResourceLoader(lang);
if (command) output = res('CMD_HELP_CODEBLOCK', {
serverOnly: command.guildOnly ? `${res('CMD_HELP_SERVERONLY')}\n` : '',
ownerOnly: command.ownerOnly ? `${res('CMD_HELP_OWNERONLY')}\n` : '',
serverOnly: command.guildOnly ? res('CMD_HELP_SERVERONLY') : '',
ownerOnly: command.ownerOnly ? res('CMD_HELP_OWNERONLY') : '',
commandName: command.name,
desc: info.desc,
aliasText: command.aliases.length > 0 ?
`${res('CMD_HELP_ALIASES', { aliases: command.aliases.join(', ')})}\n`
res('CMD_HELP_ALIASES', { aliases: command.aliases.join(', ')})
: '',
usage: info.usage,
info: info.info ? `\n${info.info}` : ''
Expand Down
57 changes: 39 additions & 18 deletions src/localization/Lang.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import * as fs from 'fs';
import { LangFileParser } from './LangFileParser';
import { LangResourceFunction } from '../types/LangResourceFunction';
import { LocalizedCommandInfo } from '../types/LocalizedCommandInfo';
import { TokenReplaceData } from '../types/TokenReplaceData';
import { logger, Logger } from '../util/logger/Logger';
import { Command } from '../command/Command';
import { Client } from '../client/Client';
import { Language } from './Language';
import * as glob from 'glob';
import * as path from 'path';

Expand All @@ -16,7 +19,7 @@ export class Lang
private static _instance: Lang;
private client: Client;
private commandInfo: { [command: string]: { [lang: string]: LocalizedCommandInfo } };
private langs: { [lang: string]: { [key: string]: string } };
private langs: { [lang: string]: Language };
private constructor(client: Client)
{
if (Lang._instance)
Expand All @@ -32,7 +35,7 @@ export class Lang
* This does not include localized helptext
* @type {object}
*/
public static get langs(): { [lang: string]: { [key: string]: string } }
public static get langs(): { [lang: string]: Language }
{
return Lang._instance.langs;
}
Expand Down Expand Up @@ -70,24 +73,35 @@ export class Lang
public static loadLocalizations(): void
{
if (!Lang._instance) return;
const langNameRegex: RegExp = /\/([^\/\.]+)(?:\..+)?\.lang\.json/;
const langNameRegex: RegExp = /\/([^\/\.]+)(?:\..+)?\.lang/;

let langFiles: string[] = [];
langFiles.push(...glob.sync(`${path.join(__dirname, './en_us')}/**/*.lang.json`));
let langs: { [key: string]: string[] } = {};
let allLangFiles: string[] = [];
allLangFiles.push(...glob.sync(`${path.join(__dirname, './en_us')}/**/*.lang`));
if (Lang._instance.client.localeDir)
langFiles.push(...glob.sync(`${Lang._instance.client.localeDir}/**/*.lang.json`));
allLangFiles.push(...glob.sync(`${Lang._instance.client.localeDir}/**/*.lang`));

for (const langFile of langFiles)
for (const langFile of allLangFiles)
{
delete require.cache[require.resolve(langFile)];
const loadedLangFile: { [key: string]: string } = require(langFile);

if (!langNameRegex.test(langFile)) continue;
const langName: string = langFile.match(langNameRegex)[1];
if (typeof Lang._instance.langs[langName] !== 'undefined')
Lang._instance.langs[langName] = { ...Lang._instance.langs[langName], ...loadedLangFile };
else
Lang._instance.langs[langName] = loadedLangFile;
if (!langs[langName]) langs[langName] = [];
langs[langName].push(langFile);
}

for (const langName of Object.keys(langs))
{
for (const langFile of langs[langName])
{
if (!langNameRegex.test(langFile)) continue;
const loadedLangFile: string = fs.readFileSync(langFile).toString();
const parsedLanguageFile: Language = LangFileParser.parseFile(langName, loadedLangFile);

if (typeof Lang._instance.langs[langName] !== 'undefined')
Lang._instance.langs[langName].concat(parsedLanguageFile);
else
Lang._instance.langs[langName] = parsedLanguageFile;
}
}

Lang.logger.info('Lang', `Loaded string localizations for ${Object.keys(Lang.langs).length} languages`);
Expand Down Expand Up @@ -140,7 +154,7 @@ export class Lang

/**
* Get a string resource for the given language, replacing any
* tokens with the given data
* template tokens with the given data
* @param {string} lang Language to get a string resource for
* @param {string} key String key to get
* @param {TokenReplaceData} [data] Values to replace in the string
Expand All @@ -149,16 +163,23 @@ export class Lang
public static res(lang: string, key: string, data?: TokenReplaceData): string
{
if (!Lang.langs[lang]) return key;
const strings: { [key: string]: string } = Lang.langs[lang];
const maybeTemplates: RegExp = /^{{ *[a-zA-Z]+ *\?}}[\t ]*\n|{{ *[a-zA-Z]+ *\?}}/gm;
const strings: { [key: string]: string } = Lang.langs[lang].strings;
let loadedString: string = strings[key];

if (!loadedString) return key;
if (typeof data === 'undefined') return loadedString;

for (const token of Object.keys(data))
loadedString = loadedString.replace(new RegExp(`{{ *${token} *}}`, 'g'), data[token]);
{
// Skip maybe templates so they can be removed properly later
if (new RegExp(`{{ *${token} *\\?}}`, 'g').test(loadedString)
&& (data[token] === '' || data[token] === undefined)) continue;

loadedString = loadedString.replace(new RegExp(`{{ *${token} *\\??}}`, 'g'), data[token]);
}

return loadedString;
return loadedString.replace(maybeTemplates, '');
}

/**
Expand Down
38 changes: 38 additions & 0 deletions src/localization/LangFileParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Language } from './Language';

/**
* Class for parsing `.lang` files
* @private
*/
export class LangFileParser
{
private static parseBlock: RegExp = /(\[([a-zA-Z0-9_]+)\]([^]+)\[\/\2\])/;
private static parseBlocks: RegExp = new RegExp(LangFileParser.parseBlock, 'g');
private static stripComments: RegExp = /^(?!$)\s*##.*\n|##.*$/gm;
private static trimNewlines: RegExp = /^\n|\n$/g;

/**
* Parse a given language file string and return a Language
* object containing all the parsed values
*/
public static parseFile(langName: string, langFile: string): Language
{
const lang: Language = new Language(langName);
const blocks: string[] = langFile.match(LangFileParser.parseBlocks);
for (const block of blocks)
{
const match: RegExpMatchArray = block.match(LangFileParser.parseBlock);
const raw: string = match[1].replace(/\r\n/g, '\n');
const key: string = match[2];
const value: string = match[3]
.replace(/\r\n/g, '\n')
.replace(LangFileParser.stripComments, '')
.replace(LangFileParser.trimNewlines, '')
.trim();

lang.strings[key] = value;
lang.raw[key] = raw;
}
return lang;
}
}
29 changes: 29 additions & 0 deletions src/localization/Language.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Holds the localization strings for a given language
* @private
*/
export class Language
{
public lang: string;
public strings: { [key: string]: string };
public raw: { [key: string]: string };
public constructor(lang: string)
{
this.lang = lang;
this.strings = {};
this.raw = {};
}

/**
* Concatenate another Language object's strings of the
* same language with this Language object's strings,
* saving them to this Language object's `strings` value
*/
public concat(lang: Language): void
{
if (lang.lang !== this.lang)
throw new Error('Cannot concatenate strings for different languages.');
this.strings = { ...this.strings, ...lang.strings };
this.raw = { ...this.raw, ...lang.raw };
}
}
19 changes: 19 additions & 0 deletions src/localization/en_us/en_us.cmd.help.lang
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@

[CMD_HELP_SERVERONLY] [Server Only] [/CMD_HELP_SERVERONLY]
[CMD_HELP_OWNERONLY] [Owner Only] [/CMD_HELP_OWNERONLY]
[CMD_HELP_ALIASES] Aliases: {{ aliases }} [/CMD_HELP_ALIASES]

[CMD_HELP_CODEBLOCK]
## I feel ldif is the best codeblock language for
## displaying all of the help commands but it could
## be changed without any consequence if desired
```ldif
{{ serverOnly ?}}
{{ ownerOnly ?}}
Command: {{ commandName }}
Description: {{ desc }}
{{ aliasText ?}}
Usage: {{ usage }}
{{ info ?}}
```
[/CMD_HELP_CODEBLOCK]
6 changes: 0 additions & 6 deletions src/localization/en_us/en_us.cmd.help.lang.json

This file was deleted.

4 changes: 2 additions & 2 deletions test/commands/test_command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default class extends Command
{
message.channel.send(args.join(' ') || 'MISSING ARGS');
this.logger.debug('Command:test', util.inspect(this.group));
this.logger.debug('Command:test', this.translation.al_bhed.desc);
this.logger.debug('Command:test', this.translation.al_bhed.info);
// this.logger.debug('Command:test', this.translation.al_bhed.desc);
// this.logger.debug('Command:test', this.translation.al_bhed.info);
}
}
22 changes: 22 additions & 0 deletions test/locale/al_bhed/al_bhed.cmd.help.lang
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"CMD_HELP_SERVERONLY": "[Canjan Uhmo]",
"CMD_HELP_OWNERONLY": "[Ufhan Uhmo]",
"CMD_HELP_ALIASES": "Ymeycac: {{ aliases }}",
"CMD_HELP_CODEBLOCK": "```ldif\n{{serverOnly}}{{ownerOnly}}Lussyht: {{commandName}}\nTaclnebdeuh: {{desc}}\n{{aliasText}}Icyka: {{usage}}\n{{info}}\n```"
}

[CMD_HELP_SERVERONLY] [Canjan Uhmo] [/CMD_HELP_SERVERONLY]
[CMD_HELP_OWNERONLY] [Ufhan Uhmo] [/CMD_HELP_OWNERONLY]
[CMD_HELP_ALIASES] Ymeycac: {{ aliases }} [/CMD_HELP_ALIASES]

[CMD_HELP_CODEBLOCK]
```ldif
{{ serverOnly ?}}
{{ ownerOnly ?}}
Lussyht: {{ commandName }}
nTaclnebdeuh: {{ desc }}
{{ aliasText ?}}
Icyka: {{ usage }}
{{ info ?}}
```
[/CMD_HELP_CODEBLOCK]
6 changes: 0 additions & 6 deletions test/locale/al_bhed/al_bhed.cmd.help.lang.json

This file was deleted.

3 changes: 2 additions & 1 deletion test/test_client.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Lang } from '../bin';
import { Client, LogLevel, Logger, ListenerUtil, Util } from '../bin/';
const config: any = require('./config.json');
const logger: Logger = Logger.instance();
Expand Down Expand Up @@ -30,7 +31,7 @@ class Test extends Client
owner: config.owner,
commandsDir: './commands',
localeDir: './locale',
defaultLang: 'al_bhed',
// defaultLang: 'al_bhed',
pause: true,
logLevel: LogLevel.DEBUG,
disableBase: Util.baseCommandNames
Expand Down

0 comments on commit 05a51d4

Please sign in to comment.