diff --git a/Robust.Shared.IntegrationTests/Localization/LocalizationTests.cs b/Robust.Shared.IntegrationTests/Localization/LocalizationTests.cs index e630f0981e9..25d3582ecef 100644 --- a/Robust.Shared.IntegrationTests/Localization/LocalizationTests.cs +++ b/Robust.Shared.IntegrationTests/Localization/LocalizationTests.cs @@ -77,6 +77,14 @@ public void Setup() gender: male proper: true +# Values specified in fluent at PARENT and supplemented by empty child fluent. +- type: entity + id: TestInheritEmptyValueParent + +- type: entity + id: TestInheritEmptyValueChild + parent: TestInheritEmptyValueParent + # Attribures stored in grammar component - type: entity id: PropsInGrammar @@ -141,6 +149,14 @@ [1] B .gender = female .proper = false +ent-TestInheritEmptyValueParent = A + .desc = B + .gender = male + .proper = true + +ent-TestInheritEmptyValueChild = + .suffix = C + test-message-gender = { GENDER($entity) -> [male] male @@ -234,6 +250,7 @@ public void TestNumbers(object o1, object o2, object o3) [TestCase("PropsInLocOverriding")] [TestCase("PropsInGrammar")] [TestCase("TestInheritOverridingChild")] + [TestCase("TestInheritEmptyValueChild")] public void TestLocData(string prototype) { var loc = IoCManager.Resolve(); diff --git a/Robust.Shared/Localization/LocalizationManager.Entity.cs b/Robust.Shared/Localization/LocalizationManager.Entity.cs index a50f54be467..0fe08d04ff7 100644 --- a/Robust.Shared/Localization/LocalizationManager.Entity.cs +++ b/Robust.Shared/Localization/LocalizationManager.Entity.cs @@ -54,11 +54,17 @@ private EntityLocData CalcEntityLoc(string prototypeId) string? name = null; string? desc = null; string? suffix = null; + Dictionary? attributes = null; - foreach (var prototype in _prototype.EnumerateParents(prototypeId, true)) + // A child can have a locale entry just to override its suffix, while + // still inheriting the description from a parent. Hold these errors + // until we know no parent supplied a description either. + List<(string locId, List errs)>? deferredDescErrors = null; + + foreach (var (id, prototype) in _prototype.EnumerateAllParents(prototypeId, true)) { - var locId = prototype?.CustomLocalizationID ?? $"ent-{prototypeId}"; + var locId = prototype?.CustomLocalizationID ?? $"ent-{id}"; if (TryGetMessage(locId, out var bundle, out var msg)) { @@ -69,8 +75,11 @@ private EntityLocData CalcEntityLoc(string prototypeId) { // Only set name if the value isn't empty. // So that you can override *only* a desc/suffix. - name = bundle.FormatPattern(msg.Value, null, out var fmtErr); + var locName = bundle.FormatPattern(msg.Value, null, out var fmtErr); WriteWarningForErrs(fmtErr, locId); + + if (!string.IsNullOrEmpty(locName)) + name = locName; } if (msgAttrs.Count != 0) @@ -91,12 +100,13 @@ private EntityLocData CalcEntityLoc(string prototypeId) } } - var allErrors = new List(); if (desc == null && !bundle.TryGetMessage(locId, "desc", null, out var err1, out desc)) { desc = null; - allErrors.AddRange(err1); + // Defer: a parent prototype may still provide .desc. + deferredDescErrors ??= new List<(string, List)>(); + deferredDescErrors.Add((locId, err1.ToList())); } if (suffix == null @@ -104,16 +114,17 @@ private EntityLocData CalcEntityLoc(string prototypeId) { suffix = null; } - - WriteWarningForErrs(allErrors, locId); } } - name ??= prototype?.SetName; - desc ??= prototype?.SetDesc; - suffix ??= prototype?.SetSuffix; + if (prototype == null) + continue; - if (prototype?.LocProperties != null && prototype.LocProperties.Count != 0) + name ??= prototype.SetName; + desc ??= prototype.SetDesc; + suffix ??= prototype.SetSuffix; + + if (prototype.LocProperties.Count != 0) { foreach (var (attrib, value) in prototype.LocProperties) { @@ -126,6 +137,20 @@ private EntityLocData CalcEntityLoc(string prototypeId) } } + // Plenty of prototypes have no description at all, such as spawners, + // markers, and debug/admin entities. Keep the missing .desc warning + // visible for debugging, but don't treat it as a real localization issue. + if (desc == null && deferredDescErrors != null) + { + foreach (var (locId, errs) in deferredDescErrors) + { + foreach (var err in errs) + { + _logSawmill.Debug("Missing .desc for `{locId}`\n{e1}", locId, err); + } + } + } + // Attempt to infer suffix from entity categories if (suffix == null) { @@ -136,11 +161,11 @@ private EntityLocData CalcEntityLoc(string prototypeId) } return new EntityLocData( - name ?? "", + name ?? prototypeId, desc ?? "", suffix, attributes?.ToImmutableDictionary() ?? ImmutableDictionary.Empty); - } + } public EntityLocData GetEntityData(string prototypeId) diff --git a/Robust.Shared/Prototypes/EntityPrototype.cs b/Robust.Shared/Prototypes/EntityPrototype.cs index 8cf4d7098f9..26fe0d412d0 100644 --- a/Robust.Shared/Prototypes/EntityPrototype.cs +++ b/Robust.Shared/Prototypes/EntityPrototype.cs @@ -36,6 +36,7 @@ public sealed partial class EntityPrototype : IPrototype, IInheritingPrototype, private const int DEFAULT_RANGE = 200; [DataField("loc")] + [NeverPushInheritance] private Dictionary? _locPropertiesSet; /// @@ -51,12 +52,15 @@ public sealed partial class EntityPrototype : IPrototype, IInheritingPrototype, /// /// [DataField("name")] + [NeverPushInheritance] public string? SetName { get; private set; } [DataField("description")] + [NeverPushInheritance] public string? SetDesc { get; private set; } [DataField("suffix")] + [NeverPushInheritance] public string? SetSuffix { get; private set; } [DataField("categories"), Access(typeof(PrototypeManager))]