Skip to content

Commit

Permalink
2.12.0
Browse files Browse the repository at this point in the history
- Mod config settings now support translations. OWML will look for files named after the `TextTranslation.Language` enums in base game in a folder named `translations` where your mod is installed. Eg, `translations/english.json`. These files will follow the same format as New Horizons translations, and the `UIDictionary` will be used to store key-value pairs for your translation results. Translations support the `title` and `tooltip` fields for a setting, and if the title is absent it will use the setting's ID as a key. Check the OWML settings documentation for more info.
- Added `dlcOnly` support to settings that should only appear when Echoes of the Eye is installed.
  • Loading branch information
xen-42 authored Jun 5, 2024
2 parents 362b7e6 + e1e0097 commit 1ed0296
Show file tree
Hide file tree
Showing 12 changed files with 254 additions and 14 deletions.
72 changes: 72 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@

[*.{cs,vb}]
#### Naming styles ####

# Naming rules

dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i

dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case

dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case

# Symbol specifications

dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =

dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =

dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =

# Naming styles

dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case

dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case

dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
indent_size = 4
end_of_line = crlf
dotnet_style_null_propagation = true:suggestion
indent_style = tab

[*.cs]
csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_prefer_braces = true:silent
csharp_style_namespace_declarations = block_scoped:silent
csharp_style_prefer_method_group_conversion = true:silent
csharp_style_prefer_top_level_statements = true:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_indent_labels = one_less_than_current
3 changes: 2 additions & 1 deletion OWML.sln
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OWML.ExampleAPI", "src\Samp
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{C7F76E72-1CF2-4C0D-8A39-3D13EB868119}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
LICENSE = LICENSE
.github\workflows\main.yml = .github\workflows\main.yml
owmllogo.png = owmllogo.png
Expand Down Expand Up @@ -209,7 +210,7 @@ Global
{739D16FB-7848-4047-A173-500CE7C40399} = {C447A599-2700-44E1-BBFA-52880B7BFFBA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
EnterpriseLibraryConfigurationToolBinariesPath = packages\Unity.2.1.505.2\lib\NET35;packages\Unity.2.1.505.0\lib\NET35
SolutionGuid = {0E767163-75F9-420A-80EB-320429543CAD}
EnterpriseLibraryConfigurationToolBinariesPath = packages\Unity.2.1.505.2\lib\NET35;packages\Unity.2.1.505.0\lib\NET35
EndGlobalSection
EndGlobal
19 changes: 19 additions & 0 deletions docs/content/pages/guides/mod_settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,22 @@ public class MyMod : ModBehaviour {
## Config Updates

Something important to note is that when the manager pulls and update for your mod, the `config.json` file is preserved. The issue with this is menus are generated from the `config.json` file. When changing options like slider minimums and maximums or choices, you may want to create a new property rather than edit an existing one to make sure the UI is correct.

## Translations

Mod config options can be translated in the same way that [New Horizons mods do translations](https://nh.outerwildsmods.com/guides/translation/). First, you need to add a folder named `translations` in the root directory of your mod.

There are 12 supported languages in Outer Wilds: `english`, `spanish_la`, `german`, `french`, `italian`, `polish`, `portuguese_br`, `japanese`, `russian`, `chinese_simple`, `korean`, and `turkish`.

In the `translations` folder you can put json files with the name of the language you want to translate for. This file will contain a single dictionary named `UIDictionary` which will have key-value pairs for the translation.

```json
{
"UIDictionary": {
"settingTitle": "My Translated Title",
"settingTooltip": "My Translated Tooltip"
}
}
```

If your mod already uses New Horizons and supports translations, these values are added directly into the same translation files that NH uses.
5 changes: 4 additions & 1 deletion src/OWML.Common/Interfaces/IModHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using OWML.Common.Menus;
using OWML.Common.Interfaces;
using OWML.Common.Menus;
using System;

namespace OWML.Common
Expand Down Expand Up @@ -31,5 +32,7 @@ public interface IModHelper
IModInteraction Interaction { get; }

IMenuManager MenuHelper { get; }

IModTranslations MenuTranslations { get; }
}
}
7 changes: 7 additions & 0 deletions src/OWML.Common/Interfaces/IModTranslations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace OWML.Common.Interfaces
{
public interface IModTranslations
{
public string GetLocalizedString(string key);
}
}
2 changes: 1 addition & 1 deletion src/OWML.Launcher/OWML.Manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"author": "Alek",
"name": "OWML",
"uniqueName": "Alek.OWML",
"version": "2.11.1",
"version": "2.12.0",
"minGameVersion": "1.1.14.768",
"maxGameVersion": "1.1.14.768"
}
24 changes: 17 additions & 7 deletions src/OWML.ModHelper.Menus/ModConfigMenuBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using OWML.Common;
using OWML.Common.Menus;
using OWML.Utils;
using OWML.Common.Interfaces;

