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);
|
||||
|
||||
InitializeMod(modID, args);
|
||||
Ui.InitializeTranslation();
|
||||
}
|
||||
|
||||
public static void InitializeMod(string mod, Arguments args)
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace OpenRA
|
||||
public readonly string[]
|
||||
Rules, ServerTraits,
|
||||
Sequences, ModelSequences, Cursors, Chrome, Assemblies, ChromeLayout,
|
||||
Weapons, Voices, Notifications, Music, TileSets,
|
||||
Weapons, Voices, Notifications, Music, Translations, TileSets,
|
||||
ChromeMetrics, MapCompatibility, Missions, Hotkeys;
|
||||
|
||||
public readonly IReadOnlyDictionary<string, string> Packages;
|
||||
@@ -141,6 +141,7 @@ namespace OpenRA
|
||||
Voices = YamlList(yaml, "Voices");
|
||||
Notifications = YamlList(yaml, "Notifications");
|
||||
Music = YamlList(yaml, "Music");
|
||||
Translations = YamlList(yaml, "Translations");
|
||||
TileSets = YamlList(yaml, "TileSets");
|
||||
ChromeMetrics = YamlList(yaml, "ChromeMetrics");
|
||||
Missions = YamlList(yaml, "Missions");
|
||||
|
||||
@@ -21,6 +21,7 @@ using OpenRA.Graphics;
|
||||
using OpenRA.Primitives;
|
||||
using OpenRA.Support;
|
||||
using OpenRA.Traits;
|
||||
using OpenRA.Widgets;
|
||||
|
||||
namespace OpenRA
|
||||
{
|
||||
@@ -164,6 +165,7 @@ namespace OpenRA
|
||||
new MapField("Bounds"),
|
||||
new MapField("Visibility"),
|
||||
new MapField("Categories"),
|
||||
new MapField("Translations", required: false),
|
||||
new MapField("LockPreview", required: false, ignoreIfValue: "False"),
|
||||
new MapField("Players", "PlayerDefinitions"),
|
||||
new MapField("Actors", "ActorDefinitions"),
|
||||
@@ -189,6 +191,7 @@ namespace OpenRA
|
||||
public Rectangle Bounds;
|
||||
public MapVisibility Visibility = MapVisibility.Lobby;
|
||||
public string[] Categories = { "Conquest" };
|
||||
public string[] Translations;
|
||||
|
||||
public int2 MapSize { get; private set; }
|
||||
|
||||
@@ -246,6 +249,8 @@ namespace OpenRA
|
||||
CellLayer<List<MPos>> inverseCellProjection;
|
||||
CellLayer<byte> projectedHeight;
|
||||
|
||||
internal Translation Translation;
|
||||
|
||||
public static string ComputeUID(IReadOnlyPackage package)
|
||||
{
|
||||
// UID is calculated by taking an SHA1 of the yaml and binary data
|
||||
@@ -418,6 +423,8 @@ namespace OpenRA
|
||||
|
||||
Rules.Sequences.Preload();
|
||||
|
||||
Translation = new Translation(Game.Settings.Player.Language, Translations, this);
|
||||
|
||||
var tl = new MPos(0, 0).ToCPos(this);
|
||||
var br = new MPos(MapSize.X - 1, MapSize.Y - 1).ToCPos(this);
|
||||
AllCells = new CellRegion(Grid.Type, tl, br);
|
||||
@@ -1307,5 +1314,13 @@ namespace OpenRA
|
||||
|
||||
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" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Fluent.Net" Version="1.0.50" />
|
||||
<PackageReference Include="OpenRA-Eluant" Version="1.0.18" />
|
||||
<PackageReference Include="Mono.NAT" Version="3.0.1" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.3.1" />
|
||||
|
||||
@@ -221,6 +221,7 @@ namespace OpenRA
|
||||
public Color Color = Color.FromArgb(200, 32, 32);
|
||||
public string LastServer = "localhost:1234";
|
||||
public Color[] CustomColors = { };
|
||||
public string Language = "en";
|
||||
}
|
||||
|
||||
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 MouseOverWidget;
|
||||
|
||||
internal static Translation Translation;
|
||||
|
||||
public static void CloseWindow()
|
||||
{
|
||||
if (WindowList.Count > 0)
|
||||
@@ -155,6 +157,27 @@ namespace OpenRA.Widgets
|
||||
HandleInput(new MouseInput(MouseInputEvent.Move, MouseButton.None,
|
||||
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
|
||||
|
||||
@@ -31,5 +31,10 @@ namespace OpenRA.Mods.Common.Scripting.Global
|
||||
var c = color.HasValue ? color.Value : Color.White;
|
||||
luaLabel.GetColor = () => c;
|
||||
}
|
||||
|
||||
public string Translate(string text)
|
||||
{
|
||||
return Context.World.Map.Translate(text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user