Skip to content

Commit 36eaed5

Browse files
committed
Add support for localized section and enum display names
Consumed SectionDisplayName and ValueDisplayNames properties from IConfigEntry (added in Common) to allow mods to provide localized display text for config section headers and enum dropdown values. - BuildDescription: reads new properties via reflection and passes them through ConfigurationManagerAttributes tags - ComboboxDrawer: checks ValueDisplayNames before falling back to [Description] attributes for enum rendering - SettingEntryBase: added ValueDisplayNames property (auto-copied via the existing reflection-based property join)
1 parent 921548e commit 36eaed5

5 files changed

Lines changed: 162 additions & 4 deletions

File tree

LobCorp.ConfigurationManager.Test/ModTests/ConfigurationManagerTests/LmmConfigurationProviderTests.cs

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,81 @@ public void Save_WithNoLoadedEntries_ShouldNotThrow()
327327
act.Should().NotThrow();
328328
}
329329

330+
[Fact]
331+
public void LoadPersistedValues_EntryWithSectionDisplayName_ShouldAttachCategoryAttribute()
332+
{
333+
var file = CreateTempConfigFile();
334+
var provider = CreateProvider(file);
335+
var entry = new StubConfigEntry
336+
{
337+
ModId = "mod1",
338+
Section = "General",
339+
Key = "Volume",
340+
SettingType = typeof(int),
341+
DefaultValue = 50,
342+
SectionDisplayName = "Localized General",
343+
};
344+
345+
provider.LoadPersistedValues([entry]);
346+
347+
var bound = file.Bind("General", "Volume", 0);
348+
var attrs = bound
349+
.Description.Tags.OfType<global::ConfigurationManager.ConfigurationManagerAttributes>()
350+
.Single();
351+
attrs.Category.Should().Be("Localized General");
352+
}
353+
354+
[Fact]
355+
public void LoadPersistedValues_EntryWithValueDisplayNames_ShouldAttachValueDisplayNames()
356+
{
357+
var file = CreateTempConfigFile();
358+
var provider = CreateProvider(file);
359+
var displayNames = new Dictionary<string, string>
360+
{
361+
{ "Normalized", "Localized Normalized" },
362+
};
363+
var entry = new StubConfigEntry
364+
{
365+
ModId = "mod1",
366+
Section = "General",
367+
Key = "Mode",
368+
SettingType = typeof(int),
369+
DefaultValue = 0,
370+
ValueDisplayNames = displayNames,
371+
};
372+
373+
provider.LoadPersistedValues([entry]);
374+
375+
var bound = file.Bind("General", "Mode", 0);
376+
var attrs = bound
377+
.Description.Tags.OfType<global::ConfigurationManager.ConfigurationManagerAttributes>()
378+
.Single();
379+
attrs.ValueDisplayNames.Should().BeSameAs(displayNames);
380+
}
381+
382+
[Fact]
383+
public void LoadPersistedValues_EntryWithoutSectionDisplayName_ShouldNotAttachCategoryAttribute()
384+
{
385+
var file = CreateTempConfigFile();
386+
var provider = CreateProvider(file);
387+
var entry = new StubConfigEntry
388+
{
389+
ModId = "mod1",
390+
Section = "General",
391+
Key = "Volume",
392+
SettingType = typeof(int),
393+
DefaultValue = 50,
394+
};
395+
396+
provider.LoadPersistedValues([entry]);
397+
398+
var bound = file.Bind("General", "Volume", 0);
399+
bound
400+
.Description.Tags.OfType<global::ConfigurationManager.ConfigurationManagerAttributes>()
401+
.Should()
402+
.BeEmpty();
403+
}
404+
330405
/// <summary>
331406
/// Minimal stub implementing <see cref="IConfigEntry"/> for testing the provider
332407
/// without needing a real <see cref="ModConfig"/>.
@@ -343,6 +418,8 @@ private sealed class StubConfigEntry : IConfigEntry
343418
public Type SettingType { get; set; } = typeof(int);
344419
public object DefaultValue { get; set; } = 0;
345420
public bool UseSlider { get; set; }
421+
public string? SectionDisplayName { get; set; }
422+
public IDictionary<string, string>? ValueDisplayNames { get; set; }
346423
public object Value { get; set; } = 0;
347424
}
348425
}

LobCorp.ConfigurationManager/ConfigurationManagerAttributes.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,5 +128,19 @@ ref bool isCurrentlyAcceptingInput
128128
/// Custom converter from string to setting type for the built-in editor textboxes.
129129
/// </summary>
130130
public System.Func<string, object> StrToObj { get; set; }
131+
132+
/// <summary>
133+
/// Maps value.ToString() keys to localized display names for combobox/flags UI rendering.
134+
/// When set, these override [Description] attributes on enum members.
135+
/// </summary>
136+
[System.Diagnostics.CodeAnalysis.SuppressMessage(
137+
"Usage",
138+
"CA2227:Collection properties should be read only",
139+
Justification = "DTO assigned by mods via object initializer"
140+
)]
141+
public System.Collections.Generic.IDictionary<
142+
string,
143+
string
144+
> ValueDisplayNames { get; set; }
131145
}
132146
}

