diff --git a/Directory.Build.props b/Directory.Build.props
index 5176381..372c6fa 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -11,6 +11,6 @@
It should follow the format major.minor.patch (semantic versioning). If you publish your mod
as a library to NuGet, this version will also be used as the package version.
-->
- 0.3.0
+ 0.4.0
diff --git a/Elements/ChoiceElement.cs b/Elements/ChoiceElement.cs
index 05ebc5c..150043b 100644
--- a/Elements/ChoiceElement.cs
+++ b/Elements/ChoiceElement.cs
@@ -17,7 +17,7 @@ public class ChoiceElement : SelectableValueElement
///
/// Construct a ChoiceElement with the given label text, model, and description text.
///
- public ChoiceElement(string label, IChoiceModel model, string description = "")
+ public ChoiceElement(LocalizedText label, IChoiceModel model, LocalizedText description)
: base(
MenuPrefabs.Get().NewTextChoiceContainer(out var menuOptionHorizontal),
menuOptionHorizontal,
@@ -36,17 +36,29 @@ public ChoiceElement(string label, IChoiceModel model, string description = "
OnValueChanged += _ => custom.UpdateText();
- LabelText.text = label;
- DescriptionText.text = description;
+ LabelText.LocalizedText = label;
+ DescriptionText.LocalizedText = description;
custom.UpdateText();
}
+ ///
+ /// Construct a ChoiceElement with the given label and model.
+ ///
+ public ChoiceElement(LocalizedText label, IChoiceModel model)
+ : this(label, model, "") { }
+
///
/// Shortcut for building a ChoiceElement from a finite list of values.
///
- public ChoiceElement(string label, List items, string description = "")
+ public ChoiceElement(LocalizedText label, List items, LocalizedText description)
: this(label, ChoiceModels.ForValues(items), description) { }
+ ///
+ /// Shortcut for building a ChoiceElement from a finite list of values.
+ ///
+ public ChoiceElement(LocalizedText label, List items)
+ : this(label, ChoiceModels.ForValues(items), "") { }
+
///
/// The value holder and model underlying this choice element.
///
diff --git a/Elements/DynamicDescriptionChoiceElement.cs b/Elements/DynamicDescriptionChoiceElement.cs
index b1c2864..9283bc3 100644
--- a/Elements/DynamicDescriptionChoiceElement.cs
+++ b/Elements/DynamicDescriptionChoiceElement.cs
@@ -19,17 +19,17 @@ public class DynamicDescriptionChoiceElement : ChoiceElement
{
internal const string RIGHT_DESCRIPTION_NAME = "ModMenu-Right Description";
- ///
+ ///
public DynamicDescriptionChoiceElement(
- string label,
+ LocalizedText label,
IChoiceModel model,
- string description,
- string rightDescription
+ LocalizedText description,
+ LocalizedText rightDescription
)
: base(label, model, description)
{
RightText = SetupRightDescription(DescriptionText, ChoiceText);
- RightText.text = rightDescription;
+ RightText.LocalizedText = rightDescription;
}
///
@@ -40,14 +40,14 @@ string rightDescription
///
/// Function used to determine the description below the choice.
public DynamicDescriptionChoiceElement(
- string label,
+ LocalizedText label,
IChoiceModel model,
- string description,
- Func getRightDescription
+ LocalizedText description,
+ Func getRightDescription
)
: this(label, model, description, getRightDescription(model.Value))
{
- model.OnValueChanged += value => RightText.text = getRightDescription(value);
+ model.OnValueChanged += value => RightText.LocalizedText = getRightDescription(value);
}
///
diff --git a/Elements/KeyBindElement.cs b/Elements/KeyBindElement.cs
index 6c83fe8..a15d1e7 100644
--- a/Elements/KeyBindElement.cs
+++ b/Elements/KeyBindElement.cs
@@ -14,7 +14,7 @@ public class KeyBindElement : SelectableValueElement
///
/// Construct a KeyBindElement with a custom model.
///
- public KeyBindElement(string label, IValueModel model)
+ public KeyBindElement(LocalizedText label, IValueModel model)
: base(
MenuPrefabs.Get().NewKeyBindContainer(out var customMappableKey),
customMappableKey,
@@ -27,13 +27,13 @@ public KeyBindElement(string label, IValueModel model)
KeyBindText = customMappableKey.KeymapText!;
KeyBindImage = customMappableKey.KeymapImage!;
- LabelText.text = label;
+ LabelText.LocalizedText = label;
}
///
/// Construct a KeyBindElement with a default model that accepts any KeyCode.
///
- public KeyBindElement(string label)
+ public KeyBindElement(LocalizedText label)
: this(label, new ValueModel(KeyCode.A)) { }
///
diff --git a/Elements/LocalizedText.cs b/Elements/LocalizedText.cs
new file mode 100644
index 0000000..1dae199
--- /dev/null
+++ b/Elements/LocalizedText.cs
@@ -0,0 +1,62 @@
+using TeamCherry.Localization;
+
+namespace Silksong.ModMenu.Elements;
+
+///
+/// Wrapper class for either text that supports localization, or raw text which does not.
+///
+public class LocalizedText
+{
+ private readonly TeamCherry.Localization.LocalisedString localisedString;
+ private readonly string rawText;
+
+ private LocalizedText(LocalisedString localisedString, string rawText)
+ {
+ this.localisedString = localisedString;
+ this.rawText = rawText;
+ }
+
+ ///
+ /// Get the readable text represented by this object.
+ ///
+ public string Text =>
+ localisedString.IsEmpty
+ ? rawText
+ : Language.Get(localisedString.Key, localisedString.Sheet);
+
+ ///
+ /// Returns true if this object has localization support.
+ ///
+ public bool IsLocalized => !localisedString.IsEmpty;
+
+ ///
+ /// Returns the localized identifier for this object, which may be empty.
+ ///
+ public LocalisedString Localized => localisedString;
+
+ ///
+ /// Represents localized text with the given key and an empty sheet title.
+ ///
+ public static LocalizedText Key(string languageKey) => new(new("", languageKey), "");
+
+ ///
+ /// Represents localized text with the given key.
+ ///
+ public static LocalizedText Key(LocalisedString localisedString) => new(localisedString, "");
+
+ ///
+ /// Represents text with no localization that always renders to the given value.
+ ///
+ public static LocalizedText Raw(string rawText) => new(new(), rawText);
+
+ ///
+ /// Implicit conversion for raw text to un-localized LocalizedText.
+ ///
+ public static implicit operator LocalizedText(string rawText) => Raw(rawText);
+
+ ///
+ /// Implicit conversion from a LocalisedString.
+ ///
+ public static implicit operator LocalizedText(LocalisedString localisedString) =>
+ Key(localisedString);
+}
diff --git a/Elements/LocalizedTextExtensions.cs b/Elements/LocalizedTextExtensions.cs
new file mode 100644
index 0000000..59974f6
--- /dev/null
+++ b/Elements/LocalizedTextExtensions.cs
@@ -0,0 +1,46 @@
+using UnityEngine.UI;
+
+namespace Silksong.ModMenu.Elements;
+
+///
+/// Helper methods for working with LocalizedText.
+///
+public static class LocalizedTextExtensions
+{
+ extension(Text self)
+ {
+ ///
+ /// Helper field to set localized text on a Text component.
+ ///
+ public LocalizedText LocalizedText
+ {
+ get
+ {
+ if (self.TryGetComponent(out var auto) && !auto.text.IsEmpty)
+ return auto.text;
+ else
+ return self.text;
+ }
+ set
+ {
+ if (value.IsLocalized)
+ {
+ if (!self.TryGetComponent(out var auto))
+ {
+ auto = self.gameObject.AddComponent();
+ auto.textField = self;
+ }
+
+ auto.text = value.Localized;
+ auto.RefreshTextFromLocalization();
+ }
+ else
+ {
+ if (self.TryGetComponent(out var auto))
+ UnityEngine.Object.Destroy(auto);
+ self.text = value.Text;
+ }
+ }
+ }
+ }
+}
diff --git a/Elements/SliderElement.cs b/Elements/SliderElement.cs
index 16c6632..513ee74 100644
--- a/Elements/SliderElement.cs
+++ b/Elements/SliderElement.cs
@@ -19,10 +19,10 @@ public class SliderElement : SelectableValueElement
///
/// The label text for the slider.
/// The model for the domain range and underlying value.
- public SliderElement(string label, SliderModel model)
+ public SliderElement(LocalizedText label, SliderModel model)
: base(MenuPrefabs.Get().NewSliderContainer(out var slider), slider, model)
{
- Container.name = label;
+ Container.name = label.Text;
Slider = slider;
var sliderObj = Slider.gameObject;
@@ -57,7 +57,7 @@ public SliderElement(string label, SliderModel model)
}
});
- LabelText.text = label;
+ LabelText.LocalizedText = label;
UpdateValueText();
}
@@ -95,5 +95,5 @@ public override void SetFontSizes(FontSizes fontSizes)
ValueText.fontSize = fontSizes.SliderSize();
}
- private void UpdateValueText() => ValueText.text = SliderModel.DisplayString();
+ private void UpdateValueText() => ValueText.LocalizedText = SliderModel.DisplayString();
}
diff --git a/Elements/TextButton.cs b/Elements/TextButton.cs
index 92eeba7..553f3ce 100644
--- a/Elements/TextButton.cs
+++ b/Elements/TextButton.cs
@@ -15,10 +15,10 @@ public class TextButton : SelectableElement
///
/// Construct a text button with the given text.
///
- public TextButton(string text)
+ public TextButton(LocalizedText text)
: base(MenuPrefabs.Get().NewTextButtonContainer(out var menuButton), menuButton)
{
- Container.name = text;
+ Container.name = text.Text;
MenuButton = menuButton;
MenuButton
@@ -35,7 +35,7 @@ public TextButton(string text)
MenuButton.buttonType = MenuButton.MenuButtonType.Activate;
ButtonText = menuButton.gameObject.FindChild("Menu Button Text")!.GetComponent();
- ButtonText.text = text;
+ ButtonText.LocalizedText = text;
}
///
diff --git a/Elements/TextInput.cs b/Elements/TextInput.cs
index 9ff4def..0a0af3d 100644
--- a/Elements/TextInput.cs
+++ b/Elements/TextInput.cs
@@ -28,7 +28,7 @@ public class TextInput : SelectableValueElement
///
/// Construct a basic text input.
///
- public TextInput(string label, ITextModel model, string description = "")
+ public TextInput(LocalizedText label, ITextModel model, LocalizedText description)
: base(MenuPrefabs.Get().NewTextInputContainer(out var inputField), inputField, model)
{
TextModel = model;
@@ -40,8 +40,8 @@ public TextInput(string label, ITextModel model, string description = "")
OnTextValueChanged += value => InputField.text = value;
- LabelText.text = label;
- DescriptionText.text = description;
+ LabelText.LocalizedText = label;
+ DescriptionText.LocalizedText = description;
if (intTypes.Contains(typeof(T)))
InputField.contentType = InputField.ContentType.IntegerNumber;
@@ -49,6 +49,12 @@ public TextInput(string label, ITextModel model, string description = "")
InputField.contentType = InputField.ContentType.DecimalNumber;
}
+ ///
+ /// Construct a basic text input with no description.
+ ///
+ public TextInput(LocalizedText label, ITextModel model)
+ : this(label, model, "") { }
+
///
/// The value holder and model underlying this choice element.
///
diff --git a/Elements/TextLabel.cs b/Elements/TextLabel.cs
index 88bd189..1271d9a 100644
--- a/Elements/TextLabel.cs
+++ b/Elements/TextLabel.cs
@@ -12,12 +12,12 @@ public class TextLabel : MenuElement
///
/// Construct a label with the given text contents.
///
- public TextLabel(string text)
+ public TextLabel(LocalizedText text)
: base(MenuPrefabs.Get().NewTextLabel())
{
- Container.name = text;
+ Container.name = text.Text;
Text = Container.GetComponent();
- Text.text = text;
+ Text.LocalizedText = text;
}
///
diff --git a/Internal/CustomMenuOptionHorizontal.cs b/Internal/CustomMenuOptionHorizontal.cs
index f4a36d9..450c2fa 100644
--- a/Internal/CustomMenuOptionHorizontal.cs
+++ b/Internal/CustomMenuOptionHorizontal.cs
@@ -1,6 +1,7 @@
using MonoDetour;
using MonoDetour.DetourTypes;
using MonoDetour.HookGen;
+using Silksong.ModMenu.Elements;
using Silksong.ModMenu.Models;
using UnityEngine;
using UnityEngine.EventSystems;
@@ -27,7 +28,7 @@ internal void UpdateText()
if (orig == null)
return;
- orig.optionText.text = Model?.DisplayString() ?? "???";
+ orig.optionText.LocalizedText = Model?.DisplayString() ?? "???";
if (orig.optionText.TryGetComponent(out var align))
align.AlignText();
}
diff --git a/Models/AbstractValueModel.cs b/Models/AbstractValueModel.cs
index 1dc7736..9f69cf8 100644
--- a/Models/AbstractValueModel.cs
+++ b/Models/AbstractValueModel.cs
@@ -1,4 +1,5 @@
using System;
+using Silksong.ModMenu.Elements;
namespace Silksong.ModMenu.Models;
@@ -57,5 +58,5 @@ public T Value
}
///
- public virtual string DisplayString() => $"{GetValue()}";
+ public virtual LocalizedText DisplayString() => $"{GetValue()}";
}
diff --git a/Models/Delegates.cs b/Models/Delegates.cs
index e923489..0eb0705 100644
--- a/Models/Delegates.cs
+++ b/Models/Delegates.cs
@@ -1,6 +1,8 @@
-namespace Silksong.ModMenu.Models;
+using Silksong.ModMenu.Elements;
+
+namespace Silksong.ModMenu.Models;
///
/// Converts 'item' to string with the additional context of an associated index, most likely within a list.
///
-public delegate string IndexedToString(int index, T item);
+public delegate LocalizedText IndexedToString(int index, T item);
diff --git a/Models/IDisplayable.cs b/Models/IDisplayable.cs
index ebe2231..b66ff8e 100644
--- a/Models/IDisplayable.cs
+++ b/Models/IDisplayable.cs
@@ -1,4 +1,6 @@
-namespace Silksong.ModMenu.Models;
+using Silksong.ModMenu.Elements;
+
+namespace Silksong.ModMenu.Models;
///
/// Base interface for all models which ultimately display a string to represent the chosen value.
@@ -8,5 +10,5 @@ public interface IDisplayable
///
/// The UI string to display for this entity.
///
- string DisplayString();
+ LocalizedText DisplayString();
}
diff --git a/Models/IntRangeChoiceModel.cs b/Models/IntRangeChoiceModel.cs
index 2ac12de..686d873 100644
--- a/Models/IntRangeChoiceModel.cs
+++ b/Models/IntRangeChoiceModel.cs
@@ -1,4 +1,5 @@
using System;
+using Silksong.ModMenu.Elements;
namespace Silksong.ModMenu.Models;
@@ -90,10 +91,11 @@ public override bool SetValue(int value)
///
/// A custom display function to use for the selected integer value.
///
- public Func? DisplayFn;
+ public Func? DisplayFn;
///
- public override string DisplayString() => DisplayFn?.Invoke(GetValue()) ?? $"{GetValue()}";
+ public override LocalizedText DisplayString() =>
+ DisplayFn?.Invoke(GetValue()) ?? $"{GetValue()}";
private void ResetParamsInternal(int min, int max, int value)
{
diff --git a/Models/LinearFloatSliderModel.cs b/Models/LinearFloatSliderModel.cs
index 179e564..d07a606 100644
--- a/Models/LinearFloatSliderModel.cs
+++ b/Models/LinearFloatSliderModel.cs
@@ -1,4 +1,5 @@
using System;
+using Silksong.ModMenu.Elements;
using UnityEngine;
namespace Silksong.ModMenu.Models;
@@ -67,5 +68,5 @@ protected override bool GetIndex(float value, out int index)
}
///
- protected override string DefaultDisplayString(int index, float item) => $"{item:0.###}";
+ protected override LocalizedText DefaultDisplayString(int index, float item) => $"{item:0.###}";
}
diff --git a/Models/ListChoiceModel.cs b/Models/ListChoiceModel.cs
index e45b29e..f7e9409 100644
--- a/Models/ListChoiceModel.cs
+++ b/Models/ListChoiceModel.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using Silksong.ModMenu.Elements;
namespace Silksong.ModMenu.Models;
@@ -102,13 +103,13 @@ public override bool SetValue(T value)
public IndexedToString? DisplayFn;
///
- public override string DisplayString() =>
+ public override LocalizedText DisplayString() =>
(DisplayFn ?? DefaultDisplayString).Invoke(Index, GetValue());
///
/// Default string to display, in the absence of a DisplayFn.
///
- protected virtual string DefaultDisplayString(int index, T item) => $"{item}";
+ protected virtual LocalizedText DefaultDisplayString(int index, T item) => $"{item}";
private bool Move(int delta)
{
diff --git a/Models/ParserTextModel.cs b/Models/ParserTextModel.cs
index 398e188..5627ea9 100644
--- a/Models/ParserTextModel.cs
+++ b/Models/ParserTextModel.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using Silksong.ModMenu.Elements;
namespace Silksong.ModMenu.Models;
@@ -109,7 +110,7 @@ public bool SetValue(T value)
}
///
- public string DisplayString() => text;
+ public LocalizedText DisplayString() => text;
///
public T Value
diff --git a/Models/SliderModel.cs b/Models/SliderModel.cs
index 3503912..3008955 100644
--- a/Models/SliderModel.cs
+++ b/Models/SliderModel.cs
@@ -1,4 +1,5 @@
using System;
+using Silksong.ModMenu.Elements;
namespace Silksong.ModMenu.Models;
@@ -89,11 +90,11 @@ public override bool SetValue(T value)
public IndexedToString? DisplayFn;
///
- public override string DisplayString() =>
+ public override LocalizedText DisplayString() =>
(DisplayFn ?? DefaultDisplayString).Invoke(Index, GetValue());
///
/// The default display string for this model, in the absence of a DisplayFn.
///
- protected virtual string DefaultDisplayString(int index, T item) => $"{item}";
+ protected virtual LocalizedText DefaultDisplayString(int index, T item) => $"{item}";
}
diff --git a/README.md b/README.md
index 0110cc7..7128dfe 100644
--- a/README.md
+++ b/README.md
@@ -58,10 +58,7 @@ It is recommended, though not required, that you create new Models every time yo
## Future work
-The ModMenu mod should be considered _unstable_ version 1.0 is released. Breaking API changes may occur in the pursuit of implementing additional features to reach 1.0.
-
-Required for 1.0:
-* Localization
+The ModMenu mod should be considered _unstable_ until version 1.0 is released. Breaking API changes may occur in the pursuit of implementing additional features to reach 1.0.
Optional future work:
* Extending the 'new game' menu with new game modes.