Localization
Learn how to add localization support to your Resonite mods using BepisLocaleLoader, enabling your mod to display text in multiple languages.
Prerequisites
Section titled “Prerequisites”Basic Usage
Section titled “Basic Usage”Adding the Package Reference
Section titled “Adding the Package Reference”First, add the BepisLocaleLoader NuGet package to your project file (.csproj):
<ItemGroup> <PackageReference Include="ResoniteModding.BepisLocaleLoader" Version="1.*" /></ItemGroup>
<!-- Add the Resonite modding NuGet source --><PropertyGroup> <RestoreAdditionalProjectSources> https://nuget-modding.resonite.net/v3/index.json; </RestoreAdditionalProjectSources></PropertyGroup>Adding the Dependency
Section titled “Adding the Dependency”Then, add BepisLocaleLoader as a dependency in your plugin:
using BepisLocaleLoader;using BepInEx;using BepInEx.Configuration;using BepInExResoniteShim;
// PluginMetadata is automatically generated by ResonitePluginInfoProps[ResonitePlugin(PluginMetadata.GUID, PluginMetadata.NAME, PluginMetadata.VERSION, PluginMetadata.AUTHORS, PluginMetadata.REPOSITORY_URL)][BepInDependency(BepInExResoniteShim.PluginMetadata.GUID, BepInDependency.DependencyFlags.HardDependency)][BepInDependency(BepisLocaleLoader.PluginMetadata.GUID, BepInDependency.DependencyFlags.HardDependency)]public class MyPlugin : BasePlugin{ // Your plugin code}Using Localized Config Descriptions
Section titled “Using Localized Config Descriptions”When creating configuration entries, use ConfigLocale to provide localized descriptions:
public override void Load(){ var myConfig = Config.Bind( "General", "EnableFeature", true, new ConfigDescription( "Enable this feature", null, new ConfigLocale( "Settings.dev.author.myPlugin.config.Key", "Settings.dev.author.myPlugin.config.Description" ) ) );}Creating Locale Files
Section titled “Creating Locale Files”File Structure
Section titled “File Structure”Create a Locale folder next to your plugin DLL with JSON files for each language:
MyPlugin/├── MyPlugin.dll└── Locale/ ├── en.json ├── es.json ├── fr.json └── ja.jsonJSON Format
Section titled “JSON Format”Each locale file should follow this structure:
{ "localeCode": "en-US", "authors": ["YourName"], "messages": { "Settings.dev.author.myPlugin": "My Plugin", "Settings.dev.author.myPlugin.Breadcrumb": "My Plugin Settings", "Settings.dev.author.myPlugin.config.Key": "Enable Feature", "Settings.dev.author.myPlugin.config.Description": "Enables the main feature of this plugin." }}String Formatting
Section titled “String Formatting”Using the .T() Extension Method
Section titled “Using the .T() Extension Method”BepisLocaleLoader provides a convenient .T() extension method for string localization:
// Simple key lookupvar text = "Settings.MyPlugin.Welcome".T();// Single argumentvar greeting = "Settings.MyPlugin.Greeting".T( "Hello {name}!", "name", userName);
// Multiple argumentsvar stats = "Settings.MyPlugin.Stats".T( ("kills", killCount), ("deaths", deathCount));// For dynamic UI that updates continuouslyvar timer = "Settings.MyPlugin.Timer".T( continuous: true, arguments: new Dictionary<string, object> { { "time", elapsedTime } });Advanced Features
Section titled “Advanced Features”Adding Strings Programmatically
Section titled “Adding Strings Programmatically”You can register localized strings directly in code:
LocaleLoader.AddLocaleString( "Settings.MyPlugin.NewFeature", "New Feature Text", force: true, // Overwrite if exists authors: "YourName");Force Parameter Usage
Section titled “Force Parameter Usage”force: false(default) - Safe for adding new keys without overwritingforce: true- Use when you need to override existing translations
Complete Example
Section titled “Complete Example”Here’s a complete example based on the Optizoom mod:
using BepInEx;using BepInEx.Configuration;using BepisLocaleLoader;using BepInExResoniteShim;using Elements.Core;
// PluginMetadata is automatically generated by ResonitePluginInfoProps[ResonitePlugin(PluginMetadata.GUID, PluginMetadata.NAME, PluginMetadata.VERSION, PluginMetadata.AUTHORS, PluginMetadata.REPOSITORY_URL)][BepInDependency(BepInExResoniteShim.PluginMetadata.GUID, BepInDependency.DependencyFlags.HardDependency)][BepInDependency(BepisLocaleLoader.PluginMetadata.GUID, BepInDependency.DependencyFlags.HardDependency)]public class MyPlugin : BasePlugin{ private static ConfigEntry<bool> enabled; private static ConfigEntry<Key> hotkey; private static ConfigEntry<float> intensity;
public override void Load() { // Basic toggle enabled = Config.Bind( "General", "Enabled", true, new ConfigDescription( "Enable the plugin", null, new ConfigLocale( "Settings.dev.author.myPlugin.enabled.Key", "Settings.dev.author.myPlugin.enabled.Description" ) ) );
// Key binding hotkey = Config.Bind( "General", "Hotkey", Key.Tab, new ConfigDescription( "Activation hotkey", null, new ConfigLocale( "Settings.dev.author.myPlugin.hotkey.Key", "Settings.dev.author.myPlugin.hotkey.Description" ) ) );
// Slider with range intensity = Config.Bind( "General", "Intensity", 50f, new ConfigDescription( "Effect intensity", new AcceptableValueRange<float>(0f, 100f), new ConfigLocale( "Settings.dev.author.myPlugin.intensity.Key", "Settings.dev.author.myPlugin.intensity.Description" ) ) ); }}{ "localeCode": "en-US", "authors": ["YourName"], "messages": { "Settings.dev.author.myPlugin": "My Plugin", "Settings.dev.author.myPlugin.Breadcrumb": "My Plugin Settings",
"Settings.dev.author.myPlugin.enabled.Key": "Enable Plugin", "Settings.dev.author.myPlugin.enabled.Description": "Enable or disable the plugin functionality.",
"Settings.dev.author.myPlugin.hotkey.Key": "Activation Hotkey", "Settings.dev.author.myPlugin.hotkey.Description": "The key to press to activate the plugin feature.",
"Settings.dev.author.myPlugin.intensity.Key": "Effect Intensity", "Settings.dev.author.myPlugin.intensity.Description": "Controls the strength of the effect (0-100)." }}{ "localeCode": "es-ES", "authors": ["YourName"], "messages": { "Settings.dev.author.myPlugin": "Mi Plugin", "Settings.dev.author.myPlugin.Breadcrumb": "Configuración de Mi Plugin",
"Settings.dev.author.myPlugin.enabled.Key": "Habilitar Plugin", "Settings.dev.author.myPlugin.enabled.Description": "Habilita o deshabilita la funcionalidad del plugin.",
"Settings.dev.author.myPlugin.hotkey.Key": "Tecla de Activación", "Settings.dev.author.myPlugin.hotkey.Description": "La tecla para activar la función del plugin.",
"Settings.dev.author.myPlugin.intensity.Key": "Intensidad del Efecto", "Settings.dev.author.myPlugin.intensity.Description": "Controla la fuerza del efecto (0-100)." }}Best Practices
Section titled “Best Practices”Naming Convention
Section titled “Naming Convention”Follow a consistent naming pattern for your locale keys:
Settings.dev.[author].[plugin].[category].[setting]Example:
Settings.dev.lecloutpanda.optizoom.zoomSpeed.KeySettings.dev.lecloutpanda.optizoom.zoomSpeed.DescriptionOrganization Tips
Section titled “Organization Tips”- Group related settings - Use consistent prefixes for related configuration options
- Provide clear descriptions - Help users understand what each setting does
- Include default languages - At minimum, provide English (en.json)
- Test all languages - Verify that text displays correctly in different languages
Debugging
Section titled “Debugging”Checking Loaded Locales
Section titled “Checking Loaded Locales”You can verify which plugins have loaded locales:
foreach (var plugin in LocaleLoader.PluginsWithLocales){ Log.LogInfo($"Plugin with locales: {plugin}");}Common Issues
Section titled “Common Issues”- Locales not loading: Ensure the
Localefolder is next to your plugin DLL - JSON parsing errors: Check logs for detailed error messages
- Keys not found: Verify the key names match exactly between code and JSON
- Missing dependency: Ensure BepisLocaleLoader is installed and loaded before your plugin