LobCorp.ConfigurationManager/Implementations/ComboboxDrawer.cs

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ float windowYmax
8383

8484
public static void DrawComboboxField(SettingEntryBase setting, IList list, float windowYmax)
8585
{
86-
var buttonText = ObjectToGuiContent(setting.GetValue());
86+
var buttonText = ObjectToGuiContent(setting.GetValue(), setting.ValueDisplayNames);
8787
var dispRect = GUILayoutUtility.GetRect(
8888
buttonText,
8989
GUI.skin.button,
@@ -95,7 +95,9 @@ public static void DrawComboboxField(SettingEntryBase setting, IList list, float
9595
box = new ComboBox(
9696
dispRect,
9797
buttonText,
98-
list.Cast<object>().Select(ObjectToGuiContent).ToArray(),
98+
list.Cast<object>()
99+
.Select(v => ObjectToGuiContent(v, setting.ValueDisplayNames))
100+
.ToArray(),
99101
GUI.skin.button,
100102
windowYmax
101103
);
@@ -134,7 +136,7 @@ private static void DrawFlagsField(SettingEntryBase setting, IList enumValues, i
134136
var allValues = enumValues
135137
.Cast<Enum>()
136138
.Select(x => new FlagEntry(
137-
x.ToString(),
139+
GetValueDisplayName(x, setting.ValueDisplayNames),
138140
Convert.ToInt64(x, CultureInfo.InvariantCulture)
139141
))
140142
.ToArray();
@@ -202,8 +204,19 @@ ref int index
202204
}
203205
}
204206

205-
private static GUIContent ObjectToGuiContent(object x)
207+
private static GUIContent ObjectToGuiContent(
208+
object x,
209+
IDictionary<string, string> valueDisplayNames = null
210+
)
206211
{
212+
if (
213+
valueDisplayNames != null
214+
&& valueDisplayNames.TryGetValue(x.ToString(), out var displayName)
215+
)
216+
{
217+
return new GUIContent(displayName);
218+
}
219+
207220
if (x is Enum)
208221
{
209222
var enumType = x.GetType();
@@ -221,5 +234,21 @@ private static GUIContent ObjectToGuiContent(object x)
221234
}
222235
return new GUIContent(x.ToString());
223236
}
237+
238+
private static string GetValueDisplayName(
239+
Enum x,
240+
IDictionary<string, string> valueDisplayNames
241+
)
242+
{
243+
if (
244+
valueDisplayNames != null
245+
&& valueDisplayNames.TryGetValue(x.ToString(), out var displayName)
246+
)
247+
{
248+
return displayName;
249+
}
250+
251+
return x.ToString();
252+
}
224253
}
225254
}

LobCorp.ConfigurationManager/Implementations/LmmConfigurationProvider.cs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,26 @@ private static LmmConfigDescription BuildDescription(IConfigEntry entry)
136136
hasAttrs = true;
137137
}
138138

139+
var sectionDisplayName = TryGetPropertyViaReflection<string>(
140+
entry,
141+
"SectionDisplayName"
142+
);
143+
if (sectionDisplayName != null)
144+
{
145+
attrs.Category = sectionDisplayName;
146+
hasAttrs = true;
147+
}
148+
149+
var valueDisplayNames = TryGetPropertyViaReflection<IDictionary<string, string>>(
150+
entry,
151+
"ValueDisplayNames"
152+
);
153+
if (valueDisplayNames != null)
154+
{
155+
attrs.ValueDisplayNames = valueDisplayNames;
156+
hasAttrs = true;
157+
}
158+
139159
if (hasAttrs)
140160
{
141161
tags.Add(attrs);
@@ -198,6 +218,14 @@ out object max
198218
return min != null && max != null;
199219
}
200220

221+
private static T TryGetPropertyViaReflection<T>(IConfigEntry entry, string propertyName)
222+
where T : class
223+
{
224+
var prop = entry.GetType().GetProperty(propertyName);
225+
226+
return prop != null ? prop.GetValue(entry, null) as T : null;
227+
}
228+
201229
private static IAcceptableValue CreateAcceptableValueRange(
202230
Type settingType,
203231
object min,

LobCorp.ConfigurationManager/Implementations/SettingEntryBase.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@ ref bool isCurrentlyAcceptingInput
9090
/// </summary>
9191
public virtual string DispName { get; protected internal set; }
9292

93+
/// <summary>
94+
/// Maps value.ToString() keys to localized display names for combobox/flags UI rendering.
95+
/// </summary>
96+
[System.Diagnostics.CodeAnalysis.SuppressMessage(
97+
"Usage",
98+
"CA2227:Collection properties should be read only",
99+
Justification = "Set via reflection from ConfigurationManagerAttributes"
100+
)]
101+
public IDictionary<string, string> ValueDisplayNames { get; protected set; }
102+
93103
/// <summary>
94104
/// Plugin this setting belongs to.
95105
/// </summary>

0 commit comments

Comments
 (0)