Using MultiLangLib in your own mod
This guide explains how to add MultiLangLib as a shared translation library to a Captain of Industry mod. The consumer mod in the examples is named MyMod.
1. Declare the dependency
Add MultiLangLib to your mod's manifest.json. The dependency deliberately contains no spaces around >=:
{
"id": "MyMod",
"version": "1.0.0",
"primary_dlls": [ "MyMod.dll" ],
"mod_dependencies": [ "MultiLangLib>=0.1.0" ]
}
This ensures that your mod is only loaded when a compatible MultiLangLib version is available.
2. Reference the DLL
Reference the installed MultiLangLib.dll in your project file, but do not copy it to your mod's output directory:
<ItemGroup>
<Reference Include="MultiLangLib">
<HintPath>$(APPDATA)\Captain of Industry\Mods\MultiLangLib\MultiLangLib.dll</HintPath>
<Private>false</Private>
</Reference>
</ItemGroup>
Private=false is important: exactly one shared MultiLangLib DLL should be loaded. If both projects are in the same source tree, you can use a project reference with Private="false" instead.
3. Register the mod directory
Import the namespace and register your mod's root directory in its constructor:
using MultiLangLib;
using Mafi.Core.Mods;
public sealed class MyMod : DataOnlyMod {
public MyMod(ModManifest manifest) : base(manifest) {
Lang.RegisterMod(manifest.Id, manifest.RootDirectoryPath);
}
// Remaining mod implementation ...
}
Registration is recommended. MultiLangLib can discover normally installed sibling directories automatically, but explicit registration also works reliably with nonstandard directory layouts.
4. Create language files
Store the translations in your own mod's lang directory:
MyMod\
├── MyMod.dll
├── manifest.json
└── lang\
├── de.json
└── en.json
The recommended format is a simple JSON object. en.json:
{
"window.title": "Production overview",
"window.close": "Close",
"welcome": "Welcome, Captain {0}!"
}
And de.json:
{
"window.title": "Produktionsübersicht",
"window.close": "Schließen",
"welcome": "Willkommen, Captain {0}!"
}
The COI array format is also accepted:
[
[ "multilanglib.MyMod.window.title", "Production overview" ],
[ "window.close", "Close" ]
]
Both short IDs and complete keys are supported. Use each ID only once within a file.
5. Resolve text in code
Every complete key follows this pattern:
multilanglib.<ModId>.<TextId>
Typical calls:
using Mafi.Localization;
using MultiLangLib;
string title = Lang.Get("multilanglib.MyMod.window.title");
string close = Lang.Get("MyMod", "window.close");
LocStrFormatted titleForUi = Lang.Localized("MyMod", "window.title");
string greeting = Lang.Format("multilanglib.MyMod.welcome", playerName);
Lang.Get(...)returns an already resolvedstring.Lang.Localized(...)wraps the resolved text inLocStrFormattedfor COI user interfaces.Lang.Format(...)replaces placeholders using the active language culture.Lang.TryGet(...)reports through its return value whether an entry was found.
A plain string such as "multilanglib.MyMod.window.title" is not replaced automatically by the game. Always resolve the key through the MultiLangLib API.
Key rules
- The
ModIdmust begin with a letter or digit. - The remainder of the
ModIdmay contain letters, digits,_, and-. - The
TextIdmay additionally contain dots, for examplesettings.audio.volume. - Dots are not allowed in the
ModId. - IDs are case-sensitive.
A static helper class prevents typing mistakes at call sites:
public static class Texts {
public static string WindowTitle =>
Lang.Get("MyMod", "window.title");
public static LocStrFormatted WindowTitleForUi =>
Lang.Localized("MyMod", "window.title");
}
Lookup order and fallback
For multilanglib.MyMod.window.title with German selected in the game, MultiLangLib searches in this order:
1. <MyMod>/lang/de.json
2. <MultiLangLib>/lang/MyMod/de.json
3. <MyMod>/lang/en.json
4. <MultiLangLib>/lang/MyMod/en.json
5. multilanglib.MyMod.window.title
Central files under MultiLangLib\lang\MyMod\ can be used for community translations supplied separately, for example. The file included in the consumer mod takes precedence.
For regional languages, the exact variant is checked before the neutral variant, such as de-DE.json before de.json. In automatic mode, MultiLangLib uses the file names supplied by Captain of Industry, including names such as pt_BR.json and zh_Hans.json.
Debugging
Enable the following option in the MultiLangLib settings:
debug_language = true
Every valid lookup will then return its complete key. This makes it easy to identify which translation is expected at any location in the UI.
Additional settings:
language_override = auto: follows the language selected in the game.language_override = debug: also enables key output.language_override = de: forces a language.language_override = de.json: forces a literal file name.fallback_language = en: selects the fallback language.
MultiLangLib caches loaded files. During development, call Lang.Reload() after changing a file. UI elements that have already been rendered are not rebuilt automatically.
Common problems
Only the key is visible in the game: Check the file name, ModId, TextId, and capitalization. Also verify that the JSON is valid and MultiLangLib is enabled.
The consumer mod does not load: Check the MultiLangLib>=0.1.0 dependency and confirm that the library directory is named MultiLangLib.
There is a DLL conflict: Set Private=false on the reference and do not distribute MultiLangLib.dll inside the consumer mod directory.
Placeholders remain visible: Use Lang.Format(...) and supply a suitable argument for every placeholder. Format errors are logged; MultiLangLib safely returns the unformatted text in that case.
A changed file is not reloaded: Call Lang.Reload() during development or restart the game.
Complete example
A buildable example containing a manifest, project reference, C# calls, and German and English language files is available under:
examples/ExampleConsumer/