namespace OWML.ModHelper.Menus
{
Expand All @@ -19,6 +20,8 @@ public abstract class ModConfigMenuBase : ModMenuWithSelectables, IModConfigMenu
private IModNumberInput _numberInputTemplate;
private IModSeparator _seperatorTemplate;

private IModTranslations _translations;

protected abstract void AddInputs();

public abstract void UpdateUIValues();
Expand All @@ -28,6 +31,8 @@ protected ModConfigMenuBase(IModManifest manifest, IModStorage storage, IModCons
{
Manifest = manifest;
Storage = storage;

_translations = new ModTranslations(manifest, console);
}

public void Initialize(Menu menu, IModToggleInput toggleTemplate, IModSliderInput sliderTemplate,
Expand Down Expand Up @@ -111,7 +116,7 @@ private void AddToggleInput(string key, int index, JObject obj = null)
{
var toggle = AddToggleInput(_toggleTemplate.Copy(key), index);
toggle.Element.name = key;
toggle.Title = (string)obj?["title"] ?? key;
SetupTitle(toggle, (string)obj?["title"], key);
SetupInputTooltip(toggle, (string)obj?["tooltip"]);
toggle.Show();
}
Expand All @@ -122,7 +127,7 @@ private void AddSliderInput(string key, int index, JObject obj)
slider.Min = (float)obj["min"];
slider.Max = (float)obj["max"];
slider.Element.name = key;
slider.Title = (string)obj["title"] ?? key;
SetupTitle(slider, (string)obj?["title"], key);
SetupInputTooltip(slider, (string)obj["tooltip"]);
slider.Show();
}
Expand All @@ -132,7 +137,7 @@ private void AddSelectorInput(string key, int index, JObject obj)
var options = obj["options"].ToObject<string[]>();
var selector = AddSelectorInput(_selectorTemplate.Copy(key), index);
selector.Element.name = key;
selector.Title = (string)obj["title"] ?? key;
SetupTitle(selector, (string)obj?["title"], key);
selector.Initialize((string)obj["value"], options);
SetupInputTooltip(selector, (string)obj["tooltip"]);
selector.Show();
Expand All @@ -142,7 +147,7 @@ private void AddTextInput(string key, int index, JObject obj = null)
{
var textInput = AddTextInput(_textInputTemplate.Copy(key), index);
textInput.Element.name = key;
textInput.Title = (string)obj?["title"] ?? key;
SetupTitle(textInput, (string)obj?["title"], key);
SetupInputTooltip(textInput, (string)obj?["tooltip"]);
textInput.Show();
}
Expand All @@ -151,7 +156,7 @@ private void AddNumberInput(string key, int index, JObject obj = null)
{
var numberInput = AddNumberInput(_numberInputTemplate.Copy(key), index);
numberInput.Element.name = key;
numberInput.Title = (string)obj?["title"] ?? key;
SetupTitle(numberInput, (string)obj?["title"], key);
SetupInputTooltip(numberInput, (string)obj?["tooltip"]);
numberInput.Show();
}
Expand All @@ -160,15 +165,20 @@ private void AddSeparator(string key, int index, JObject obj)
{
var separator = AddSeparator(_seperatorTemplate.Copy("Inputs"), index);
separator.Element.name = key;
separator.Title = (string)obj?["title"] ?? key;
SetupTitle(separator, (string)obj?["title"], key);
separator.Show();
}

internal void SetupInputTooltip<T>(IModInput<T> input, string tooltip)
{
var menuOption = input.Element.GetComponent<MenuOption>();
menuOption.SetValue("_tooltipTextType", UITextType.None);
menuOption.SetValue("_overrideTooltipText", tooltip?? "");
menuOption.SetValue("_overrideTooltipText", _translations.GetLocalizedString(tooltip) ?? "");
}

internal void SetupTitle(IModInputBase input, string title, string key)
{
input.Title = title == null ? key : _translations.GetLocalizedString(title);
}
}
}
92 changes: 92 additions & 0 deletions src/OWML.ModHelper.Menus/ModTranslations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using Newtonsoft.Json.Linq;
using OWML.Common;
using OWML.Common.Interfaces;
using System;
using System.Collections.Generic;
using System.IO;

