-
Notifications
You must be signed in to change notification settings - Fork 395
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Localization support for any prototypes #4335
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
using System; | ||
using System.Collections.Concurrent; | ||
using System.Collections.Generic; | ||
using Linguini.Bundle.Errors; | ||
using Robust.Shared.Prototypes; | ||
|
||
namespace Robust.Shared.Localization; | ||
|
||
internal sealed partial class LocalizationManager | ||
{ | ||
// Concurrent dict so that multiple threads "reading" .Name won't cause a concurrent write issue | ||
// when the cache gets populated. | ||
private readonly ConcurrentDictionary<string, PrototypeLocData> _prototypeCache = new(); | ||
|
||
private void FlushPrototypeCache() | ||
{ | ||
_logSawmill.Debug("Flushing entity localization cache."); | ||
_entityCache.Clear(); | ||
} | ||
|
||
// Flush caches conservatively on prototype/localization changes. | ||
private void OnPrototypesReloaded(PrototypesReloadedEventArgs args) | ||
Check failure on line 22 in Robust.Shared/Localization/LocalizationManager.Prototypes.cs GitHub Actions / build (ubuntu-latest)
|
||
{ | ||
if (args.ByType.ContainsKey(typeof(EntityPrototype))) | ||
FlushEntityCache(); | ||
|
||
FlushPrototypeCache(); | ||
Comment on lines
+24
to
+27
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
|
||
private PrototypeLocData CalcInheritingPrototypeLoc<T>(string prototypeId) where T : class, IPrototype, IInheritingPrototype, ILocalizedPrototype | ||
{ | ||
return CalcInheritingPrototypeLoc(typeof(T), prototypeId); | ||
} | ||
|
||
private PrototypeLocData CalcInheritingPrototypeLoc(Type kind, string prototypeId) | ||
{ | ||
string? name = null; | ||
string? desc = null; | ||
|
||
foreach (var prototype in _prototype.EnumerateParents(kind, prototypeId, true)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't recursively iterate up through a prototypes parents, this is just the direct parent list |
||
{ | ||
var lproto = (ILocalizedPrototype)prototype; | ||
var locId = $"{lproto.CustomLocalizationPrefix}-{prototypeId}"; | ||
|
||
if (TryGetMessage(locId, out var bundle, out var msg)) | ||
{ | ||
// Localization override exists. | ||
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. | ||
name = bundle.FormatPattern(msg.Value, null, out var fmtErr); | ||
WriteWarningForErrs(fmtErr, locId); | ||
} | ||
|
||
if (msgAttrs.Count != 0) | ||
{ | ||
var allErrors = new List<FluentError>(); | ||
if (desc == null | ||
&& !bundle.TryGetMsg(locId, "desc", null, out var err1, out desc)) | ||
{ | ||
desc = null; | ||
allErrors.AddRange(err1); | ||
} | ||
|
||
WriteWarningForErrs(allErrors, locId); | ||
} | ||
} | ||
|
||
name ??= lproto.SetName; | ||
desc ??= lproto.SetDesc; | ||
} | ||
|
||
return new PrototypeLocData( | ||
name ?? "", | ||
desc ?? ""); | ||
} | ||
|
||
private PrototypeLocData CalcPrototypeLoc<T>(string prototypeId) where T : class, IPrototype, ILocalizedPrototype | ||
{ | ||
return CalcPrototypeLoc(typeof(T), prototypeId); | ||
} | ||
|
||
private PrototypeLocData CalcPrototypeLoc(Type kind, string prototypeId) | ||
{ | ||
string? name = null; | ||
string? desc = null; | ||
|
||
// Return empty strings if no prototype found | ||
if (!_prototype.TryIndex(kind, prototypeId, out var prototype)) | ||
return new PrototypeLocData("", ""); | ||
|
||
var lproto = (ILocalizedPrototype)prototype; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. try cast |
||
var locId = $"{lproto.CustomLocalizationPrefix}-{prototypeId}"; | ||
|
||
if (!TryGetMessage(locId, out var bundle, out var msg) | ||
|| msg.Value == null) | ||
return new PrototypeLocData(lproto.SetName ?? "", lproto.SetDesc ?? ""); | ||
|
||
name = bundle.FormatPattern(msg.Value, null, out var fmtErr); | ||
|
||
if (!bundle.TryGetMsg(locId, "desc", null, out var err1, out desc)) | ||
{ | ||
return new PrototypeLocData(name ?? "", lproto.SetDesc ?? ""); | ||
} | ||
|
||
return new PrototypeLocData(name ?? "", desc); | ||
} | ||
|
||
public PrototypeLocData GetPrototypeData(Type kind, string prototypeId) | ||
{ | ||
if (kind.IsAssignableTo(typeof(IInheritingPrototype))) | ||
return _prototypeCache.GetOrAdd(prototypeId, (id, t) => t.CalcInheritingPrototypeLoc(kind, id), this); | ||
return _prototypeCache.GetOrAdd(prototypeId, (id, t) => t.CalcPrototypeLoc(kind, id), this); | ||
} | ||
|
||
public PrototypeLocData GetPrototypeData<T>(string prototypeId) where T: class, IPrototype, ILocalizedPrototype | ||
{ | ||
return GetPrototypeData(typeof(T), prototypeId); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
namespace Robust.Shared.Localization; | ||
|
||
/// <summary> | ||
/// Contains based localized prototype data. | ||
/// </summary> | ||
/// <param name="Name">The localized name of the prototype.</param> | ||
/// <param name="Desc">The localized description of the prototype.</param> | ||
public record PrototypeLocData( | ||
string Name, | ||
string Desc); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
using System; | ||
using JetBrains.Annotations; | ||
using Robust.Shared.Localization; | ||
using Robust.Shared.Serialization.Manager.Attributes; | ||
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; | ||
using Robust.Shared.Serialization.TypeSerializers.Interfaces; | ||
|
@@ -32,6 +33,20 @@ public interface IInheritingPrototype | |
bool Abstract { get; } | ||
} | ||
|
||
public interface ILocalizedPrototype | ||
{ | ||
|
||
[DataField("name")] public string? SetName { get; } | ||
|
||
[DataField("description")] public string? SetDesc { get; } | ||
|
||
[ViewVariables] public string Name { get; } | ||
|
||
[ViewVariables] public string Description { get; } | ||
Comment on lines
+39
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I might be wrong, but I don't think the data-field attributes do anything on interfaces? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Also, these should have documentation comments |
||
|
||
public string? CustomLocalizationPrefix { get; } | ||
} | ||
|
||
public sealed class IdDataFieldAttribute : DataFieldAttribute | ||
{ | ||
public const string Name = "id"; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Needs to be per-prototypeNVM see my earlier comment, just remove it altogether.