Add a Fluent based translation system.

This commit is contained in:
Matthias Mailänder
2021-04-24 12:39:06 +02:00
committed by teinarss
parent f1a9a5180d
commit 1f01d0b6b1
8 changed files with 169 additions and 1 deletions

View File

@@ -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)

View File

@@ -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");

View File

@@ -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);
}
} }
} }

View File

@@ -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" />

View File

@@ -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
View 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;
}
}
}

View File

@@ -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

View File

@@ -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);
}
} }
} }