Some work supporting localization.

Far from complete, but a good start.
This commit is contained in:
Pieter-Jan Briers
2019-05-11 23:59:48 +02:00
parent 1fd6488362
commit ddbeccdb8c
11 changed files with 236 additions and 4 deletions

View File

@@ -33,6 +33,7 @@ using Robust.Shared.Utility;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Threading;
@@ -43,6 +44,7 @@ using Robust.Client.ViewVariables;
using Robust.Shared;
using Robust.Shared.Asynchronous;
using Robust.Shared.Interfaces.Resources;
using Robust.Shared.Localization;
namespace Robust.Client
{
@@ -74,6 +76,7 @@ namespace Robust.Client
[Dependency] private readonly IDiscordRichPresence _discord;
[Dependency] private readonly IClyde _clyde;
[Dependency] private readonly IFontManagerInternal _fontManager;
[Dependency] private readonly ILocalizationManager _localizationManager;
private void Startup()
{
@@ -103,6 +106,10 @@ namespace Robust.Client
_resourceCache.Initialize(userDataDir);
_resourceCache.LoadBaseResources();
// Default to en-US.
// Perhaps in the future we could make a command line arg or something to change this default.
_localizationManager.LoadCulture(new CultureInfo("en-US"));
// Bring display up as soon as resources are mounted.
_displayManager.Initialize();
_displayManager.SetWindowTitle("Space Station 14");

View File

@@ -59,6 +59,7 @@ using Robust.Client.ViewVariables;
using Robust.Shared.Asynchronous;
using Robust.Shared.Interfaces.Resources;
using Robust.Shared.Exceptions;
using Robust.Shared.Localization;
using Robust.Shared.Map;
namespace Robust.Client
@@ -93,6 +94,7 @@ namespace Robust.Client
IoCManager.Register<ITaskManager, TaskManager>();
IoCManager.Register<IRuntimeLog, RuntimeLog>();
IoCManager.Register<IDynamicTypeFactory, DynamicTypeFactory>();
IoCManager.Register<ILocalizationManager, LocalizationManager>();
// Client stuff.
IoCManager.Register<IGameController, GameController>();

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -44,6 +45,7 @@ using Robust.Shared.Utility;
using Robust.Shared.Interfaces.Log;
using Robust.Shared.Interfaces.Resources;
using Robust.Shared.Exceptions;
using Robust.Shared.Localization;
namespace Robust.Server
{
@@ -80,6 +82,8 @@ namespace Robust.Server
private readonly ISystemConsoleManager _systemConsole;
[Dependency]
private readonly ITaskManager _taskManager;
[Dependency]
private readonly ILocalizationManager _localizationManager;
private FileLogHandler fileLogHandler;
private IGameLoop _mainLoop;
@@ -187,6 +191,11 @@ namespace Robust.Server
_resources.MountContentDirectory($@"{ContentRootDir}Resources/");
#endif
// Default to en-US.
// Perhaps in the future we could make a command line arg or something to change this default.
_localizationManager.LoadCulture(new CultureInfo("en-US"));
//mount the engine content pack
// _resources.MountContentPack(@"EngineContentPack.zip");

View File

@@ -47,6 +47,7 @@ using Robust.Server.Timing;
using Robust.Server.ViewVariables;
using Robust.Shared.Asynchronous;
using Robust.Shared.Exceptions;
using Robust.Shared.Localization;
namespace Robust.Server
{
@@ -118,6 +119,7 @@ namespace Robust.Server
IoCManager.Register<ITaskManager, TaskManager>();
IoCManager.Register<IRuntimeLog, RuntimeLog>();
IoCManager.Register<IDynamicTypeFactory, DynamicTypeFactory>();
IoCManager.Register<ILocalizationManager, LocalizationManager>();
// Server stuff.
IoCManager.Register<IEntityManager, ServerEntityManager>();

View File

@@ -0,0 +1,51 @@
using System.Globalization;
namespace Robust.Shared.Localization
{
public interface ILocalizationManager
{
/// <summary>
/// Gets a string translated for the current culture.
/// </summary>
/// <param name="text">The string to get translated.</param>
/// <returns>
/// The translated string if a translation is available, otherwise the string is returned.
/// </returns>
string GetString(string text);
/// <summary>
/// Version of <see cref="GetString(string)"/> that also runs string formatting.
/// </summary>
string GetString(string text, params object[] args);
/// <summary>
/// Gets a string inside a context or category.
/// </summary>
string GetParticularString(string context, string text);
/// <summary>
/// Gets a string inside a context or category with formatting.
/// </summary>
string GetParticularString(string context, string text, params object[] args);
string GetPluralString(string text, string pluralText, long n);
string GetPluralString(string text, string pluralText, long n, params object[] args);
string GetParticularPluralString(string context, string text, string pluralText, long n);
string GetParticularPluralString(string context, string text, string pluralText, long n, params object[] args);
/// <summary>
/// Default culture used by other methods when no culture is explicitly specified.
/// Changing this also changes the current thread's culture.
/// </summary>
CultureInfo DefaultCulture { get; set; }
/// <summary>
/// Load data for a culture.
/// </summary>
/// <param name="culture"></param>
void LoadCulture(CultureInfo culture);
}
}

View File

@@ -0,0 +1,150 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using NGettext;
using Robust.Shared.Interfaces.Resources;
using Robust.Shared.IoC;
using Robust.Shared.Utility;
using YamlDotNet.RepresentationModel;
namespace Robust.Shared.Localization
{
internal sealed class LocalizationManager : ILocalizationManager
{
[Dependency] private readonly IResourceManager _resourceManager;
private readonly Dictionary<CultureInfo, Catalog> _catalogs = new Dictionary<CultureInfo, Catalog>();
private CultureInfo _defaultCulture;
public string GetString(string text)
{
var catalog = _catalogs[_defaultCulture];
return catalog.GetString(text);
}
public string GetString(string text, params object[] args)
{
var catalog = _catalogs[_defaultCulture];
return catalog.GetString(text, args);
}
public string GetParticularString(string context, string text)
{
var catalog = _catalogs[_defaultCulture];
return catalog.GetParticularString(context, text);
}
public string GetParticularString(string context, string text, params object[] args)
{
var catalog = _catalogs[_defaultCulture];
return catalog.GetParticularString(context, text, args);
}
public string GetPluralString(string text, string pluralText, long n)
{
var catalog = _catalogs[_defaultCulture];
return catalog.GetParticularString(text, pluralText, n);
}
public string GetPluralString(string text, string pluralText, long n, params object[] args)
{
var catalog = _catalogs[_defaultCulture];
return catalog.GetParticularString(text, pluralText, n, args);
}
public string GetParticularPluralString(string context, string text, string pluralText, long n)
{
var catalog = _catalogs[_defaultCulture];
return catalog.GetParticularString(context, text, n, pluralText);
}
public string GetParticularPluralString(string context, string text, string pluralText, long n, params object[] args)
{
var catalog = _catalogs[_defaultCulture];
return catalog.GetParticularPluralString(context, text, pluralText, n, args);
}
public CultureInfo DefaultCulture
{
get => _defaultCulture;
set
{
if (!_catalogs.ContainsKey(value))
{
throw new ArgumentException("That culture is not yet loaded and cannot be used.", nameof(value));
}
_defaultCulture = value;
CultureInfo.CurrentCulture = value;
CultureInfo.CurrentUICulture = value;
}
}
public void LoadCulture(CultureInfo culture)
{
var catalog = new Catalog(culture);
_catalogs.Add(culture, catalog);
_loadData(culture, catalog);
if (DefaultCulture == null)
{
DefaultCulture = culture;
}
}
private void _loadData(CultureInfo culture, Catalog catalog)
{
// Load data from .yml files.
// Data is loaded from /Locale/<language-code>/*
var root = new ResourcePath($"/Locale/{culture.IetfLanguageTag}/");
foreach (var file in _resourceManager.ContentFindFiles(root))
{
var yamlFile = root / file;
_loadFromFile(yamlFile, catalog);
}
}
private void _loadFromFile(ResourcePath filePath, Catalog catalog)
{
var yamlStream = new YamlStream();
using (var fileStream = _resourceManager.ContentFileRead(filePath))
using (var reader = new StreamReader(fileStream, EncodingHelpers.UTF8))
{
yamlStream.Load(reader);
}
foreach (var entry in yamlStream.Documents
.SelectMany(d => (YamlSequenceNode) d.RootNode)
.Cast<YamlMappingNode>())
{
_readEntry(entry, catalog);
}
}
private static void _readEntry(YamlMappingNode entry, Catalog catalog)
{
var id = entry.GetNode("msgid").AsString();
var str = entry.GetNode("msgstr");
string[] strings;
if (str is YamlScalarNode scalar)
{
strings = new[] {scalar.AsString()};
}
else if (str is YamlSequenceNode sequence)
{
strings = sequence.Children.Select(c => c.AsString()).ToArray();
}
else
{
// TODO: Improve error reporting here.
throw new Exception("Invalid format in translation file.");
}
catalog.Translations.Add(id, strings);
}
}
}

View File

@@ -7,6 +7,7 @@ using Robust.Shared.Utility;
using System;
using System.Collections.Generic;
using System.Linq;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Maths;
@@ -22,6 +23,8 @@ namespace Robust.Shared.GameObjects
[Prototype("entity")]
public class EntityPrototype : IPrototype, IIndexedPrototype, ISyncingPrototype
{
[Dependency] private readonly ILocalizationManager _localization;
/// <summary>
/// The type string of this prototype used in files.
/// </summary>
@@ -143,7 +146,7 @@ namespace Robust.Shared.GameObjects
if (mapping.TryGetNode("name", out YamlNode node))
{
Name = node.AsString();
Name = _localization.GetString(node.AsString());
}
if (mapping.TryGetNode("class", out node))
@@ -163,7 +166,7 @@ namespace Robust.Shared.GameObjects
// DESCRIPTION
if (mapping.TryGetNode("description", out node))
{
Description = node.AsString();
Description = _localization.GetString(node.AsString());
}
// COMPONENTS

View File

@@ -1,4 +1,5 @@
using YamlDotNet.RepresentationModel;
using Robust.Shared.Localization;
using YamlDotNet.RepresentationModel;
namespace Robust.Shared.Prototypes
{

View File

@@ -94,6 +94,8 @@ namespace Robust.Shared.Prototypes
{
[Dependency]
private readonly IReflectionManager ReflectionManager;
[Dependency]
private readonly IDynamicTypeFactory _dynamicTypeFactory;
private readonly Dictionary<string, Type> prototypeTypes = new Dictionary<string, Type>();
[Dependency]
@@ -287,7 +289,7 @@ namespace Robust.Shared.Prototypes
}
var prototypeType = prototypeTypes[type];
var prototype = (IPrototype)Activator.CreateInstance(prototypeType);
var prototype = (IPrototype)_dynamicTypeFactory.CreateInstance(prototypeType);
prototype.LoadFrom(node);
prototypes[prototypeType].Add(prototype);
var indexedPrototype = prototype as IIndexedPrototype;

View File

@@ -75,6 +75,7 @@
<PackageReference Include="NetSerializer" Version="4.1.0" />
<PackageReference Include="Nett" Version="0.9.0" />
<PackageReference Include="Newtonsoft.Json" Version="11.0.2" />
<PackageReference Include="NGettext" Version="0.6.3" />
<PackageReference Include="SharpZipLib" Version="1.0.0" />
<PackageReference Include="System.Memory" Version="4.5.2" />
<PackageReference Include="System.ValueTuple" Version="4.5.0" />
@@ -171,6 +172,8 @@
<Compile Include="Interfaces\Resources\IResourceManager.cs" />
<Compile Include="Interfaces\Resources\IWritableDirProvider.cs" />
<Compile Include="Interfaces\Serialization\IRobustSerializer.cs" />
<Compile Include="Localization\ILocalizationManager.cs" />
<Compile Include="Localization\LocalizationManager.cs" />
<Compile Include="Map\IMapGrid.cs" />
<Compile Include="Interfaces\Map\IMapManager.cs" />
<Compile Include="Interfaces\Map\ITileDefinition.cs" />

View File

@@ -68,6 +68,7 @@ using Robust.Shared.Interfaces.Serialization;
using Robust.Shared.Interfaces.Timers;
using Robust.Shared.Interfaces.Timing;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Map;
using Robust.Shared.Network;
@@ -200,6 +201,7 @@ namespace Robust.UnitTesting
IoCManager.Register<ITaskManager, TaskManager>();
IoCManager.Register<IRuntimeLog, RuntimeLog>();
IoCManager.Register<IDynamicTypeFactory, DynamicTypeFactory>();
IoCManager.Register<ILocalizationManager, LocalizationManager>();
switch (Project)
{