Localization

Providing new localizations for the strings your YAMDBF Client sends to Discord is pretty simple overall but it would be inaccurate to say there's not a lot of work that will go into it. There are a lot of strings throughout all of the framework functionality and default commands that get sent to Discord so there is potentially a lot of translating to be done if you wish to provide localizations for any additional languages.

.lang files

The first thing to cover is the file format. .lang is a simple data file format created specifically for the purpose of providing strings for localization throughout the framework. Here are a couple simple examples:

[EXAMPLE_FOO]
Simple single-line string

[EXAMPLE_BAR]
This is a multi-line string.
    Whitespace, like the tab at the beginning of this line
  or the two spaces at the beginning of this line are preserved.

Newlines are preserved as well, though you can \nadd newlines mid-line
as seen in the line above this, here -----------^

There should not be anything directly above the key (the text in braces) or it will cause the following key/value set to be parsed as part of the previous set. The text present in the braces for key/value set is the key that will be used when retrieving the string via Lang.res() or a ResourceLoader from Lang.createResourceLoader().

Single-line and inline comments are also allowed, using the syntax ##:

[EXAMPLE_BAZ]
## This is an example of a single-line comment
## Here's another

foo bar baz ## And here's an inline comment

Comments are removed when parsing. Comments between key/value sets that do not end up captured as a part of of a key/value set will not be captured at all.

Templating

Unrelated to the .lang file format, but arguably the most important part of the localization system as a whole, is the templating system. Any token surrounded with {{ }} or {{ ?}} is a template, and can be replaced at runtime via matching keys within a TemplateData object passed to Lang.res() or a ResourceLoader from Lang.createResourceLoader(). For example:

[TEMPLATE_EXAMPLE]
foo {{ barTemplate }} baz

// When in use:
Lang.res('en_us', 'TEMPLATE_EXAMPLE', { barTemplate: 'bar' }) // Produces: 'foo bar baz'

Templates with a question mark ({{ foo ?}}) are "maybe templates". If a value is not given for that template it will be removed when loaded. If a maybe template is the only thing occupying a line and no value is given for it, it will be removed and a blank line will not be left behind:

[MAYBE_TEMPLATE_EXAMPLE]
foo{{ barTemplate ?}}baz
{{ emptyTemplate ?}}
boo

// When in use:
Lang.res('en_us', 'MAYBE_TEMPLATE_EXAMPLE') // Produces: 'foobaz\nboo'

Note: Passing empty strings for maybe templates results in them being removed in the same manner as if no value was given

Template scripting

I don't speak any other languages myself but I'm aware that translating between languages is hardly ever a static 1:1 thing. Pluralization, for example, is something that would hardly be able to be represented via simple static translation for all languages. This is where template scripting becomes useful. Template scripting is done within {{! !}} template braces. (Note the !s). This template syntax is important as the script won't be able to be interpreted if not within the proper template braces.

Using the topic of pluralization from earlier, an example can be given from a template script currently in place for one of the strings for the default help Command:

[CMD_HELP_ALIASES]
## 'Aliases: foo, bar' | 'Alias: foo'
{{! args.aliases.split(',').length > 1 ? 'Aliases' : 'Alias' !}}: {{ aliases }}

Note: A template script itself is just interpreted Javascript that must return a value. A template script that does not return a value will simply have the template removed from the output string in the same manner as maybe templates. Implicit returns are possible in a manner similar to arrow functions. If the script is complex enough to necessitate an actual return statement then an implicit return is not possible

Template scripts receive two parameter values within their scope at runtime, the first of which is the TemplateData object passed to the resource loader that is loading the string resource containing the template script in question. This parameter is called args. The second parameter, called res, is a ResourceLoader function that can be used for loading other string resources for the same language in case you need to nest localizable strings.

The same args object received in a template script is automatically forwarded by reference to res() within that template script when called (meaning new values can be attached and old values modified), so unless a new args object is created to pass to res() you will not need to pass more than a string key to nested res() calls within template scripts

It's easiest to imagine that the script itself is surrounded by an anonymous function declaration taking the parameters args, and res with simple arrow-function style implicit returns converted explicitly. Using the example script from above:

// {{! args.aliases.split(',').length > 1 ? 'Aliases' : 'Alias' !}}
function(args, res) {
    return args.aliases.split(',').length > 1 ? 'Aliases' : 'Alias';
}

When working with template scripts, and really templates in general, knowing what is going to be passed to the localization string and thus the template script is important. Be sure to pay close attention to the original defaults and their use of templates to get an idea of what to expect. And, of course, being sure to test the results at runtime never hurts.