mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Add linguini loc (#1760)
Co-authored-by: Pieter-Jan Briers <pieterjan.briers@gmail.com>
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -13,3 +13,6 @@
|
||||
[submodule "ManagedHttpListener"]
|
||||
path = ManagedHttpListener
|
||||
url = https://github.com/space-wizards/ManagedHttpListener.git
|
||||
[submodule "Linguini"]
|
||||
path = Linguini
|
||||
url = https://github.com/space-wizards/Linguini
|
||||
|
||||
1
Linguini
Submodule
1
Linguini
Submodule
Submodule Linguini added at f161cf907a
@@ -52,7 +52,7 @@ namespace Robust.Shared.Localization
|
||||
/// Does not log a warning if the message does not exist.
|
||||
/// Does however log errors if any occur while formatting.
|
||||
/// </remarks>
|
||||
bool TryGetString(string messageId, [NotNullWhen(true)] out string? value, params (string, object)[] args);
|
||||
bool TryGetString(string messageId, [NotNullWhen(true)] out string? value, params (string, object)[] keyArgs);
|
||||
|
||||
/// <summary>
|
||||
/// Default culture used by other methods when no culture is explicitly specified.
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Robust.Shared.Localization
|
||||
private static ILocalizationManager LocalizationManager => IoCManager.Resolve<ILocalizationManager>();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a language approrpiate string represented by the supplied messageId.
|
||||
/// Gets a language appropriate string represented by the supplied messageId.
|
||||
/// </summary>
|
||||
/// <param name="messageId">Unique Identifier for a translated message.</param>
|
||||
/// <returns>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Fluent.Net;
|
||||
using JetBrains.Annotations;
|
||||
using Linguini.Bundle;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Robust.Shared.Localization
|
||||
@@ -25,13 +25,13 @@ namespace Robust.Shared.Localization
|
||||
[PublicAPI]
|
||||
public readonly struct LocContext
|
||||
{
|
||||
public CultureInfo Culture => Context.Culture;
|
||||
public CultureInfo Culture => Bundle.Culture;
|
||||
|
||||
internal readonly MessageContext Context;
|
||||
internal readonly FluentBundle Bundle;
|
||||
|
||||
internal LocContext(MessageContext ctx)
|
||||
internal LocContext(FluentBundle bundle)
|
||||
{
|
||||
Context = ctx;
|
||||
Bundle = bundle;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ namespace Robust.Shared.Localization
|
||||
/// <summary>
|
||||
/// Checks if this value matches a string in a select expression.
|
||||
/// </summary>
|
||||
bool Matches(LocContext ctx, string matchValue)
|
||||
bool Matches(LocContext bundle, string matchValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -119,7 +119,7 @@ namespace Robust.Shared.Localization
|
||||
public abstract string Format(LocContext ctx);
|
||||
|
||||
/*
|
||||
public virtual bool Matches(LocContext ctx, string matchValue)
|
||||
public virtual bool Matches(LocContext bundle, string matchValue)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -150,6 +150,7 @@ namespace Robust.Shared.Localization
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Stores an "invalid" string value. Produced by e.g. unresolved variable references.
|
||||
/// </summary>
|
||||
@@ -173,13 +174,13 @@ namespace Robust.Shared.Localization
|
||||
|
||||
/*public sealed record LocValueBool(bool Value) : LocValue<bool>(Value)
|
||||
{
|
||||
public override string Format(LocContext ctx)
|
||||
public override string Format(LocContext bundle)
|
||||
{
|
||||
return Value.ToString(ctx.Culture);
|
||||
return Value.ToString(bundle.Culture);
|
||||
}
|
||||
|
||||
/*
|
||||
public override bool Matches(LocContext ctx, string matchValue)
|
||||
public override bool Matches(LocContext bundle, string matchValue)
|
||||
{
|
||||
var word = Value ? "true" : "false";
|
||||
return word.Equals(matchValue, StringComparison.InvariantCultureIgnoreCase);
|
||||
@@ -189,14 +190,14 @@ namespace Robust.Shared.Localization
|
||||
|
||||
public sealed record LocValueEnum(Enum Value) : LocValue<Enum>(Value)
|
||||
{
|
||||
public override string Format(LocContext ctx)
|
||||
public override string Format(LocContext bundle)
|
||||
{
|
||||
return Value.ToString().ToLowerInvariant();
|
||||
}
|
||||
|
||||
/*public override bool Matches(LocContext ctx, string matchValue)
|
||||
/*public override bool Matches(LocContext bundle, string matchValue)
|
||||
{
|
||||
return matchValue.Equals(Value.ToString(), StringComparison.InvariantCultureIgnoreCase);
|
||||
}#1#
|
||||
}*/
|
||||
}
|
||||
}
|
||||
30
Robust.Shared/Localization/LocHelper.cs
Normal file
30
Robust.Shared/Localization/LocHelper.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using Linguini.Bundle.Errors;
|
||||
using Linguini.Syntax.Parser.Error;
|
||||
|
||||
namespace Robust.Shared.Localization
|
||||
{
|
||||
internal static class LocHelper
|
||||
{
|
||||
public static string FormatCompileErrors(this ParseError self, ReadOnlyMemory<char> resource)
|
||||
{
|
||||
ErrorSpan span = new(self.Row, self.Slice!.Value.Start.Value, self.Slice.Value.End.Value,
|
||||
self.Position.Start.Value, self.Position.End.Value);
|
||||
return FormatErrors(self.Message, span, resource);
|
||||
}
|
||||
|
||||
private static string FormatErrors(string message, ErrorSpan span, ReadOnlyMemory<char> resource)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
var row = $" {span.Row} ";
|
||||
var errContext = resource.Slice(span.StartSpan, span.EndSpan - span.StartSpan).ToString();
|
||||
sb.Append(row).Append('|')
|
||||
.AppendLine(errContext);
|
||||
sb.Append(' ', row.Length).Append('|')
|
||||
.Append(' ', span.StartMark - span.StartSpan - 1).Append('^', span.EndMark - span.StartMark)
|
||||
.AppendLine($" {message}");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Linguini.Bundle.Errors;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components.Localization;
|
||||
using Robust.Shared.Prototypes;
|
||||
@@ -41,7 +42,7 @@ namespace Robust.Shared.Localization
|
||||
// Flush caches conservatively on prototype/localization changes.
|
||||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs args)
|
||||
{
|
||||
if (!args.ByType.TryGetValue(typeof(EntityPrototype), out var changeSet))
|
||||
if (!args.ByType.ContainsKey(typeof(EntityPrototype)))
|
||||
return;
|
||||
|
||||
FlushEntityCache();
|
||||
@@ -52,48 +53,58 @@ namespace Robust.Shared.Localization
|
||||
string? name = null;
|
||||
string? desc = null;
|
||||
string? suffix = null;
|
||||
Dictionary<string, string>? attribs = null;
|
||||
Dictionary<string, string>? attributes = null;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var prototype = _prototype.Index<EntityPrototype>(prototypeId);
|
||||
var locId = prototype.CustomLocalizationID ?? $"ent-{prototypeId}";
|
||||
|
||||
if (TryGetMessage(locId, out var ctx, out var msg))
|
||||
if (TryGetMessage(locId, out var bundle, out var msg))
|
||||
{
|
||||
// Localization override exists.
|
||||
var mAttribs = msg.Attributes;
|
||||
var msgAttrs = msg.Attributes;
|
||||
|
||||
if (name == null && msg.Value != null)
|
||||
{
|
||||
// Only set name if the value isn't empty.
|
||||
// So that you can override *only* a desc/suffix.
|
||||
name = ctx.Format(msg.Value);
|
||||
name = bundle.FormatPattern(msg.Value, null, out var fmtErr);
|
||||
WriteWarningForErrs(fmtErr, locId);
|
||||
}
|
||||
|
||||
if (mAttribs != null && mAttribs.Count != 0)
|
||||
if (msgAttrs.Count != 0)
|
||||
{
|
||||
if (desc == null && mAttribs.TryGetValue("desc", out var mDesc))
|
||||
foreach (var (attrId, pattern) in msg.Attributes)
|
||||
{
|
||||
desc = ctx.Format(mDesc);
|
||||
}
|
||||
|
||||
if (suffix == null && mAttribs.TryGetValue("suffix", out var mSuffix))
|
||||
{
|
||||
suffix = ctx.Format(mSuffix);
|
||||
}
|
||||
|
||||
foreach (var (attrib, value) in msg.Attributes)
|
||||
{
|
||||
if (attrib is "desc" or "suffix")
|
||||
var attrib = attrId.ToString();
|
||||
if (attrib.Equals("desc")
|
||||
|| attrib.Equals("suffix"))
|
||||
continue;
|
||||
|
||||
attribs ??= new Dictionary<string, string>();
|
||||
if (!attribs.ContainsKey(attrib))
|
||||
attributes ??= new Dictionary<string, string>();
|
||||
if (!attributes.ContainsKey(attrib))
|
||||
{
|
||||
attribs[attrib] = ctx.Format(value);
|
||||
var value = bundle.FormatPattern(pattern, null, out var errors);
|
||||
WriteWarningForErrs(errors, locId);
|
||||
attributes[attrib] = value;
|
||||
}
|
||||
}
|
||||
|
||||
var allErrors = new List<FluentError>();
|
||||
if (desc == null
|
||||
&& bundle.TryGetMsg(locId, "desc", null, out var err1, out desc))
|
||||
{
|
||||
allErrors.AddRange(err1);
|
||||
}
|
||||
|
||||
if (suffix == null
|
||||
&& bundle.TryGetMsg(locId, "suffix", null, out var err, out suffix))
|
||||
{
|
||||
allErrors.AddRange(err);
|
||||
}
|
||||
|
||||
WriteWarningForErrs(allErrors, locId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,10 +116,10 @@ namespace Robust.Shared.Localization
|
||||
{
|
||||
foreach (var (attrib, value) in prototype.LocProperties)
|
||||
{
|
||||
attribs ??= new Dictionary<string, string>();
|
||||
if (!attribs.ContainsKey(attrib))
|
||||
attributes ??= new Dictionary<string, string>();
|
||||
if (!attributes.ContainsKey(attrib))
|
||||
{
|
||||
attribs[attrib] = value;
|
||||
attributes[attrib] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,9 +134,10 @@ namespace Robust.Shared.Localization
|
||||
name ?? "",
|
||||
desc ?? "",
|
||||
suffix,
|
||||
attribs?.ToImmutableDictionary() ?? ImmutableDictionary<string, string>.Empty);
|
||||
attributes?.ToImmutableDictionary() ?? ImmutableDictionary<string, string>.Empty);
|
||||
}
|
||||
|
||||
|
||||
public EntityLocData GetEntityData(string prototypeId)
|
||||
{
|
||||
return _entityCache.GetOrAdd(prototypeId, (id, t) => t.CalcEntityLoc(id), this);
|
||||
|
||||
@@ -1,38 +1,39 @@
|
||||
using System;
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Fluent.Net;
|
||||
using Linguini.Bundle;
|
||||
using Linguini.Bundle.Types;
|
||||
using Linguini.Shared.Types.Bundle;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components.Localization;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Localization
|
||||
{
|
||||
internal sealed partial class LocalizationManager
|
||||
{
|
||||
private void AddBuiltinFunctions(MessageContext context)
|
||||
private void AddBuiltInFunctions(FluentBundle bundle)
|
||||
{
|
||||
// Grammatical gender / pronouns
|
||||
AddCtxFunction(context, "GENDER", FuncGender);
|
||||
AddCtxFunction(context, "SUBJECT", FuncSubject);
|
||||
AddCtxFunction(context, "OBJECT", FuncObject);
|
||||
AddCtxFunction(context, "POSS-ADJ", FuncPossAdj);
|
||||
AddCtxFunction(context, "POSS-PRONOUN", FuncPossPronoun);
|
||||
AddCtxFunction(context, "REFLEXIVE", FuncReflexive);
|
||||
AddCtxFunction(bundle, "GENDER", FuncGender);
|
||||
AddCtxFunction(bundle, "SUBJECT", FuncSubject);
|
||||
AddCtxFunction(bundle, "OBJECT", FuncObject);
|
||||
AddCtxFunction(bundle, "POSS-ADJ", FuncPossAdj);
|
||||
AddCtxFunction(bundle, "POSS-PRONOUN", FuncPossPronoun);
|
||||
AddCtxFunction(bundle, "REFLEXIVE", FuncReflexive);
|
||||
|
||||
// Conjugation
|
||||
AddCtxFunction(context, "CONJUGATE-BE", FuncConjugateBe);
|
||||
AddCtxFunction(context, "CONJUGATE-HAVE", FuncConjugateHave);
|
||||
AddCtxFunction(bundle, "CONJUGATE-BE", FuncConjugateBe);
|
||||
AddCtxFunction(bundle, "CONJUGATE-HAVE", FuncConjugateHave);
|
||||
|
||||
// Proper nouns
|
||||
AddCtxFunction(context, "PROPER", FuncProper);
|
||||
AddCtxFunction(context, "THE", FuncThe);
|
||||
AddCtxFunction(bundle, "PROPER", FuncProper);
|
||||
AddCtxFunction(bundle, "THE", FuncThe);
|
||||
|
||||
// Misc
|
||||
AddCtxFunction(context, "ATTRIB", args => FuncAttrib(context, args));
|
||||
AddCtxFunction(context, "CAPITALIZE", FuncCapitalize);
|
||||
AddCtxFunction(bundle, "ATTRIB", args => FuncAttrib(bundle, args));
|
||||
AddCtxFunction(bundle, "CAPITALIZE", FuncCapitalize);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -50,7 +51,7 @@ namespace Robust.Shared.Localization
|
||||
{
|
||||
var input = args.Args[0].Format(new LocContext());
|
||||
if (!String.IsNullOrEmpty(input))
|
||||
return new LocValueString(input.First().ToString().ToUpper() + input.Substring(1));
|
||||
return new LocValueString(input[0].ToString().ToUpper() + input.Substring(1));
|
||||
else return new LocValueString("");
|
||||
}
|
||||
|
||||
@@ -64,7 +65,7 @@ namespace Robust.Shared.Localization
|
||||
ILocValue entity0 = args.Args[0];
|
||||
if (entity0.Value != null)
|
||||
{
|
||||
IEntity entity = (IEntity) entity0.Value;
|
||||
IEntity entity = (IEntity)entity0.Value;
|
||||
|
||||
if (entity.TryGetComponent<GrammarComponent>(out var grammar) && grammar.Gender.HasValue)
|
||||
{
|
||||
@@ -136,16 +137,16 @@ namespace Robust.Shared.Localization
|
||||
return new LocValueString(GetString("zzzz-conjugate-have", ("ent", args.Args[0])));
|
||||
}
|
||||
|
||||
private ILocValue FuncAttrib(MessageContext context, LocArgs args)
|
||||
private ILocValue FuncAttrib(FluentBundle bundle, LocArgs args)
|
||||
{
|
||||
if (args.Args.Count < 2) return new LocValueString("other");
|
||||
|
||||
ILocValue entity0 = args.Args[0];
|
||||
if (entity0.Value != null)
|
||||
{
|
||||
IEntity entity = (IEntity) entity0.Value;
|
||||
IEntity entity = (IEntity)entity0.Value;
|
||||
ILocValue attrib0 = args.Args[1];
|
||||
if (TryGetEntityLocAttrib(entity, attrib0.Format(new LocContext(context)), out var attrib))
|
||||
if (TryGetEntityLocAttrib(entity, attrib0.Format(new LocContext(bundle)), out var attrib))
|
||||
{
|
||||
return new LocValueString(attrib);
|
||||
}
|
||||
@@ -164,7 +165,7 @@ namespace Robust.Shared.Localization
|
||||
ILocValue entity0 = args.Args[0];
|
||||
if (entity0.Value != null)
|
||||
{
|
||||
IEntity entity = (IEntity) entity0.Value;
|
||||
IEntity entity = (IEntity)entity0.Value;
|
||||
|
||||
if (entity.TryGetComponent<GrammarComponent>(out var grammar) && grammar.ProperNoun.HasValue)
|
||||
{
|
||||
@@ -180,39 +181,99 @@ namespace Robust.Shared.Localization
|
||||
return new LocValueString("false");
|
||||
}
|
||||
|
||||
private void AddCtxFunction(MessageContext ctx, string name, LocFunction function)
|
||||
|
||||
private void AddCtxFunction(FluentBundle ctx, string name, LocFunction function)
|
||||
{
|
||||
ctx.Functions.Add(name, (args, options) => CallFunction(function, args, options));
|
||||
ctx.AddFunction(name, (args, options)
|
||||
=> CallFunction(function, args, options), out _, InsertBehavior.Overriding);
|
||||
}
|
||||
|
||||
private IFluentType CallFunction(LocFunction function,
|
||||
IList<IFluentType> positionalArgs, IDictionary<string, IFluentType> namedArgs)
|
||||
{
|
||||
var args = new ILocValue[positionalArgs.Count];
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
args[i] = positionalArgs[i].ToLocValue();
|
||||
}
|
||||
|
||||
var options = new Dictionary<string, ILocValue>(namedArgs.Count);
|
||||
foreach (var (k, v) in namedArgs)
|
||||
{
|
||||
options.Add(k, v.ToLocValue());
|
||||
}
|
||||
|
||||
var argStruct = new LocArgs(args, options);
|
||||
return function.Invoke(argStruct).FluentFromVal();
|
||||
}
|
||||
|
||||
public void AddFunction(CultureInfo culture, string name, LocFunction function)
|
||||
{
|
||||
var context = _contexts[culture];
|
||||
var bundle = _contexts[culture];
|
||||
|
||||
context.Functions.Add(name, (args, options) => CallFunction(function, args, options));
|
||||
}
|
||||
|
||||
private FluentType CallFunction(
|
||||
LocFunction function,
|
||||
IList<object> fluentArgs, IDictionary<string, object> fluentOptions)
|
||||
{
|
||||
var args = new ILocValue[fluentArgs.Count];
|
||||
for (var i = 0; i < args.Length; i++)
|
||||
{
|
||||
args[i] = ValFromFluent(fluentArgs[i]);
|
||||
}
|
||||
|
||||
var options = new Dictionary<string, ILocValue>(fluentOptions.Count);
|
||||
foreach (var (k, v) in fluentOptions)
|
||||
{
|
||||
options.Add(k, ValFromFluent(v));
|
||||
}
|
||||
|
||||
var argStruct = new LocArgs(args, options);
|
||||
|
||||
var ret = function(argStruct);
|
||||
|
||||
return ValToFluent(ret);
|
||||
bundle.AddFunction(name, (args, options)
|
||||
=> CallFunction(function, args, options), out _, InsertBehavior.Overriding);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class FluentLocWrapperType : IFluentType
|
||||
{
|
||||
public readonly ILocValue WrappedValue;
|
||||
|
||||
public FluentLocWrapperType(ILocValue wrappedValue)
|
||||
{
|
||||
WrappedValue = wrappedValue;
|
||||
}
|
||||
|
||||
public string AsString()
|
||||
{
|
||||
return WrappedValue.Format(new LocContext());
|
||||
}
|
||||
|
||||
public IFluentType Copy()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
static class LinguiniAdapter
|
||||
{
|
||||
internal static ILocValue ToLocValue(this IFluentType arg)
|
||||
{
|
||||
return arg switch
|
||||
{
|
||||
FluentNone => new LocValueNone(""),
|
||||
FluentNumber number => new LocValueNumber(number),
|
||||
FluentString str => new LocValueString(str),
|
||||
FluentLocWrapperType value => value.WrappedValue,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(arg)),
|
||||
};
|
||||
}
|
||||
|
||||
public static IFluentType FluentFromObject(this object obj)
|
||||
{
|
||||
return obj switch
|
||||
{
|
||||
ILocValue wrap => new FluentLocWrapperType(wrap),
|
||||
IEntity entity => new FluentLocWrapperType(new LocValueEntity(entity)),
|
||||
DateTime dateTime => new FluentLocWrapperType(new LocValueDateTime(dateTime)),
|
||||
bool or Enum => (FluentString)obj.ToString()!.ToLowerInvariant(),
|
||||
string str => (FluentString)str,
|
||||
double dbl => (FluentNumber)dbl,
|
||||
_ => (FluentString)obj.ToString()!,
|
||||
};
|
||||
}
|
||||
|
||||
public static IFluentType FluentFromVal(this ILocValue locValue)
|
||||
{
|
||||
return locValue switch
|
||||
{
|
||||
LocValueNone => FluentNone.None,
|
||||
LocValueNumber number => (FluentNumber)number.Value,
|
||||
LocValueString str => (FluentString)str.Value,
|
||||
LocValueDateTime dateTime => new FluentLocWrapperType(dateTime),
|
||||
_ => new FluentLocWrapperType(locValue),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,9 +4,14 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Fluent.Net;
|
||||
using Fluent.Net.RuntimeAst;
|
||||
using JetBrains.Annotations;
|
||||
using Linguini.Bundle;
|
||||
using Linguini.Bundle.Builder;
|
||||
using Linguini.Bundle.Errors;
|
||||
using Linguini.Shared.Types.Bundle;
|
||||
using Linguini.Syntax.Ast;
|
||||
using Linguini.Syntax.Parser;
|
||||
using Linguini.Syntax.Parser.Error;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -24,7 +29,7 @@ namespace Robust.Shared.Localization
|
||||
[Dependency] private readonly IPrototypeManager _prototype = default!;
|
||||
|
||||
private ISawmill _logSawmill = default!;
|
||||
private readonly Dictionary<CultureInfo, MessageContext> _contexts = new();
|
||||
private readonly Dictionary<CultureInfo, FluentBundle> _contexts = new();
|
||||
|
||||
private CultureInfo? _defaultCulture;
|
||||
|
||||
@@ -48,16 +53,6 @@ namespace Robust.Shared.Localization
|
||||
return msg;
|
||||
}
|
||||
|
||||
public bool TryGetString(string messageId, [NotNullWhen(true)] out string? value)
|
||||
{
|
||||
if (!TryGetNode(messageId, out var context, out var node))
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return DoFormat(messageId, out value, context, node);
|
||||
}
|
||||
|
||||
public string GetString(string messageId, params (string, object)[] args0)
|
||||
{
|
||||
@@ -73,144 +68,38 @@ namespace Robust.Shared.Localization
|
||||
return msg;
|
||||
}
|
||||
|
||||
public bool TryGetString(string messageId, [NotNullWhen(true)] out string? value,
|
||||
params (string, object)[] args0)
|
||||
public bool TryGetString(string messageId, [NotNullWhen(true)] out string? value)
|
||||
{
|
||||
if (!TryGetNode(messageId, out var context, out var node))
|
||||
return TryGetString(messageId, out value, null);
|
||||
}
|
||||
|
||||
public bool TryGetString(string messageId, [NotNullWhen(true)] out string? value,
|
||||
params (string, object)[]? keyArgs)
|
||||
{
|
||||
var args = new Dictionary<string, IFluentType>();
|
||||
if (keyArgs != null)
|
||||
{
|
||||
foreach (var (k, v) in keyArgs)
|
||||
{
|
||||
args.Add(k, v.FluentFromObject());
|
||||
}
|
||||
}
|
||||
|
||||
if (!HasMessage(messageId, out var bundle))
|
||||
{
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var args = new Dictionary<string, object>();
|
||||
foreach (var (k, v) in args0)
|
||||
{
|
||||
var val = v switch
|
||||
{
|
||||
IEntity entity => new LocValueEntity(entity),
|
||||
bool or Enum => v.ToString()!.ToLowerInvariant(),
|
||||
_ => v,
|
||||
};
|
||||
|
||||
if (val is ILocValue locVal)
|
||||
val = new FluentLocWrapperType(locVal);
|
||||
|
||||
args.Add(k, val);
|
||||
}
|
||||
|
||||
return DoFormat(messageId, out value, context, node, args);
|
||||
}
|
||||
|
||||
private bool TryGetMessage(
|
||||
string messageId,
|
||||
[NotNullWhen(true)] out MessageContext? ctx,
|
||||
[NotNullWhen(true)] out Message? message)
|
||||
{
|
||||
if (_defaultCulture == null)
|
||||
{
|
||||
ctx = null;
|
||||
message = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
ctx = _contexts[_defaultCulture];
|
||||
message = ctx.GetMessage(messageId);
|
||||
return message != null;
|
||||
}
|
||||
|
||||
private bool TryGetNode(
|
||||
string messageId,
|
||||
[NotNullWhen(true)] out MessageContext? ctx,
|
||||
[NotNullWhen(true)] out Node? node)
|
||||
{
|
||||
string? attribName = null;
|
||||
|
||||
if (messageId.Contains('.'))
|
||||
{
|
||||
var split = messageId.Split('.');
|
||||
messageId = split[0];
|
||||
attribName = split[1];
|
||||
}
|
||||
|
||||
if (!TryGetMessage(messageId, out ctx, out var message))
|
||||
{
|
||||
node = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (attribName != null)
|
||||
{
|
||||
if (message.Attributes == null || !message.Attributes.TryGetValue(attribName, out var attrib))
|
||||
{
|
||||
node = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
node = attrib;
|
||||
}
|
||||
else
|
||||
{
|
||||
node = message;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ReloadLocalizations()
|
||||
{
|
||||
foreach (var (culture, context) in _contexts.ToArray())
|
||||
{
|
||||
// Fluent.Net doesn't allow us to remove messages so...
|
||||
var newContext = new MessageContext(
|
||||
culture.Name,
|
||||
new MessageContextOptions
|
||||
{
|
||||
UseIsolating = false,
|
||||
Functions = context.Functions
|
||||
}
|
||||
);
|
||||
|
||||
_contexts[culture] = newContext;
|
||||
|
||||
_loadData(_res, culture, newContext);
|
||||
}
|
||||
|
||||
FlushEntityCache();
|
||||
}
|
||||
|
||||
private static ILocValue ValFromFluent(object arg)
|
||||
{
|
||||
return arg switch
|
||||
{
|
||||
FluentNone none => new LocValueNone(none.Value),
|
||||
FluentNumber number => new LocValueNumber(double.Parse(number.Value)),
|
||||
FluentString str => new LocValueString(str.Value),
|
||||
FluentDateTime dateTime =>
|
||||
new LocValueDateTime(DateTime.Parse(dateTime.Value, null, DateTimeStyles.RoundtripKind)),
|
||||
FluentLocWrapperType wrap => wrap.WrappedValue,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(arg))
|
||||
};
|
||||
}
|
||||
|
||||
private static FluentType ValToFluent(ILocValue arg)
|
||||
{
|
||||
return arg switch
|
||||
{
|
||||
LocValueNone =>
|
||||
throw new NotSupportedException("Cannot currently return LocValueNone from loc functions."),
|
||||
LocValueNumber number => new FluentNumber(number.Value.ToString("R")),
|
||||
LocValueString str => new FluentString(str.Value),
|
||||
LocValueDateTime dateTime => new FluentDateTime(dateTime.Value),
|
||||
_ => new FluentLocWrapperType(arg)
|
||||
};
|
||||
}
|
||||
|
||||
private bool DoFormat(string messageId, out string? value, MessageContext context, Node node, IDictionary<string, object>? args = null)
|
||||
{
|
||||
var errs = new List<FluentError>();
|
||||
try
|
||||
{
|
||||
value = context.Format(node, args, errs);
|
||||
var result = bundle.TryGetAttrMsg(messageId, args, out var errs, out value);
|
||||
foreach (var err in errs)
|
||||
{
|
||||
_logSawmill.Error("{culture}/{messageId}: {error}", _defaultCulture!.Name, messageId, err);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@@ -218,13 +107,52 @@ namespace Robust.Shared.Localization
|
||||
value = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var err in errs)
|
||||
private bool HasMessage(
|
||||
string messageId,
|
||||
[NotNullWhen(true)] out FluentBundle? bundle)
|
||||
{
|
||||
if (_defaultCulture == null)
|
||||
{
|
||||
_logSawmill.Error("{culture}/{messageId}: {error}", _defaultCulture!.Name, messageId, err);
|
||||
bundle = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
bundle = _contexts[_defaultCulture];
|
||||
if (messageId.Contains('.'))
|
||||
{
|
||||
var split = messageId.Split('.');
|
||||
return bundle.HasMessage(split[0]);
|
||||
}
|
||||
|
||||
return bundle.HasMessage(messageId);
|
||||
}
|
||||
|
||||
private bool TryGetMessage(
|
||||
string messageId,
|
||||
[NotNullWhen(true)] out FluentBundle? bundle,
|
||||
[NotNullWhen(true)] out AstMessage? message)
|
||||
{
|
||||
if (_defaultCulture == null)
|
||||
{
|
||||
bundle = null;
|
||||
message = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
bundle = _contexts[_defaultCulture];
|
||||
return bundle.TryGetAstMessage(messageId, out message);
|
||||
}
|
||||
|
||||
public void ReloadLocalizations()
|
||||
{
|
||||
foreach (var (culture, context) in _contexts.ToArray())
|
||||
{
|
||||
_loadData(_res, culture, context);
|
||||
}
|
||||
|
||||
FlushEntityCache();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -261,26 +189,18 @@ namespace Robust.Shared.Localization
|
||||
|
||||
public void LoadCulture(CultureInfo culture)
|
||||
{
|
||||
var context = new MessageContext(
|
||||
culture.Name,
|
||||
new MessageContextOptions
|
||||
{
|
||||
UseIsolating = false,
|
||||
// Have to pass empty dict here or else Fluent.Net will fuck up
|
||||
// and share the same dict between multiple message contexts.
|
||||
// Yes, you read that right.
|
||||
Functions = new Dictionary<string, Resolver.ExternalFunction>(),
|
||||
}
|
||||
);
|
||||
AddBuiltinFunctions(context);
|
||||
var bundle = LinguiniBuilder.Builder()
|
||||
.CultureInfo(culture)
|
||||
.SkipResources()
|
||||
.SetUseIsolating(false)
|
||||
.UseConcurrent()
|
||||
.UncheckedBuild();
|
||||
|
||||
_contexts.Add(culture, context);
|
||||
_contexts.Add(culture, bundle);
|
||||
AddBuiltInFunctions(bundle);
|
||||
|
||||
_loadData(_res, culture, context);
|
||||
if (DefaultCulture == null)
|
||||
{
|
||||
DefaultCulture = culture;
|
||||
}
|
||||
_loadData(_res, culture, bundle);
|
||||
DefaultCulture ??= culture;
|
||||
}
|
||||
|
||||
public void AddLoadedToStringSerializer(IRobustMappedStringSerializer serializer)
|
||||
@@ -307,7 +227,7 @@ namespace Robust.Shared.Localization
|
||||
*/
|
||||
}
|
||||
|
||||
private void _loadData(IResourceManager resourceManager, CultureInfo culture, MessageContext context)
|
||||
private void _loadData(IResourceManager resourceManager, CultureInfo culture, FluentBundle context)
|
||||
{
|
||||
// Load data from .ftl files.
|
||||
// Data is loaded from /Locale/<language-code>/*
|
||||
@@ -323,40 +243,33 @@ namespace Robust.Shared.Localization
|
||||
using var fileStream = resourceManager.ContentFileRead(path);
|
||||
using var reader = new StreamReader(fileStream, EncodingHelpers.UTF8);
|
||||
|
||||
var resource = FluentResource.FromReader(reader);
|
||||
return (path, resource);
|
||||
var parser = new LinguiniParser(reader);
|
||||
var resource = parser.Parse();
|
||||
return (path, resource, parser.GetReadonlyData);
|
||||
});
|
||||
|
||||
foreach (var (path, resource) in resources)
|
||||
foreach (var (path, resource, data) in resources)
|
||||
{
|
||||
var errors = context.AddResource(resource);
|
||||
foreach (var error in errors)
|
||||
{
|
||||
_logSawmill.Error("{path}: {exception}", path, error.Message);
|
||||
}
|
||||
var errors = resource.Errors;
|
||||
context.AddResourceOverriding(resource);
|
||||
WriteWarningForErrs(path, errors, data);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class FluentLocWrapperType : FluentType
|
||||
private void WriteWarningForErrs(ResourcePath path, List<ParseError> errs, ReadOnlyMemory<char> resource)
|
||||
{
|
||||
public readonly ILocValue WrappedValue;
|
||||
|
||||
public FluentLocWrapperType(ILocValue wrappedValue)
|
||||
foreach (var err in errs)
|
||||
{
|
||||
WrappedValue = wrappedValue;
|
||||
_logSawmill.Warning("{path}:\n{exception}", path, err.FormatCompileErrors(resource));
|
||||
}
|
||||
}
|
||||
|
||||
public override string Format(MessageContext ctx)
|
||||
private void WriteWarningForErrs(IList<FluentError> errs, string locId)
|
||||
{
|
||||
foreach (var err in errs)
|
||||
{
|
||||
return WrappedValue.Format(new LocContext(ctx));
|
||||
}
|
||||
|
||||
public override bool Match(MessageContext ctx, object obj)
|
||||
{
|
||||
return false;
|
||||
/*var strVal = obj is IFluentType ft ? ft.Value : obj.ToString() ?? "";
|
||||
return WrappedValue.Matches(new LocContext(ctx), strVal);*/
|
||||
_logSawmill.Warning("Error extracting `{locId}`\n{e1}", locId, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@
|
||||
<PackageReference Include="Nett" Version="0.15.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="nfluidsynth" Version="0.3.1" />
|
||||
<PackageReference Include="Fluent.Net" Version="1.0.50" />
|
||||
<PackageReference Include="Pidgin" Version="2.5.0" />
|
||||
<PackageReference Include="prometheus-net" Version="4.1.1" />
|
||||
<PackageReference Include="Robust.Shared.AuthLib" Version="0.1.2" />
|
||||
@@ -24,6 +23,7 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Lidgren.Network\Lidgren.Network.csproj" />
|
||||
<ProjectReference Include="..\Linguini\Linguini.Bundle\Linguini.Bundle.csproj" />
|
||||
<ProjectReference Include="..\NetSerializer\NetSerializer\NetSerializer.csproj" />
|
||||
<ProjectReference Include="..\Robust.Physics\Robust.Physics.csproj" />
|
||||
<ProjectReference Include="..\Robust.Shared.Maths\Robust.Shared.Maths.csproj" />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.Globalization;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
@@ -49,6 +49,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Robust.Analyzers", "Robust.
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.Base", "Avalonia.Base\Avalonia.Base.csproj", "{C60905B4-072F-4376-BCEC-C91186644127}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Linguini", "Linguini", "{3D0047D0-1089-48EC-AF72-A9B7A2E7A4BF}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Linguini.Bundle", "Linguini\Linguini.Bundle\Linguini.Bundle.csproj", "{71A3AD7B-9D09-4246-A574-E5EB7D7FF20B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Linguini.Shared", "Linguini\Linguini.Shared\Linguini.Shared.csproj", "{918FEA70-4B6B-4C8D-8AAB-2C911DCDCAE5}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Linguini.Syntax", "Linguini\Linguini.Syntax\Linguini.Syntax.csproj", "{A5EDDBF2-3FA0-4364-A953-A814A0CBBE53}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluralRules.Generator", "Linguini\PluralRules.Generator\PluralRules.Generator.csproj", "{0641361F-37D7-40D8-A763-16896F67DC54}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluralRules.Generator", "Linguini\PluralRules.Generator\PluralRules.Generator.csproj", "{479FD643-665B-4DE1-B68E-3AAAD8E7A967}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -209,6 +221,46 @@ Global
|
||||
{C60905B4-072F-4376-BCEC-C91186644127}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{C60905B4-072F-4376-BCEC-C91186644127}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{C60905B4-072F-4376-BCEC-C91186644127}.Release|x64.Build.0 = Release|Any CPU
|
||||
{71A3AD7B-9D09-4246-A574-E5EB7D7FF20B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{71A3AD7B-9D09-4246-A574-E5EB7D7FF20B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{71A3AD7B-9D09-4246-A574-E5EB7D7FF20B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{71A3AD7B-9D09-4246-A574-E5EB7D7FF20B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{71A3AD7B-9D09-4246-A574-E5EB7D7FF20B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{71A3AD7B-9D09-4246-A574-E5EB7D7FF20B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{71A3AD7B-9D09-4246-A574-E5EB7D7FF20B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{71A3AD7B-9D09-4246-A574-E5EB7D7FF20B}.Release|x64.Build.0 = Release|Any CPU
|
||||
{918FEA70-4B6B-4C8D-8AAB-2C911DCDCAE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{918FEA70-4B6B-4C8D-8AAB-2C911DCDCAE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{918FEA70-4B6B-4C8D-8AAB-2C911DCDCAE5}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{918FEA70-4B6B-4C8D-8AAB-2C911DCDCAE5}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{918FEA70-4B6B-4C8D-8AAB-2C911DCDCAE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{918FEA70-4B6B-4C8D-8AAB-2C911DCDCAE5}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{918FEA70-4B6B-4C8D-8AAB-2C911DCDCAE5}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{918FEA70-4B6B-4C8D-8AAB-2C911DCDCAE5}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A5EDDBF2-3FA0-4364-A953-A814A0CBBE53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A5EDDBF2-3FA0-4364-A953-A814A0CBBE53}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A5EDDBF2-3FA0-4364-A953-A814A0CBBE53}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{A5EDDBF2-3FA0-4364-A953-A814A0CBBE53}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{A5EDDBF2-3FA0-4364-A953-A814A0CBBE53}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A5EDDBF2-3FA0-4364-A953-A814A0CBBE53}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A5EDDBF2-3FA0-4364-A953-A814A0CBBE53}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{A5EDDBF2-3FA0-4364-A953-A814A0CBBE53}.Release|x64.Build.0 = Release|Any CPU
|
||||
{0641361F-37D7-40D8-A763-16896F67DC54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{0641361F-37D7-40D8-A763-16896F67DC54}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{0641361F-37D7-40D8-A763-16896F67DC54}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{0641361F-37D7-40D8-A763-16896F67DC54}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{0641361F-37D7-40D8-A763-16896F67DC54}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{0641361F-37D7-40D8-A763-16896F67DC54}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{0641361F-37D7-40D8-A763-16896F67DC54}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{0641361F-37D7-40D8-A763-16896F67DC54}.Release|x64.Build.0 = Release|Any CPU
|
||||
{479FD643-665B-4DE1-B68E-3AAAD8E7A967}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{479FD643-665B-4DE1-B68E-3AAAD8E7A967}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{479FD643-665B-4DE1-B68E-3AAAD8E7A967}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{479FD643-665B-4DE1-B68E-3AAAD8E7A967}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{479FD643-665B-4DE1-B68E-3AAAD8E7A967}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{479FD643-665B-4DE1-B68E-3AAAD8E7A967}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{479FD643-665B-4DE1-B68E-3AAAD8E7A967}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{479FD643-665B-4DE1-B68E-3AAAD8E7A967}.Release|x64.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
@@ -220,6 +272,10 @@ Global
|
||||
{D73768A2-BFCD-4916-8F52-4034C28F345C} = {1B1FC7C4-0212-4B3E-90D4-C7B58759E4B0}
|
||||
{1CDC9C4F-668E-47A3-8A44-216E95644BEB} = {1B1FC7C4-0212-4B3E-90D4-C7B58759E4B0}
|
||||
{B05EFB71-AEC7-4C6E-984A-A1BCC58F9AD1} = {1B1FC7C4-0212-4B3E-90D4-C7B58759E4B0}
|
||||
{71A3AD7B-9D09-4246-A574-E5EB7D7FF20B} = {3D0047D0-1089-48EC-AF72-A9B7A2E7A4BF}
|
||||
{918FEA70-4B6B-4C8D-8AAB-2C911DCDCAE5} = {3D0047D0-1089-48EC-AF72-A9B7A2E7A4BF}
|
||||
{A5EDDBF2-3FA0-4364-A953-A814A0CBBE53} = {3D0047D0-1089-48EC-AF72-A9B7A2E7A4BF}
|
||||
{479FD643-665B-4DE1-B68E-3AAAD8E7A967} = {3D0047D0-1089-48EC-AF72-A9B7A2E7A4BF}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {57757344-0FF4-4842-8A68-141CAA18A35D}
|
||||
|
||||
Reference in New Issue
Block a user