Add a Fluent based translation system.
This commit is contained in:
committed by
teinarss
parent
f1a9a5180d
commit
1f01d0b6b1
@@ -408,6 +408,7 @@ namespace OpenRA
|
|||||||
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Title, mod.Value.Version);
|
Console.WriteLine("\t{0}: {1} ({2})", mod.Key, mod.Value.Title, mod.Value.Version);
|
||||||
|
|
||||||
InitializeMod(modID, args);
|
InitializeMod(modID, args);
|
||||||
|
Ui.InitializeTranslation();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void InitializeMod(string mod, Arguments args)
|
public static void InitializeMod(string mod, Arguments args)
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ namespace OpenRA
|
|||||||
public readonly string[]
|
public readonly string[]
|
||||||
Rules, ServerTraits,
|
Rules, ServerTraits,
|
||||||
Sequences, ModelSequences, Cursors, Chrome, Assemblies, ChromeLayout,
|
Sequences, ModelSequences, Cursors, Chrome, Assemblies, ChromeLayout,
|
||||||
Weapons, Voices, Notifications, Music, TileSets,
|
Weapons, Voices, Notifications, Music, Translations, TileSets,
|
||||||
ChromeMetrics, MapCompatibility, Missions, Hotkeys;
|
ChromeMetrics, MapCompatibility, Missions, Hotkeys;
|
||||||
|
|
||||||
public readonly IReadOnlyDictionary<string, string> Packages;
|
public readonly IReadOnlyDictionary<string, string> Packages;
|
||||||
@@ -141,6 +141,7 @@ namespace OpenRA
|
|||||||
Voices = YamlList(yaml, "Voices");
|
Voices = YamlList(yaml, "Voices");
|
||||||
Notifications = YamlList(yaml, "Notifications");
|
Notifications = YamlList(yaml, "Notifications");
|
||||||
Music = YamlList(yaml, "Music");
|
Music = YamlList(yaml, "Music");
|
||||||
|
Translations = YamlList(yaml, "Translations");
|
||||||
TileSets = YamlList(yaml, "TileSets");
|
TileSets = YamlList(yaml, "TileSets");
|
||||||
ChromeMetrics = YamlList(yaml, "ChromeMetrics");
|
ChromeMetrics = YamlList(yaml, "ChromeMetrics");
|
||||||
Missions = YamlList(yaml, "Missions");
|
Missions = YamlList(yaml, "Missions");
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ using OpenRA.Graphics;
|
|||||||
using OpenRA.Primitives;
|
using OpenRA.Primitives;
|
||||||
using OpenRA.Support;
|
using OpenRA.Support;
|
||||||
using OpenRA.Traits;
|
using OpenRA.Traits;
|
||||||
|
using OpenRA.Widgets;
|
||||||
|
|
||||||
namespace OpenRA
|
namespace OpenRA
|
||||||
{
|
{
|
||||||
@@ -164,6 +165,7 @@ namespace OpenRA
|
|||||||
new MapField("Bounds"),
|
new MapField("Bounds"),
|
||||||
new MapField("Visibility"),
|
new MapField("Visibility"),
|
||||||
new MapField("Categories"),
|
new MapField("Categories"),
|
||||||
|
new MapField("Translations", required: false),
|
||||||
new MapField("LockPreview", required: false, ignoreIfValue: "False"),
|
new MapField("LockPreview", required: false, ignoreIfValue: "False"),
|
||||||
new MapField("Players", "PlayerDefinitions"),
|
new MapField("Players", "PlayerDefinitions"),
|
||||||
new MapField("Actors", "ActorDefinitions"),
|
new MapField("Actors", "ActorDefinitions"),
|
||||||
@@ -189,6 +191,7 @@ namespace OpenRA
|
|||||||
public Rectangle Bounds;
|
public Rectangle Bounds;
|
||||||
public MapVisibility Visibility = MapVisibility.Lobby;
|
public MapVisibility Visibility = MapVisibility.Lobby;
|
||||||
public string[] Categories = { "Conquest" };
|
public string[] Categories = { "Conquest" };
|
||||||
|
public string[] Translations;
|
||||||
|
|
||||||
public int2 MapSize { get; private set; }
|
public int2 MapSize { get; private set; }
|
||||||
|
|
||||||
@@ -246,6 +249,8 @@ namespace OpenRA
|
|||||||
CellLayer<List<MPos>> inverseCellProjection;
|
CellLayer<List<MPos>> inverseCellProjection;
|
||||||
CellLayer<byte> projectedHeight;
|
CellLayer<byte> projectedHeight;
|
||||||
|
|
||||||
|
internal Translation Translation;
|
||||||
|
|
||||||
public static string ComputeUID(IReadOnlyPackage package)
|
public static string ComputeUID(IReadOnlyPackage package)
|
||||||
{
|
{
|
||||||
// UID is calculated by taking an SHA1 of the yaml and binary data
|
// UID is calculated by taking an SHA1 of the yaml and binary data
|
||||||
@@ -418,6 +423,8 @@ namespace OpenRA
|
|||||||
|
|
||||||
Rules.Sequences.Preload();
|
Rules.Sequences.Preload();
|
||||||
|
|
||||||
|
Translation = new Translation(Game.Settings.Player.Language, Translations, this);
|
||||||
|
|
||||||
var tl = new MPos(0, 0).ToCPos(this);
|
var tl = new MPos(0, 0).ToCPos(this);
|
||||||
var br = new MPos(MapSize.X - 1, MapSize.Y - 1).ToCPos(this);
|
var br = new MPos(MapSize.X - 1, MapSize.Y - 1).ToCPos(this);
|
||||||
AllCells = new CellRegion(Grid.Type, tl, br);
|
AllCells = new CellRegion(Grid.Type, tl, br);
|
||||||
@@ -1307,5 +1314,13 @@ namespace OpenRA
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string Translate(string key, IDictionary<string, object> args = null, string attribute = null)
|
||||||
|
{
|
||||||
|
if (Translation.GetFormattedMessage(key, args, attribute) == key)
|
||||||
|
return Ui.Translate(key, args, attribute);
|
||||||
|
|
||||||
|
return Translation.GetFormattedMessage(key, args, attribute);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
|
<PackageReference Include="System.Runtime.Loader" Version="4.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Fluent.Net" Version="1.0.50" />
|
||||||
<PackageReference Include="OpenRA-Eluant" Version="1.0.18" />
|
<PackageReference Include="OpenRA-Eluant" Version="1.0.18" />
|
||||||
<PackageReference Include="Mono.NAT" Version="3.0.1" />
|
<PackageReference Include="Mono.NAT" Version="3.0.1" />
|
||||||
<PackageReference Include="SharpZipLib" Version="1.3.1" />
|
<PackageReference Include="SharpZipLib" Version="1.3.1" />
|
||||||
|
|||||||
@@ -221,6 +221,7 @@ namespace OpenRA
|
|||||||
public Color Color = Color.FromArgb(200, 32, 32);
|
public Color Color = Color.FromArgb(200, 32, 32);
|
||||||
public string LastServer = "localhost:1234";
|
public string LastServer = "localhost:1234";
|
||||||
public Color[] CustomColors = { };
|
public Color[] CustomColors = { };
|
||||||
|
public string Language = "en";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class GameSettings
|
public class GameSettings
|
||||||
|
|||||||
121
OpenRA.Game/Translation.cs
Normal file
121
OpenRA.Game/Translation.cs
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
#region Copyright & License Information
|
||||||
|
/*
|
||||||
|
* Copyright 2007-2020 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.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using Fluent.Net;
|
||||||
|
using Fluent.Net.RuntimeAst;
|
||||||
|
using OpenRA.FileSystem;
|
||||||
|
|
||||||
|
namespace OpenRA
|
||||||
|
{
|
||||||
|
public class Translation
|
||||||
|
{
|
||||||
|
readonly IEnumerable<MessageContext> messageContexts;
|
||||||
|
|
||||||
|
public Translation(string language, string[] translations, IReadOnlyFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
if (translations == null || !translations.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
|
messageContexts = GetMessageContext(language, translations, fileSystem).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
IEnumerable<MessageContext> GetMessageContext(string language, string[] translations, IReadOnlyFileSystem fileSystem)
|
||||||
|
{
|
||||||
|
var backfall = translations.Where(t => t.EndsWith("en.ftl"));
|
||||||
|
var paths = translations.Where(t => t.EndsWith(language + ".ftl"));
|
||||||
|
foreach (var path in paths.Concat(backfall))
|
||||||
|
{
|
||||||
|
var stream = fileSystem.Open(path);
|
||||||
|
using (var reader = new StreamReader(stream))
|
||||||
|
{
|
||||||
|
var options = new MessageContextOptions { UseIsolating = false };
|
||||||
|
var messageContext = new MessageContext(language, options);
|
||||||
|
var errors = messageContext.AddMessages(reader);
|
||||||
|
foreach (var error in errors)
|
||||||
|
Log.Write("debug", error.ToString());
|
||||||
|
|
||||||
|
yield return messageContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetFormattedMessage(string key, IDictionary<string, object> args = null, string attribute = null)
|
||||||
|
{
|
||||||
|
if (key == null)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
foreach (var messageContext in messageContexts)
|
||||||
|
{
|
||||||
|
var message = messageContext.GetMessage(key);
|
||||||
|
if (message != null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(attribute))
|
||||||
|
return messageContext.Format(message, args);
|
||||||
|
else
|
||||||
|
return messageContext.Format(message.Attributes[attribute], args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetAttribute(string key, string attribute)
|
||||||
|
{
|
||||||
|
if (key == null)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
foreach (var messageContext in messageContexts)
|
||||||
|
{
|
||||||
|
var message = messageContext.GetMessage(key);
|
||||||
|
if (message != null && message.Attributes != null && message.Attributes.ContainsKey(attribute))
|
||||||
|
{
|
||||||
|
var node = message.Attributes[attribute];
|
||||||
|
var stringLiteral = (StringLiteral)node;
|
||||||
|
return stringLiteral.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adapted from Fluent.Net.SimpleExample.TranslationService by Mark Weaver
|
||||||
|
public static Dictionary<string, object> Arguments(string name, object value, params object[] args)
|
||||||
|
{
|
||||||
|
if (args.Length % 2 != 0)
|
||||||
|
throw new ArgumentException("Expected a comma separated list of name, value arguments"
|
||||||
|
+ "but the number of arguments is not a multiple of two", nameof(args));
|
||||||
|
|
||||||
|
var argumentDictionary = new Dictionary<string, object> { { name, value } };
|
||||||
|
|
||||||
|
for (var i = 0; i < args.Length; i += 2)
|
||||||
|
{
|
||||||
|
name = args[i] as string;
|
||||||
|
if (string.IsNullOrEmpty(name))
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Expected the argument at index {0} to be a non-empty string".F(i),
|
||||||
|
nameof(args));
|
||||||
|
}
|
||||||
|
|
||||||
|
value = args[i + 1];
|
||||||
|
if (value == null)
|
||||||
|
throw new ArgumentNullException("args", "Expected the argument at index {0} to be a non-null value".F(i + 1));
|
||||||
|
|
||||||
|
argumentDictionary.Add(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return argumentDictionary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,6 +32,8 @@ namespace OpenRA.Widgets
|
|||||||
public static Widget KeyboardFocusWidget;
|
public static Widget KeyboardFocusWidget;
|
||||||
public static Widget MouseOverWidget;
|
public static Widget MouseOverWidget;
|
||||||
|
|
||||||
|
internal static Translation Translation;
|
||||||
|
|
||||||
public static void CloseWindow()
|
public static void CloseWindow()
|
||||||
{
|
{
|
||||||
if (WindowList.Count > 0)
|
if (WindowList.Count > 0)
|
||||||
@@ -155,6 +157,27 @@ namespace OpenRA.Widgets
|
|||||||
HandleInput(new MouseInput(MouseInputEvent.Move, MouseButton.None,
|
HandleInput(new MouseInput(MouseInputEvent.Move, MouseButton.None,
|
||||||
Viewport.LastMousePos, int2.Zero, Modifiers.None, 0));
|
Viewport.LastMousePos, int2.Zero, Modifiers.None, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void InitializeTranslation()
|
||||||
|
{
|
||||||
|
Translation = new Translation(Game.Settings.Player.Language, Game.ModData.Manifest.Translations, Game.ModData.DefaultFileSystem);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Translate(string key, IDictionary<string, object> args = null, string attribute = null)
|
||||||
|
{
|
||||||
|
if (Translation == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return Translation.GetFormattedMessage(key, args, attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string TranslationAttribute(string key, string attribute = null)
|
||||||
|
{
|
||||||
|
if (Translation == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return Translation.GetAttribute(key, attribute);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ChromeLogic : IDisposable
|
public class ChromeLogic : IDisposable
|
||||||
|
|||||||
@@ -31,5 +31,10 @@ namespace OpenRA.Mods.Common.Scripting.Global
|
|||||||
var c = color.HasValue ? color.Value : Color.White;
|
var c = color.HasValue ? color.Value : Color.White;
|
||||||
luaLabel.GetColor = () => c;
|
luaLabel.GetColor = () => c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string Translate(string text)
|
||||||
|
{
|
||||||
|
return Context.World.Map.Translate(text);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user