Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added l10n support to server messages #19847

Merged
merged 2 commits into from Apr 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
136 changes: 136 additions & 0 deletions OpenRA.Game/Network/LocalizedMessage.cs
@@ -0,0 +1,136 @@
#region Copyright & License Information
/*
* Copyright 2007-2022 The OpenRA Developers (see AUTHORS)
* This file is part of OpenRA, which is free software. It is made
* available to you under the terms of the GNU General Public License
* as published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version. For more
* information, see COPYING.
*/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using Fluent.Net;
using OpenRA.Widgets;

namespace OpenRA.Network
{
public class FluentArgument
{
[Flags]
public enum FluentArgumentType
{
String = 0,
Number = 1,
}

public readonly string Key;
public readonly string Value;
public readonly FluentArgumentType Type;

public FluentArgument() { }

public FluentArgument(string key, object value)
{
Key = key;
Mailaender marked this conversation as resolved.
Show resolved Hide resolved
Value = value.ToString();
Type = GetFluentArgumentType(value);
}

static FluentArgumentType GetFluentArgumentType(object value)
{
switch (value)
{
case byte _:
case sbyte _:
case short _:
case uint _:
case int _:
case long _:
case ulong _:
case float _:
case double _:
case decimal _:
return FluentArgumentType.Number;
default:
return FluentArgumentType.String;
}
}
}

public class LocalizedMessage
{
public const int ProtocolVersion = 1;

public readonly string Key;

[FieldLoader.LoadUsing(nameof(LoadArguments))]
public readonly FluentArgument[] Arguments;

static object LoadArguments(MiniYaml yaml)
{
var arguments = new List<FluentArgument>();
var argumentsNode = yaml.Nodes.FirstOrDefault(n => n.Key == "Arguments");
if (argumentsNode != null)
{
var regex = new Regex(@"Argument@\d+");
foreach (var argument in argumentsNode.Value.Nodes)
if (regex.IsMatch(argument.Key))
arguments.Add(FieldLoader.Load<FluentArgument>(argument.Value));
}

return arguments.ToArray();
}

static readonly string[] SerializeFields = { "Key" };

public LocalizedMessage(MiniYaml yaml)
{
FieldLoader.Load(this, yaml);
}

public LocalizedMessage(string key, Dictionary<string, object> arguments = null)
{
Key = key;
Arguments = arguments?.Select(a => new FluentArgument(a.Key, a.Value)).ToArray();
}

public string Serialize()
{
var root = new List<MiniYamlNode>() { new MiniYamlNode("Protocol", ProtocolVersion.ToString()) };
foreach (var field in SerializeFields)
root.Add(FieldSaver.SaveField(this, field));

if (Arguments != null)
{
var argumentsNode = new MiniYaml("");
var i = 0;
foreach (var argument in Arguments)
argumentsNode.Nodes.Add(new MiniYamlNode("Argument@" + i++, FieldSaver.Save(argument)));

root.Add(new MiniYamlNode("Arguments", argumentsNode));
}

return new MiniYaml("", root)
.ToLines("LocalizedMessage")
.JoinWith("\n");
}

public string Translate()
{
var argumentDictionary = new Dictionary<string, object>();
foreach (var argument in Arguments)
{
if (argument.Type == FluentArgument.FluentArgumentType.Number)
argumentDictionary.Add(argument.Key, new FluentNumber(argument.Value));
else
argumentDictionary.Add(argument.Key, new FluentString(argument.Value));
Mailaender marked this conversation as resolved.
Show resolved Hide resolved
}

return Ui.Translate(Key, argumentDictionary);
}
}
}
2 changes: 1 addition & 1 deletion OpenRA.Game/Network/Order.cs
Expand Up @@ -73,7 +73,7 @@ public sealed class Order
public bool SuppressVisualFeedback;
public ref readonly Target VisualFeedbackTarget => ref visualFeedbackTarget;

public Player Player => Subject != null ? Subject.Owner : null;
public Player Player => Subject?.Owner;

readonly Target target;
readonly Target visualFeedbackTarget;
Expand Down
20 changes: 18 additions & 2 deletions OpenRA.Game/Network/UnitOrders.cs
Expand Up @@ -22,7 +22,7 @@ public static class UnitOrders

static Player FindPlayerByClient(this World world, Session.Client c)
{
return world.Players.FirstOrDefault(p => (p.ClientIndex == c.Index && p.PlayerReference.Playable));
return world.Players.FirstOrDefault(p => p.ClientIndex == c.Index && p.PlayerReference.Playable);
}

internal static void ProcessOrder(OrderManager orderManager, World world, int clientId, Order order)
Expand All @@ -34,6 +34,22 @@ internal static void ProcessOrder(OrderManager orderManager, World world, int cl
TextNotificationsManager.AddSystemLine(order.TargetString);
break;

// Client side translated server message
case "LocalizedMessage":
{
if (string.IsNullOrEmpty(order.TargetString))
break;

var yaml = MiniYaml.FromString(order.TargetString);
foreach (var node in yaml)
{
var localizedMessage = new LocalizedMessage(node.Value);
TextNotificationsManager.AddSystemLine(localizedMessage.Translate());
}

break;
}

case "DisableChatEntry":
{
// Order must originate from the server
Expand Down Expand Up @@ -63,7 +79,7 @@ internal static void ProcessOrder(OrderManager orderManager, World world, int cl
// ExtraData 0 means this is a normal chat order, everything else is team chat
if (order.ExtraData == 0)
{
var p = world != null ? world.FindPlayerByClient(client) : null;
var p = world?.FindPlayerByClient(client);
var suffix = (p != null && p.WinState == WinState.Lost) ? " (Dead)" : "";
suffix = client.IsObserver ? " (Spectator)" : suffix;

Expand Down
2 changes: 1 addition & 1 deletion OpenRA.Game/Server/Connection.cs
Expand Up @@ -171,7 +171,7 @@ void SendReceiveLoop(object s)
var sent = socket.Send(data, start, length - start, SocketFlags.None, out var error);
if (error == SocketError.WouldBlock)
{
Log.Write("server", "Non-blocking send of {0} bytes failed. Falling back to blocking send.", length - start);
Log.Write("server", $"Non-blocking send of {length - start} bytes failed. Falling back to blocking send.");
socket.Blocking = true;
sent = socket.Send(data, start, length - start, SocketFlags.None);
socket.Blocking = false;
Expand Down
2 changes: 1 addition & 1 deletion OpenRA.Game/Server/ProtocolVersion.cs
Expand Up @@ -77,6 +77,6 @@ public static class ProtocolVersion
// The protocol for server and world orders
// This applies after the handshake has completed, and is provided to support
// alternative server implementations that wish to support multiple versions in parallel
public const int Orders = 18;
public const int Orders = 19;
}
}