Localization
EvoSC# provides a general system for localizing your modules. It leverages the ResourceManager to provide an easy-to-use but powerful interface for all your localization needs.
TIP
You can find the project for translating EvoSC# core and internal modules here: https://crowdin.com/project/evosc-sharp
We welcome any contributions!
Getting Started
To get started with localizing your module, begin by creating a new file in the root namespace of your module called Localization.resx
. The naming of this file is important as EvoSC# will look for this particular resource containing locales.
INFO
Your IDE might be able to create this file and fill the contents automatically, but be aware that it may also generate a .Design.cs file, which we will not be using.
In the file, paste the following contents:
<?xml version="1.0" encoding="utf-8"?>
<root>
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:element name="root" msdata:IsDataSet="true">
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>1.3</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<!-- Localization data goes here -->
</root>
We are now ready to begin adding data to this resource file. Your IDE might have an editor for this, which makes this process easier.
At the bottom, before the </root>
tag, we can begin adding data tags. For example:
<data name="GreetPlayer" xml:space="preserve">
<value>Hello {0}!</value>
</data>
The name
attribute denotes the name of the locale key you wish to use, and inside the <data>
tag, write the value within two <value>
tags.
You have now successfully created your first localization file. This will be your default localization and the fallback for any other language that doesn't exist. We will get into how to add different languages later.
Note the {0}
in the value. This will be replaced by an argument passed to the locale string. In this case, it is the first argument (position 0). You can add as many arguments as you want.
This supports all formatting from the string.Format
method.
Using Localization
Most of the time, localization will be used for chat messages and Manialinks. We can begin with an example of using localization for a chat command that responds with a message to the chat.
using EvoSC.Commands;
using EvoSC.Commands.Attributes;
using EvoSC.Common.Controllers;
using EvoSC.Common.Controllers.Attributes;
using EvoSC.Manialinks.Interfaces;
namespace EvoSC.Modules.Official.ExampleModule;
[Controller]
public class ExampleController2 : EvoScController<CommandInteractionContext>
{
private readonly IServerClient _server;
private readonly dynamic _locale;
public ExampleController2(IServerClient server, Locale locale)
{
_server = server;
_locale = locale;
}
[ChatCommand("greet", "Greet the player with localization.")]
public async Task GreetPlayerAsync() =>
_server.SendChatMessageAsync(_locale.GreetPlayer(Context.Player.NickName));
}
When a player sends the command /greet
to the chat, the server will respond with whatever the current language that is set for EvoSC#. In this case, if we use the localization file above, we only have one language, so the message should be somthing similar to: Hello PlayerName!
Note the way we are setting up the locale object and refering to our string. Here we are using DynamicObject referencing, and to pass arguments to the locale, we call it like a method with the arguments to pass. If you didn't have any arguments to pass, you would omit the method call syntax and just type _locale.GreetPlayer
.
There are other ways to refer to locales which we will go over later.
Manialinks
To use localization in Manialinks, we need to pass the Locale
instance as a variable to it.
For example:
<component>
<using namespace="EvoSC.Common.Interfaces.Localization" />
<property type="dynamic" name="Locale" />
<template>
<label text="{{ Locale.MyLocaleName }}}" textsize="2" />
</template>
</component>
We must then pass the "Locale" variable when sending the manialink:
_manialinks.SendManialinkAsync("MyManialink", new {
Locale = _locale
});
Referencing Localization Strings
Currently, there are two common ways to do this. Either through DynamicObject or an indexer.
DynamicObject
This is the recommended method and is compatible with XML attributes, which are often used in Manialinks.
To use DynamicObject referencing, you first have to cast the Locale
instance to dynamic
. With dependency injection, this can be done by injecting Locale
and setting it to a dynamic variable in the class. For example:
public class MyClass {
// use the "dynamic" type here
private readonly dynamic _locale;
public ExampleController2(Locale locale)
{
_locale = locale;
}
}
Referencing without Arguments
To obtain strings from a locale with no arguments, you can simple type the name of the locale like this:
var myString = _locale.MyLocaleName;
Referencing with Arguments
For arguments, we call the locale name like a method, passing the needed arguments:
var myString = _locale.MyLocaleName(arg1, arg2, ...);
Indexer
If you're not using DynamicObject, you can reference the locales using the index pattern.
For example, lets say we have the class:
public class MyClass {
// use the "Locale" type here
private readonly Locale _locale;
public ExampleController2(Locale locale)
{
_locale = locale;
}
}
Referencing without Arguments
We can now access our locale like this if we don't have any arguments to pass:
var myString = _locale["MyLocaleName"];
Referencing with Arguments
If we have arguments, simply add arguments to the indexer:
var myString = _locale["MyLocaleName", arg1, arg2, ...];
Adding Multilingual Support
The process for adding languages is almost the same as creating the default localization file. But the difference is that you specify the define language as the file extension.
For example for German, the file would be called Localization.de.resx
, French would be Localization.fr.resx
and so on. You can find all the codes for each language here.
Configuring the Default Language
To configure the default language, you can find an option under the [Locale]
section of the main.toml
config file called defaultLanguage
. The format of this value is the same language code as explained above under Adding Multilingual Support.
Player Specific Language
EvoSC# can adapt the output language depending on a player's selected language. To do this, prefix the locale reference with .PlayerLanguage
.
For example:
var myString = _locale.PlayerLanguage.MyLocaleName;
or
var myString = _locale.PlayerLanguage["MyLocaleName"];
Whenever you prefix with .PlayerLanguage
, it puts the locale instance in the state that any reference from this point on will use the player's selected language.
You can get a player's selected language from the Settings
property from an IPlayer
instance.
Arbitrary String Translations
It is possible to translate a string which contains localization references, and replace those references with the localization string. This is useful for refering to locales in compile-time constants such as command descriptions.
To reference a localization string, put the name in between two square brackets. For example: [MyLocaleName]
These can occur anywhere within a string, and you can put multiple of them. For example: My locale: [MyLocaleName]
To translate a string you can use the Translate
method. For example:
var translatedString = _locale.Translate("My locale: [MyLocaleName]");