Expose hotkeys to localisation.
Allows the Settings > Hotkeys screen to be localised, including hotkey decriptions, groups and contexts. The hotkey names are exposed to localisation via KeycodeExts. Hotkey modifiers are similarly exposed via ModifersExts. The Settings > Input screen has a Zoom Modifier dropdown, which shows the localised modifier name. The --check-yaml utility command is taught to recognise all hotkey translation, so it can validate their usage.
This commit is contained in:
@@ -21,6 +21,7 @@ using OpenRA.Mods.Common.Scripting;
|
||||
using OpenRA.Mods.Common.Scripting.Global;
|
||||
using OpenRA.Mods.Common.Traits;
|
||||
using OpenRA.Mods.Common.Warheads;
|
||||
using OpenRA.Mods.Common.Widgets.Logic;
|
||||
using OpenRA.Scripting;
|
||||
using OpenRA.Traits;
|
||||
using OpenRA.Widgets;
|
||||
@@ -249,7 +250,7 @@ namespace OpenRA.Mods.Common.Lint
|
||||
testedFields.AddRange(
|
||||
modData.ObjectCreator.GetTypes()
|
||||
.Where(t => t.IsSubclassOf(typeof(TraitInfo)) || t.IsSubclassOf(typeof(Warhead)))
|
||||
.SelectMany(t => t.GetFields().Where(f => f.HasAttribute<FluentReferenceAttribute>())));
|
||||
.SelectMany(t => Utility.GetFields(t).Where(Utility.HasAttribute<FluentReferenceAttribute>)));
|
||||
|
||||
// TODO: linter does not work with LoadUsing
|
||||
GetUsedTranslationKeysFromFieldsWithTranslationReferenceAttribute(
|
||||
@@ -278,6 +279,36 @@ namespace OpenRA.Mods.Common.Lint
|
||||
GetUsedTranslationKeysFromFieldsWithTranslationReferenceAttribute(
|
||||
usedKeys, testedFields, Utility.GetFields(typeof(ModContent.ModPackage)), modContent.Packages.Values);
|
||||
|
||||
GetUsedTranslationKeysFromFieldsWithTranslationReferenceAttribute(
|
||||
usedKeys, testedFields, Utility.GetFields(typeof(HotkeyDefinition)), modData.Hotkeys.Definitions);
|
||||
|
||||
// All keycodes and modifiers should be marked as used, as they can all be configured for use at hotkeys at runtime.
|
||||
GetUsedTranslationKeysFromFieldsWithTranslationReferenceAttribute(
|
||||
usedKeys, testedFields, Utility.GetFields(typeof(KeycodeExts)).Concat(Utility.GetFields(typeof(ModifiersExts))), new[] { (object)null });
|
||||
|
||||
foreach (var filename in modData.Manifest.ChromeLayout)
|
||||
CheckHotkeysSettingsLogic(usedKeys, MiniYaml.FromStream(modData.DefaultFileSystem.Open(filename), filename));
|
||||
|
||||
static void CheckHotkeysSettingsLogic(Keys usedKeys, IEnumerable<MiniYamlNode> nodes)
|
||||
{
|
||||
foreach (var node in nodes)
|
||||
{
|
||||
if (node.Value.Nodes != null)
|
||||
CheckHotkeysSettingsLogic(usedKeys, node.Value.Nodes);
|
||||
|
||||
if (node.Key != "Logic" || node?.Value.Value != "HotkeysSettingsLogic")
|
||||
continue;
|
||||
|
||||
var hotkeyGroupsNode = node.Value.NodeWithKeyOrDefault("HotkeyGroups");
|
||||
if (hotkeyGroupsNode == null)
|
||||
continue;
|
||||
|
||||
var hotkeyGroupsKeys = hotkeyGroupsNode?.Value.Nodes.Select(n => n.Key);
|
||||
foreach (var key in hotkeyGroupsKeys)
|
||||
usedKeys.Add(key, new FluentReferenceAttribute(), $"`{nameof(HotkeysSettingsLogic)}.HotkeyGroups`");
|
||||
}
|
||||
}
|
||||
|
||||
return (usedKeys, testedFields);
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,9 @@ namespace OpenRA.Mods.Common.Lint
|
||||
return expr != null ? expr.Variables : Enumerable.Empty<string>();
|
||||
}
|
||||
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<,>))
|
||||
if (type.IsGenericType &&
|
||||
(type.GetGenericTypeDefinition() == typeof(Dictionary<,>) ||
|
||||
type.GetGenericTypeDefinition() == typeof(IReadOnlyDictionary<,>)))
|
||||
{
|
||||
// Use an intermediate list to cover the unlikely case where both keys and values are lintable.
|
||||
var dictionaryValues = new List<string>();
|
||||
@@ -60,6 +62,9 @@ namespace OpenRA.Mods.Common.Lint
|
||||
"Dictionary<string, T> (LintDictionaryReference.Keys)",
|
||||
"Dictionary<T, string> (LintDictionaryReference.Values)",
|
||||
"Dictionary<T, IEnumerable<string>> (LintDictionaryReference.Values)",
|
||||
"IReadOnlyDictionary<string, T> (LintDictionaryReference.Keys)",
|
||||
"IReadOnlyDictionary<T, string> (LintDictionaryReference.Values)",
|
||||
"IReadOnlyDictionary<T, IEnumerable<string>> (LintDictionaryReference.Values)",
|
||||
"BooleanExpression", "IntegerExpression"
|
||||
};
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
|
||||
.Where(t => t.Name.EndsWith("Widget", StringComparison.InvariantCulture) && t.IsSubclassOf(typeof(Widget)))
|
||||
.ToDictionary(
|
||||
t => t.Name[..^6],
|
||||
t => t.GetFields().Where(f => f.HasAttribute<FluentReferenceAttribute>()).Select(f => f.Name).ToArray())
|
||||
t => Utility.GetFields(t).Where(Utility.HasAttribute<FluentReferenceAttribute>).Select(f => f.Name).ToArray())
|
||||
.Where(t => t.Value.Length > 0)
|
||||
.ToDictionary(t => t.Key, t => t.Value);
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ namespace OpenRA.Mods.Common.UtilityCommands
|
||||
.Where(t => t.Name.EndsWith("Info", StringComparison.InvariantCulture) && t.IsSubclassOf(typeof(TraitInfo)))
|
||||
.ToDictionary(
|
||||
t => t.Name[..^4],
|
||||
t => t.GetFields().Where(f => f.HasAttribute<FluentReferenceAttribute>()).Select(f => f.Name).ToArray())
|
||||
t => Utility.GetFields(t).Where(Utility.HasAttribute<FluentReferenceAttribute>).Select(f => f.Name).ToArray())
|
||||
.Where(t => t.Value.Length > 0)
|
||||
.ToDictionary(t => t.Key, t => t.Value);
|
||||
|
||||
|
||||
@@ -25,6 +25,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
[FluentReference("key", "context")]
|
||||
const string DuplicateNotice = "label-duplicate-notice";
|
||||
|
||||
[FluentReference]
|
||||
const string AnyContext = "hotkey-context-any";
|
||||
|
||||
readonly ModData modData;
|
||||
readonly Dictionary<string, MiniYaml> logicArgs;
|
||||
|
||||
@@ -37,8 +40,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
bool isHotkeyValid;
|
||||
bool isHotkeyDefault;
|
||||
|
||||
string currentContext = "Any";
|
||||
readonly HashSet<string> contexts = new() { "Any" };
|
||||
string currentContext = AnyContext;
|
||||
readonly HashSet<string> contexts = new() { AnyContext };
|
||||
readonly Dictionary<string, HashSet<string>> hotkeyGroups = new();
|
||||
TextFieldWidget filterInput;
|
||||
|
||||
@@ -66,7 +69,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
key.Id = hd.Name;
|
||||
key.IsVisible = () => true;
|
||||
|
||||
key.Get<LabelWidget>("FUNCTION").GetText = () => hd.Description + ":";
|
||||
var desc = FluentProvider.GetString(hd.Description) + ":";
|
||||
key.Get<LabelWidget>("FUNCTION").GetText = () => desc;
|
||||
|
||||
var remapButton = key.Get<ButtonWidget>("HOTKEY");
|
||||
WidgetUtils.TruncateButtonToTooltip(remapButton, modData.Hotkeys[hd.Name].GetValue().DisplayString());
|
||||
@@ -192,7 +196,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
continue;
|
||||
|
||||
var header = headerTemplate.Clone();
|
||||
header.Get<LabelWidget>("LABEL").GetText = () => hg.Key;
|
||||
var groupName = FluentProvider.GetString(hg.Key);
|
||||
header.Get<LabelWidget>("LABEL").GetText = () => groupName;
|
||||
hotkeyList.AddChild(header);
|
||||
|
||||
var added = new HashSet<HotkeyDefinition>();
|
||||
@@ -220,7 +225,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
void InitHotkeyRemapDialog(Widget panel)
|
||||
{
|
||||
var label = panel.Get<LabelWidget>("HOTKEY_LABEL");
|
||||
var labelText = new CachedTransform<HotkeyDefinition, string>(hd => hd?.Description + ":");
|
||||
var labelText = new CachedTransform<HotkeyDefinition, string>(
|
||||
hd => (hd != null ? FluentProvider.GetString(hd.Description) : "") + ":");
|
||||
label.IsVisible = () => selectedHotkeyDefinition != null;
|
||||
label.GetText = () => labelText.Update(selectedHotkeyDefinition);
|
||||
|
||||
@@ -228,10 +234,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
duplicateNotice.TextColor = ChromeMetrics.Get<Color>("NoticeErrorColor");
|
||||
duplicateNotice.IsVisible = () => !isHotkeyValid;
|
||||
var duplicateNoticeText = new CachedTransform<HotkeyDefinition, string>(hd =>
|
||||
hd != null ?
|
||||
FluentProvider.GetString(DuplicateNotice,
|
||||
"key", hd.Description,
|
||||
"context", hd.Contexts.First(c => selectedHotkeyDefinition.Contexts.Contains(c))) : "");
|
||||
hd != null
|
||||
? FluentProvider.GetString(
|
||||
DuplicateNotice,
|
||||
"key", FluentProvider.GetString(hd.Description),
|
||||
"context", FluentProvider.GetString(hd.Contexts.First(c => selectedHotkeyDefinition.Contexts.Contains(c))))
|
||||
: "");
|
||||
duplicateNotice.GetText = () => duplicateNoticeText.Update(duplicateHotkeyDefinition);
|
||||
|
||||
var originalNotice = panel.Get<LabelWidget>("ORIGINAL_NOTICE");
|
||||
@@ -328,8 +336,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
{
|
||||
var filter = filterInput.Text;
|
||||
var isFilteredByName = string.IsNullOrWhiteSpace(filter) ||
|
||||
hd.Description.Contains(filter, StringComparison.CurrentCultureIgnoreCase);
|
||||
var isFilteredByContext = currentContext == "Any" || hd.Contexts.Contains(currentContext);
|
||||
FluentProvider.GetString(hd.Description).Contains(filter, StringComparison.CurrentCultureIgnoreCase);
|
||||
var isFilteredByContext = currentContext == AnyContext || hd.Contexts.Contains(currentContext);
|
||||
|
||||
return isFilteredByName && isFilteredByContext;
|
||||
}
|
||||
@@ -357,9 +365,9 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
static string GetContextDisplayName(string context)
|
||||
{
|
||||
if (string.IsNullOrEmpty(context))
|
||||
return "Any";
|
||||
context = AnyContext;
|
||||
|
||||
return context;
|
||||
return FluentProvider.GetString(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,21 +36,6 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
[FluentReference]
|
||||
const string Joystick = "options-mouse-scroll-type.joystick";
|
||||
|
||||
[FluentReference]
|
||||
const string Alt = "options-zoom-modifier.alt";
|
||||
|
||||
[FluentReference]
|
||||
const string Ctrl = "options-zoom-modifier.ctrl";
|
||||
|
||||
[FluentReference]
|
||||
const string Meta = "options-zoom-modifier.meta";
|
||||
|
||||
[FluentReference]
|
||||
const string Shift = "options-zoom-modifier.shift";
|
||||
|
||||
[FluentReference]
|
||||
const string None = "options-zoom-modifier.none";
|
||||
|
||||
static InputSettingsLogic() { }
|
||||
|
||||
readonly string classic;
|
||||
@@ -205,11 +190,11 @@ namespace OpenRA.Mods.Common.Widgets.Logic
|
||||
{
|
||||
var options = new Dictionary<string, Modifiers>()
|
||||
{
|
||||
{ FluentProvider.GetString(Alt), Modifiers.Alt },
|
||||
{ FluentProvider.GetString(Ctrl), Modifiers.Ctrl },
|
||||
{ FluentProvider.GetString(Meta), Modifiers.Meta },
|
||||
{ FluentProvider.GetString(Shift), Modifiers.Shift },
|
||||
{ FluentProvider.GetString(None), Modifiers.None }
|
||||
{ ModifiersExts.DisplayString(Modifiers.Alt), Modifiers.Alt },
|
||||
{ ModifiersExts.DisplayString(Modifiers.Ctrl), Modifiers.Ctrl },
|
||||
{ ModifiersExts.DisplayString(Modifiers.Meta), Modifiers.Meta },
|
||||
{ ModifiersExts.DisplayString(Modifiers.Shift), Modifiers.Shift },
|
||||
{ ModifiersExts.DisplayString(Modifiers.None), Modifiers.None }
|
||||
};
|
||||
|
||||
ScrollItemWidget SetupItem(string o, ScrollItemWidget itemTemplate)
|
||||
|
||||
Reference in New Issue
Block a user