From 2cc409f12a1c056914285325bea3f2606b3686b0 Mon Sep 17 00:00:00 2001 From: iisaev Date: Sun, 18 Oct 2026 19:21:34 +0200 Subject: [PATCH] Collections are packages of their own A collection is no longer a file inside a mod: it is a package (folder or zip) named after its id with collection.json, cover and optional locale/, shipped in collections/ next to the mods or kept by the player in user://collections/. Covers and texts come from the package; mods do not ship collections. --- src/LocaleAtlasBuilder.cs | 15 +++++++++++---- src/LocaleAtlasDriverNode.cs | 6 ++++++ tests/LocaleAtlasTests.cs | 6 +++--- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/LocaleAtlasBuilder.cs b/src/LocaleAtlasBuilder.cs index 385f28c..6561a66 100644 --- a/src/LocaleAtlasBuilder.cs +++ b/src/LocaleAtlasBuilder.cs @@ -26,12 +26,17 @@ namespace Cthangover.LocaleAtlas /// manifest — mod_<id>_name/_desc used by the mod manager /// (optional: the manifest text is the fallback). /// - /// Collection files (collections/*.json) are also checked for inline - /// locale entries: a literal name/description is the - /// en text, every other project language needs a locale entry. + /// Collection packages come as sources named + /// + id: their locale/ files are declarations like a mod's, and their + /// collection.json is checked for inline locale entries — a + /// literal name/description is the en text, every other + /// project language needs a locale entry. /// public static class LocaleAtlasBuilder { + /// Source id prefix of a collection package (collection:<id>), as opposed to a mod. + public const string CollectionSourcePrefix = "collection:"; + /// Language of literal collection texts (fallback in ModCollection.GetLocalizedName). public const string CollectionBaseLanguage = "en"; @@ -66,6 +71,8 @@ namespace Cthangover.LocaleAtlas result.Mods.Add(modId); ScanLocaleFiles(modId, provider, result); ScanReferences(modId, provider, result, collections, documents); + if (modId.StartsWith(CollectionSourcePrefix, StringComparison.Ordinal)) + continue; result.References.Add(new LocaleReference { Key = $"mod_{modId}_name", ModId = modId, File = "manifest.json", Via = "manifest", Optional = true }); result.References.Add(new LocaleReference { Key = $"mod_{modId}_desc", ModId = modId, File = "manifest.json", Via = "manifest", Optional = true }); } @@ -229,7 +236,7 @@ namespace Cthangover.LocaleAtlas AddMatches(SceneText, text, file, modId, "scene", result); break; case ".json": - var isCollection = file.StartsWith("collections/", StringComparison.Ordinal); + var isCollection = file == "collection.json" && modId.StartsWith(CollectionSourcePrefix, StringComparison.Ordinal); if (!isCollection) AddMatches(JsonTextField, text, file, modId, "json", result); AddMatches(JsonAtKey, text, file, modId, isCollection ? "collection" : "json", result); diff --git a/src/LocaleAtlasDriverNode.cs b/src/LocaleAtlasDriverNode.cs index 54096f7..7b22399 100644 --- a/src/LocaleAtlasDriverNode.cs +++ b/src/LocaleAtlasDriverNode.cs @@ -91,6 +91,12 @@ namespace Cthangover.LocaleAtlas result.Add((info.Id, info.FileProvider)); } + foreach (var collection in ModCollectionStore.LoadAll()) + { + if (collection.Package != null) + result.Add((LocaleAtlasBuilder.CollectionSourcePrefix + collection.Id, collection.Package)); + } + return result; } diff --git a/tests/LocaleAtlasTests.cs b/tests/LocaleAtlasTests.cs index 18c1684..82eab15 100644 --- a/tests/LocaleAtlasTests.cs +++ b/tests/LocaleAtlasTests.cs @@ -170,13 +170,13 @@ namespace Cthangover.LocaleAtlas.Tests [ModTest("map/collection_texts_need_every_language")] static void CollectionTextsNeedEveryLanguage(ModTestContext ctx) { - var mod = new MemoryProvider("m") + var package = new MemoryProvider("c") .Add("locale/en.properties", "x = X\n") .Add("locale/ru.properties", "x = Икс\n") - .Add("collections/c.json", + .Add("collection.json", "{ \"id\": \"c\", \"name\": \"Game\", \"locale\": [ { \"lang\": \"ru\", \"name\": \"Игра\" } ],\n" + " \"mechanics\": [ { \"id\": \"m1\", \"name\": \"Story\" }, { \"id\": \"m2\", \"name\": \"@x\" } ] }"); - var result = LocaleAtlasBuilder.Build(new[] { ("m", (IModFileProvider)mod) }); + var result = LocaleAtlasBuilder.Build(new[] { (LocaleAtlasBuilder.CollectionSourcePrefix + "c", (IModFileProvider)package) }); var missing = result.Findings.Where(f => f.Code == "collection_text_missing").ToList(); ModAssert.Equal(1, missing.Count, "only the literal mechanic without ru: " + string.Join("; ", missing.Select(f => f.Message))); ModAssert.Equal("m1", missing[0].Key, "mechanic id");