diff --git a/Content.Shared/Dataset/LocalizedDatasetPrototype.cs b/Content.Shared/Dataset/LocalizedDatasetPrototype.cs
new file mode 100644
index 000000000000..8be9967e309d
--- /dev/null
+++ b/Content.Shared/Dataset/LocalizedDatasetPrototype.cs
@@ -0,0 +1,94 @@
+using System.Collections;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization;
+
+namespace Content.Shared.Dataset;
+
+///
+/// A variant of intended to specify a sequence of LocId strings
+/// without having to copy-paste a ton of LocId strings into the YAML.
+///
+[Prototype]
+public sealed partial class LocalizedDatasetPrototype : IPrototype
+{
+ ///
+ /// Identifier for this prototype.
+ ///
+ [ViewVariables]
+ [IdDataField]
+ public string ID { get; private set; } = default!;
+
+ ///
+ /// Collection of LocId strings.
+ ///
+ [DataField]
+ public LocalizedDatasetValues Values { get; private set; } = [];
+}
+
+[Serializable, NetSerializable]
+[DataDefinition]
+public sealed partial class LocalizedDatasetValues : IReadOnlyList
+{
+ ///
+ /// String prepended to the index number to generate each LocId string.
+ /// For example, a prefix of tips-dataset- will generate tips-dataset-1,
+ /// tips-dataset-2, etc.
+ ///
+ [DataField(required: true)]
+ public string Prefix { get; private set; } = default!;
+
+ ///
+ /// How many values are in the dataset.
+ ///
+ [DataField(required: true)]
+ public int Count { get; private set; }
+
+ public string this[int index]
+ {
+ get
+ {
+ if (index > Count || index < 0)
+ throw new IndexOutOfRangeException();
+ return Prefix + index;
+ }
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return new Enumerator(this);
+ }
+
+ IEnumerator IEnumerable.GetEnumerator()
+ {
+ return GetEnumerator();
+ }
+
+ public sealed class Enumerator : IEnumerator
+ {
+ private int _index = 0; // Whee, 1-indexing
+
+ private readonly LocalizedDatasetValues _values;
+
+ public Enumerator(LocalizedDatasetValues values)
+ {
+ _values = values;
+ }
+
+ public string Current => _values.Prefix + _index;
+
+ object IEnumerator.Current => Current;
+
+ public void Dispose() { }
+
+ public bool MoveNext()
+ {
+ _index++;
+ return _index <= _values.Count;
+ }
+
+ public void Reset()
+ {
+ _index = 0;
+ }
+ }
+}
diff --git a/Content.Shared/Random/Helpers/SharedRandomExtensions.cs b/Content.Shared/Random/Helpers/SharedRandomExtensions.cs
index 20e57e942128..f5fbc1bd24e8 100644
--- a/Content.Shared/Random/Helpers/SharedRandomExtensions.cs
+++ b/Content.Shared/Random/Helpers/SharedRandomExtensions.cs
@@ -12,6 +12,11 @@ public static string Pick(this IRobustRandom random, DatasetPrototype prototype)
return random.Pick(prototype.Values);
}
+ public static string Pick(this IRobustRandom random, LocalizedDatasetPrototype prototype)
+ {
+ return random.Pick(prototype.Values);
+ }
+
public static string Pick(this IWeightedRandomPrototype prototype, System.Random random)
{
var picks = prototype.Weights;
diff --git a/Content.Tests/Shared/LocalizedDatasetPrototypeTest.cs b/Content.Tests/Shared/LocalizedDatasetPrototypeTest.cs
new file mode 100644
index 000000000000..0ec4c076f5b4
--- /dev/null
+++ b/Content.Tests/Shared/LocalizedDatasetPrototypeTest.cs
@@ -0,0 +1,59 @@
+using System;
+using Content.Shared.Dataset;
+using NUnit.Framework;
+using Robust.Shared.Collections;
+using Robust.Shared.IoC;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.Manager;
+
+namespace Content.Tests.Shared;
+
+[TestFixture]
+[TestOf(typeof(LocalizedDatasetPrototype))]
+public sealed class LocalizedDatasetPrototypeTest : ContentUnitTest
+{
+ private IPrototypeManager _prototypeManager;
+
+ [OneTimeSetUp]
+ public void OneTimeSetup()
+ {
+ IoCManager.Resolve().Initialize();
+ _prototypeManager = IoCManager.Resolve();
+ _prototypeManager.Initialize();
+ _prototypeManager.LoadString(TestPrototypes);
+ _prototypeManager.ResolveResults();
+ }
+
+ private const string TestPrototypes = @"
+- type: localizedDataset
+ id: Test
+ values:
+ prefix: test-dataset-
+ count: 4
+";
+
+ [Test]
+ public void LocalizedDatasetTest()
+ {
+ var testPrototype = _prototypeManager.Index("Test");
+ var values = new ValueList();
+ foreach (var value in testPrototype.Values)
+ {
+ values.Add(value);
+ }
+
+ // Make sure we get the right number of values
+ Assert.That(values, Has.Count.EqualTo(4));
+
+ // Make sure indexing works as expected
+ Assert.That(values[0], Is.EqualTo("test-dataset-1"));
+ Assert.That(values[1], Is.EqualTo("test-dataset-2"));
+ Assert.That(values[2], Is.EqualTo("test-dataset-3"));
+ Assert.That(values[3], Is.EqualTo("test-dataset-4"));
+ Assert.Throws(() => { var x = values[4]; });
+ Assert.Throws(() => { var x = values[-1]; });
+
+ // Make sure that the enumerator gets all of the values
+ Assert.That(testPrototype.Values[testPrototype.Values.Count], Is.EqualTo("test-dataset-4"));
+ }
+}