10 minutes to read
Created by
Calytic
Updated by
Wulf

Localization

Details about basic plugin localization

This guide is for uMod, not Oxide.
Join our discord for the latest updates and the latest news! Join discord

Introduction

Plugin localization files are in the umod/lang directory. By default, a plugin localization files are named after the plugin that created them with a .json file extension.

Why use localization files?

A localization file gives server administrators a convenient way of configuring plugin messages to support different languages.

Developers should implement localization options for all messages printed by a plugin.

Lang provider

The Lang provider is a simple way for small plugins or plugins with a limited scope.

new void LoadDefaultMessages()
{
    Lang.RegisterMessages (new Dictionary<string, string>
    {
        {"ExampleKey", "Example Message"}
    });
    
    Lang.RegisterMessages (new Dictionary<string, string>
    {
        {"ExampleKey", "Exemple de message"}
    }, "fr");
}

[Command("example")]
void ExampleCommand(IPlayer player)
{
    string ExampleMessage = Lang.GetMessage("ExampleKey", player);
    player.Reply(ExampleMessage);
}

Register TOML messages

Register messages as a TOML file instead of JSON.

new void LoadDefaultMessages()
{
    Lang.RegisterToml (new Dictionary<string, string>
    {
        {"ExampleKey", "Example Message"}
    });
    
    Lang.RegisterToml (new Dictionary<string, string>
    {
        {"ExampleKey", "Exemple de message"}
    }, "fr");
}

[Command("example")]
void ExampleCommand(IPlayer player)
{
    string ExampleMessage = Lang.GetMessage("ExampleKey", player);
    player.Reply(ExampleMessage);
}

Locale schematics

The Locale provider is a memory-efficient way to scaffold and manage localization for larger plugins.

[Localization]
interface IPluginLocale : ILocale
{
    string ExampleKey { get; }
}

[Locale]
class Default : IPluginLocale
{
    public string ExampleKey => "Example Message";
}

[Locale("fr")]
class FRDefault : IPluginLocale
{
    public string ExampleKey => "Exemple de message";
}

[Command("example")]
void ExampleCommand(IPlayer player)
{
    string ExampleMessage = Locale<IPluginLocale>(player).ExampleKey;
    player.Reply(ExampleMessage);
}

TOML file

Any schematic may use TOML as an alternative file format by simply annotating the schematic with the Toml attribute.

[Localization, Toml]
interface IPluginLocale : ILocale
{
    string ExampleKey { get; }
}

Multiple locale files

Distribute localization data across multiple files by specifying a name for the locale schematic.

[Localization("separate")]
interface SeparatePluginLocale : ILocale
{
    string AnotherKey { get; }
}

The locale file storing this schematic would be named PluginName_separate.json

Upgrade path

Similarly to the configuration upgrade path, the Localization contracts support versioning and autoloading. The localization upgrade path is nearly identical to the configuration upgrade with two notable exceptions.

  1. All of the localization files (for every supported language) are upgraded all at once
  2. The upgrade hook OnLocaleUpgrade accepts the localization interface (not the concrete contracts).
void OnLocaleUpgrade(IOldLocale oldLocale, INewLocale newLocale)
{
    newLocale.Feature.Message = oldLocale.FeatureMessage;
}

Plugin language file

Both the Lang and Locale localization providers will store plugin localization data in the umod/lang folder. By default, the localization file is named after the plugin and uses the JSON data format (e.g. PluginName.json)

English default

Both uMod localization providers expect English to be the default language. Despite that, they will default to the server's configured language when available, not the plugin's.

Player language

If possible, always pass the IPlayer object that represents the message recipient into the GetMessage or Locale methods to ensure that the resulting message is in their configured language.

Get the player's language

[Command("epicstuff.language")]
void TestLanguageCommand(IPlayer player)
{
    player.Reply(Lang.GetLanguage(player));
    // Will output (by default): en
}

Set the player's language

[Command("epicstuff.french")]
void TestUpdateCommand(IPlayer player)
{
    Lang.SetLanguage(player, "fr");
    player.Reply("Merci bien! Votre langue est le français");
}

Players can also set their language by using the included umod.lang, u.lang, or lang console or chat commands along with their desired, available two-letter language code.

String formatting

Interpolation

As outlined in the official C# documentation, an interpolated string which is identified by a $ character is a string literal that may contain interpolation expressions. When resolved, the interpolation expressions are replaced by the string representations of the expressions.

string greeting = "hello";
string location = "world";

string result = $"{greeting} {location}";
// Result: "hello world"

string.Format

Another C# feature, string.Format is a commonly used method to replace placeholders in a string with arbitrary values.

string result = string.Format("{0} {1}", "hello", "world");
// Result: "hello world"

Run-time interpolation

Both of the above approaches have limitations, namely...

  1. Interpolation is a hard-coded language feature that cannot be customized by configuration.
  2. string.Format placeholders are index-based, which is non-descript and may lead to confusion.

For those reasons, uMod provides an alternative method called run-time interpolation which combines both approaches.

The run-time interpolator may use dictionary key-value-pairs and arbitrary objects.

Dictionary

string format = "{greeting} {location}";

string result = format.Interpolate(new Dictionary<string, string>
{
    ["greeting"] = "hello",
    ["location"] = "world"
});
// Result: "hello world"

Object

class MyGreeting
{
    [Placeholder("greeting")]
    public string Greeting;
    [Placeholder("location")]
    public string Location;
}

result = format.Interpolate(new MyGreeting
{
    Greeting = "hello",
    Location = "world"
});
// Result: "hello world"

Nested Object

Using the MyGreeting class from the previous example, placeholders may be nested and specified using the dot . notation.

class MyGreetingContainer
{
    [Placeholder("container")]
    MyGreeting Greetings = new MyGreeting() {
        Greeting = "hello",
        Location = "world"
    };
}

string format = "{container.greeting} {container.location}";

result = format.Interpolate(new MyGreetingContainer());
// Result: "hello world"

Formatting

Formatting may be customized using format providers.

string format = "{greeting} {location}, it is {date|hh:ss tt}";

string result = format.Interpolate(new Dictionary<string, string>
{
    ["greeting"] = "hello",
    ["location"] = "world",
    ["date"] = DateTime.Now
});

// Result: "hello world, it is 2:36 PM"

Pluralization

Define multiple alternate formats separated by the | pipe operator to distinguish between singular and plural forms.

string format = "banana|bananas";
result = format.Choice(1);
// Result: "banana"
result = format.Choice(10);
// Result: "bananas"

Specify three forms to distinguish been zero, one, and many.

string format = "There are no bananas|There is one banana|[1,*]There are multiple bananas";
string result = format.Choice(0);
// Result: "There are no bananas"
result = format.Choice(1);
// Result: "There is one banana"
result = format.Choice(10);
// Result: "There are multiple bananas"

Pluralization is also available via the run-time interpolatorInterpolate method where the choiceAmount is specified as the last parameter.

string format = "There is one {color} banana|There are multiple {color} bananas";

Dictionary<string, string> placeholders = new Dictionary<string, string>
{
    ["color"] = "yellow"
};

string result = format.Interpolate(placeholders, 1);
// Result: "There is one yellow banana"
result = format.Interpolate(placeholders, 10);
// Result: "There are multiple yellow bananas"

Language codes

uMod uses ISO-639-1 language codes to specify which language is being localized. For example...

  en - English
  fr - French
  de - German
  pl - Polish
  ru - Russian
  es - Spanish