Game API Localization

Index: GameAPI

## Localizting content

### Accomplish

The basic concept of localization is to provide strings of text, in the users language.

The game already supports several languages 'natively', so opening your mod up for being localized is potentially also opening it up for a broader audience.

Furthermore, it allows you to 'outsource' finding spelling mistakes and/or improving formulations.

It also allows the users of the mod, to customize messages, if they for whatever reason wants to, although this isn't the goal.

### Pre-requisites

This is optional, but if you don't, you will need to tweak the provided code to support this.

```
Dictionary<int, Int64> entityToSteam = new Dictionary<int, Int64>();

```

It is recommended to add to this list, as soon as a new client connects.

(You can for instance opt to chain a Event\_Player\_Connected into Event\_Player\_Info, where you add to the 'entityToSteam' dictionary)

```
entityToSteam.Add(int.Parse(ply.entityId.ToString()), Int64.Parse(ply.steamId.ToString()));

```

Once supported, you could also add to the 'locauser' dictionary from here. (assuming that will be the implementation of providing that information)

Optionally a function, 'getSource', returning the absolute path to a file, relative to the mod.

### Global variables

#### Locauser

As of A8.1, the API \_DOES NOT\_ support getting the local language of the client. Assumingly, this will eventually be provided through the Event\_Player\_Info.

You need a global Dictionary

```
Dictionary<Int64, string> locauser = new Dictionary<long, string>();

```

This will eventually contain a list of the relationsship between a steamid and a language. (Once supported by the API)

You will additionally need to keep another dictionary, relating entityID's to steamID's.

#### LocalizationIndex

Object containing the information of supported languages (index populated from provided file)

```
Object[] localizationindex;

```

#### Localization

Dictionary used to keep the extracted information from the file.

```
Dictionary<String, Object[]> Localization = new Dictionary<string, object[]>();

```

### File

In the example, the file is called 'Localization.csv', but any file, and any extension should be fine.

The essence is that the file contains the following format:

#### First line

The first line of the file must contain

```
KEY

```

At the very least.