namespace OWML.ModHelper.Menus
{
public class ModTranslations : IModTranslations
{
private Dictionary<TextTranslation.Language, Dictionary<string, string>> _translationTable = new();

private IModManifest _manifest;
private IModConsole _console;

// Menu translations are stored under UIDictionary
// This means OWML config translations follow the New Horizons format
public static readonly string UIDictionary = nameof(UIDictionary);

private bool _initialized;

public ModTranslations(IModManifest manifest, IModConsole console)
{
_manifest = manifest;
_console = console;
}

private void Init()
{
try
{
var translationsFolder = Path.Combine(_manifest.ModFolderPath, "translations");
foreach (TextTranslation.Language translation in Enum.GetValues(typeof(TextTranslation.Language)))
{
var filename = Path.Combine(translationsFolder, $"{translation}.json");
if (File.Exists(filename))
{
var dict = JObject.Parse(File.ReadAllText(filename)).ToObject<Dictionary<string, object>>();
if (dict.ContainsKey(UIDictionary))
{
_translationTable[translation] = (Dictionary<string, string>)(dict[nameof(UIDictionary)] as JObject).ToObject(typeof(Dictionary<string, string>));
}
}
}
_initialized = true;
}
catch (Exception ex)
{
_console.WriteLine($"Failed to initialize mod option translations {ex}", MessageType.Error);
}
}

public string GetLocalizedString(string key)
{
if (!_initialized)
{
Init();
}

try
{
if (key == null) return null;
if (key == string.Empty) return string.Empty;

if (!_translationTable.TryGetValue(TextTranslation.Get().m_language, out var dict))
{
// Default to English
if (!_translationTable.TryGetValue(TextTranslation.Language.ENGLISH, out dict))
{
// Default to key
return key;
}
}

if (dict.TryGetValue(key, out var value))
{
return value;
}
else
{
return key;
}
}
catch (Exception ex)
{
_console.WriteLine($"Failed to load options translation: {ex}", MessageType.Error);
return key;
}
}
}
}
14 changes: 11 additions & 3 deletions src/OWML.ModHelper.Menus/NewMenuSystem/MenuManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,21 +216,29 @@ void SaveConfig()
foreach (var (name, setting) in mod.ModHelper.Config.Settings)
{
var settingType = GetSettingType(setting);
var label = name;
var label = mod.ModHelper.MenuTranslations.GetLocalizedString(name);
var tooltip = "";
var settingObject = setting as JObject;
if (settingObject["dlcOnly"].ToObject<bool>())
{
if (EntitlementsManager.IsDlcOwned() == EntitlementsManager.AsyncOwnershipStatus.NotOwned)
{
continue;
}
}
if (settingObject != default(JObject))
{
if (settingObject["title"] != null)
{
label = settingObject["title"].ToString();
label = mod.ModHelper.MenuTranslations.GetLocalizedString(settingObject["title"].ToString());
}
if (settingObject["tooltip"] != null)
{
tooltip = settingObject["tooltip"].ToString();
tooltip = mod.ModHelper.MenuTranslations.GetLocalizedString(settingObject["tooltip"].ToString());
}
}
Expand Down
7 changes: 6 additions & 1 deletion src/OWML.ModHelper/ModHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using OWML.Common;
using OWML.Common.Interfaces;
using OWML.Common.Menus;

namespace OWML.ModHelper
Expand Down Expand Up @@ -31,6 +32,8 @@ public class ModHelper : IModHelper

public IMenuManager MenuHelper { get; }

public IModTranslations MenuTranslations { get; }

public ModHelper(
IModLogger logger,
IModConsole console,
Expand All @@ -44,7 +47,8 @@ public ModHelper(
IModDefaultConfig defaultConfig,
IOwmlConfig owmlConfig,
IModInteraction interaction,
IMenuManager menuHelper)
IMenuManager menuHelper,
IModTranslations menuTranslations)
{
Logger = logger;
Console = console;
Expand All @@ -59,6 +63,7 @@ public ModHelper(
OwmlConfig = owmlConfig;
Interaction = interaction;
MenuHelper = menuHelper;
MenuTranslations = menuTranslations;
}
}
}
Loading

0 comments on commit 1ed0296

Please sign in to comment.