You can additionally supply 0-inf languages after this. The text specified for the language, must match what we know from the player (What was used to populate 'locauser'. More documentation will follow on this, once the game implements it.

For instance:

```
KEY,English,Deutsch

```

If you want to support the languages 'English' and 'Deutsch'

#### Subsequentiel lines

After the first line, each line must start with an **UNIQUE**! key.

This key isused to later refer to the entry.

For instance:

```
KickLowRep,"You were kicked, as your reputation got too low."

```

Here, the key 'KickLowRep' would resolve to 'You were kicked, as your reputation got too low.', for English users.

No other languages were supported for this string, but could easily be done so, by specifying additional commas after, matching the index set in the first line.

(For instance, if first line was 'KEY,English,Deutsch', you could do the following)

```
KickLowRep,"You were kicked, as your reputation got too low.","Du wurdest getreten, als dein Ruf zu niedrig wurde."

```

(Sorry for google translate)

##### NOTE!

You do not have to encase each sentence in "", but you **MUST** do so, if the translation contains a comma (,)

(As shown in the translation above, both languages had a comma in the string, and hence, the translation had to be encased in quotes)

An example not needing quotes:

```
Expires,Expires,verfallen

```

The file (for just these two entries) would look like:

```
KEY,English,Deutsch
KickLowRep,"You were kicked, as your reputation got too low.","Du wurdest getreten, als dein Ruf zu niedrig wurde."
Expires,Expires,verfallen

```

### Code

There's three functions needed to make this work:

-Loading

-Get

-Helper for get

#### Load files

In order to populate the localization, you must reference to a file.

This file **MUST** be in the format specified above.

This function should be called at

```
public void Game_Start(ModGameAPI dediAPI)

```

But can be called at any point. It just needs to exist, before you try to refer to the localization, if you want any viable returns.

```
public void loadLocalization()
        {
           //If already loaded, reset currently loaded.
           localizationindex = null;
           if (Localization != null)
           {
               Localization.Clear();
           }
            //Open file
            using (var input = File.OpenText(getSource("Localization.csv"))) //Note that you must createa 'getSource' function, or specify an absolute path to the file.
            {
                string s = "";
                s = input.ReadToEnd();
                s = s.Replace("\"\"", ""); //To be compatible with official loca, if they use "", it means a " inline. Remove it entirely, to not mess with any code, where it is used
                string[] st = s.Split('\n'); //Split into segments, for each newline
                int line = 0;
                string pat = @",(?=(?:[^\""]*\""[^\""]*\"")*[^\""]*$)"; //Split by commas, not encased in quotes. (Original regex: ,(?=(?:[^\"]*\"[^\"]*\")*[^\"]*$) )
                foreach (string ss in st)
                {
                    { //If the line is 'bad', do nothing
                    }
                    else
                    {
                        string[] mv = Regex.Split(ss, pat);
                        Object[] topush = null;
                        if (mv != null && mv.Length > 0)
                        {
                            int sc = 0;
                            int finallength = mv.Length; //Determine the length of THIS object (to be efficient), depending on how much data was availible.
                            if (line > 0)
                            {
                                if (mv.Length < localizationindex.Length)
                                {
                                    finallength = localizationindex.Length;
                                }
                                topush = new object[finallength - 1]; //Push the object to the main localization array
                            }
                            if (line == 0)
                            {
                                localizationindex = new object[mv.Length]; //If we're doing the index
                            }
                            string thisindex = "";
                            foreach (var f in mv)
                            {
                                { //Again, check for bad data
                                }
                                else
                                {
                                    string off = f.TrimEnd();  //Remove bad endings (newlines for instance)
                                    if (line == 0)
                                    {
                                        localizationindex[sc] = off; //Set the data for the index.
                                    }
                                    else
                                    { //If we aren't looking at the first line, do:
                                        if (sc == 0)
                                        {
                                            thisindex = off;
                                        }
                                        else
                                        {
                                            if (topush != null)
                                            {
                                                string of = off;
                                               if (of.Length > 0) //make sure there's an index
                                               {
                                                if (of.IndexOf("\"") == 0)
                                                {
                                                    of=of.Remove(0, 1); //Remove quotes from start, if they exist
                                                }
                                               }
                                               if (of.Length > 0) //make sure there's an index
                                               {
                                                if (of.IndexOf("\"") == of.Length-1)
                                                {
                                                    of=of.Remove(of.Length-1, 1); //Remove quotes from end, if they exist
                                                }
                                               }
                                                topush[sc - 1] = of;  //edit the temporary object (Add this entry)
                                            }
                                        }
                                    }
                                    sc++;
                                }
                            }
                            if (topush != null)
                            {
                                Localization.Add(thisindex, topush); //If the push Object was good, add it as a viable Localization (Push KEY + each lang into the dictionary)
                                if (Localizationstd.ContainsKey(thisindex))
                                {
                                    //In case we have a poorly formatted string, that doesn't respect the newline, it might intersect with one already existing.
                                       Localizationstd.Remove(thisindex);
                                }
                            }
                            line++;
                        }
                    }
                }
            }
        }

```

Note that you must create a 'getSource' function, or specify an absolute path to the file.

#### GetLocalization

This is the function, referenced in your code. This requires either the steamid, or the entityid of a player, and resolves to the language of that player, and asks the helper function for the relevant string.

```
public string getLocalization(string key, Int64 steamid = -1,int entityid = -1)
        {
            string outstr = "MISSING. " + key; //Incase something went wrong OR the key wasn't found, default to this
            try
            { //If entityid was specified, resolve it to a steamID
                if (entityid > -1)
                {
                    if (entityToSteam.ContainsKey(entityid))
                    {
                        steamid = entityToSteam[entityid];
                    }
                }
            }
            catch { }
            string seeklang = "English"; //Default to 'English'
            try
            {
                if (locauser.ContainsKey(steamid))
                { //If locauser contains this user, find the desired language, based on users entry.
                    seeklang = locauser[steamid];
                }
            }
            catch { }
            outstr = getLocalizationHelper(key, seeklang); //Call helper function, to resolve the key to the desired language
            return outstr;
        }

```

#### Localization helper

This function resolves the given language to a string if possible.

If not, it will default to 'English' (If no language was provided, or the language provided is not supported for the specific string.

```
public string getLocalizationHelper(string key,string language = "English") //Default lang: 'English', if nothing was specified
        {
            string outstr = "MISSING "+key; //If nothing was found for the key OR something went wrong, fallback to this.
            try
            {
                int outi = -1;
                if (Localization.ContainsKey(key))
                { //If key was found in the Localization
                    int ix = -1;
                    foreach (var f in localizationindex)
                    {
                        if (f != null && (string)f == language)
                        { //Check if the entry is good, and the desired language
                            outi = ix;
                        }
                        ix++;
                    }
                    if (outi == -1)
                    { //None found, take the first
                        outi = 0;
                    }
                    if (Localization[key].Length >= outi)
                    {
                    }
                    else
                    { //The desired index for the language didn't exist on the object (For instance if we are looking for 'Deutsch', and only 'English' was supplied to the element
                        outi = 0;
                    }
                    if (Localization[key].Length >= outi)
                    {
                        if (Localization[key][outi] != null)
                        {
                        }
                        else
                        { //If the entry was bad, default to the first.
                            outi = 0;
                        }
                        if (Localization[key][outi] != null)
                        { //Resolve to the entry finally.
                            outstr = (string)Localization[key][outi];
                        }
                        else
                        { //If it doesn't even have a valid 0 index, return this error'
                            return "KEY " + key + " DOESNT CONTAIN 1 VALID ROW B:" + outi;
                        }
                    }
                    else
                    { //If it didn't even have one viable entry, return this error
                        return "KEY " + key + " DOESNT CONTAIN 1 VALID ROW A";
                    }
                }
            }
            catch { }
            return outstr; //Else return the resolved string
        }

```

### Usuage

For both usecases count, that you refer to a key specified in the Localization.csv file.

#### Basic

Nothing fancy. Simply just the localized string.

##### With users steamid

```
getLocalization("KickLowRep", steamid)

```

You want the KEY for 'KickLowRep', and have the users steamid.

##### With users entityid

```
getLocalization("KickLowRep", -1, entityid)

```

You want the KEY for 'KickLowRep', and does not have the steamid handy, so you pass '-1' (unknown), and then the users entityid.

You could for instance do it in this context:

```
GameAPI.Console_Write("The local key for 'KickLowRep' is for user '"+steamid+"':"+getLocalization("KickLowRep", steamid));

```

#### Advanced

Given the nature of C#, you can fairly easily include VARIABLES in translations, by using {0}, {1} (...).

The easiest way of going about this is:

```
string.Format(getLocalization("AfkKick", steamid), config.AFKKick);

```

Here referencing the KEY in localization.csv:

```
AfkKick,"You were kicked for being AFK for {0} minutes."

```

(This would resolve to for instance "You were kicked for being AFK for 10 minutes.", if the variable config.AFKKick was 10 (etc))

If you have multiple variables, you simply append them, and refer to them by a higher number in brackets; ie.

```
string.Format(getLocalization("AfkKickRepChange", steamid), config.AFKKick, config.AFKKickRepChange);

```

Would reference this KEY:

```
AfkKickRepChange,"You were kicked for being AFK for {0} minutes. Your reputation changed with {1}"

```

(This would resolve to: "You were kicked for being AFK for 10 minutes. Your reputation changed with -1", given config.AFKKick was 10, and config.AFKKickRepChange was -1)