Expose mod.yaml content to localisation.

Mod metadata, load screens and mod content is all now sourced from ftl files, allowing these items to be translated.

Translations are now initialized as part of ModData creation, as currently they are made available too late for the usage we need here.

The "modcontent" mod learns a new parameter for "Content.TranslationFile" - this allows a mod to provide the path of a translation file to the mod which it can load. This allows mods such as ra, cnc, d2k, ts to own the translations for their ModContent, yet still make them accessible to the modcontent mod.

CheckFluentReference learns to validate all these new fields to ensure translations have been set.
This commit is contained in:
RoosterDragon
2024-09-23 19:58:33 +01:00
committed by Gustas
parent d1583e8587
commit bb17cfa179
36 changed files with 292 additions and 144 deletions

View File

@@ -27,7 +27,6 @@ namespace OpenRA
{
public readonly string Id;
public readonly string Version;
public readonly string Title;
public readonly string LaunchPath;
public readonly string[] LaunchArgs;
public Sprite Icon { get; internal set; }
@@ -127,7 +126,6 @@ namespace OpenRA
{
new MiniYamlNode("Id", mod.Id),
new MiniYamlNode("Version", mod.Metadata.Version),
new MiniYamlNode("Title", mod.Metadata.Title),
new MiniYamlNode("LaunchPath", launchPath),
new MiniYamlNode("LaunchArgs", new[] { "Game.Mod=" + mod.Id }.Concat(launchArgs).JoinWith(", "))
}));

View File

@@ -395,7 +395,7 @@ namespace OpenRA
Mods = new InstalledMods(modSearchPaths, explicitModPaths);
Console.WriteLine("Internal mods:");
foreach (var mod in Mods)
Console.WriteLine($"\t{mod.Key}: {mod.Value.Metadata.Title} ({mod.Value.Metadata.Version})");
Console.WriteLine($"\t{mod.Key} ({mod.Value.Metadata.Version})");
modLaunchWrapper = args.GetValue("Engine.LaunchWrapper", null);
@@ -420,7 +420,7 @@ namespace OpenRA
Console.WriteLine("External mods:");
foreach (var mod in ExternalMods)
Console.WriteLine($"\t{mod.Key}: {mod.Value.Title} ({mod.Value.Version})");
Console.WriteLine($"\t{mod.Key} ({mod.Value.Version})");
InitializeMod(modID, args);
}
@@ -499,8 +499,8 @@ namespace OpenRA
Cursor = new CursorManager(ModData.CursorProvider, ModData.Manifest.CursorSheetSize);
var metadata = ModData.Manifest.Metadata;
if (!string.IsNullOrEmpty(metadata.WindowTitle))
Renderer.Window.SetWindowTitle(metadata.WindowTitle);
if (!string.IsNullOrEmpty(metadata.WindowTitleTranslated))
Renderer.Window.SetWindowTitle(metadata.WindowTitleTranslated);
PerfHistory.Items["render"].HasNormalTick = false;
PerfHistory.Items["batches"].HasNormalTick = false;

View File

@@ -45,12 +45,20 @@ namespace OpenRA
public class ModMetadata
{
public string Title;
public string Version;
public string Website;
public string WebIcon32;
public string WindowTitle;
public bool Hidden;
// FieldLoader used here, must matching naming in YAML.
#pragma warning disable IDE1006 // Naming Styles
[FluentReference]
readonly string Title;
public readonly string Version;
public readonly string Website;
public readonly string WebIcon32;
[FluentReference]
readonly string WindowTitle;
public readonly bool Hidden;
#pragma warning restore IDE1006 // Naming Styles
public string TitleTranslated => FluentProvider.GetString(Title);
public string WindowTitleTranslated => WindowTitle != null ? FluentProvider.GetString(WindowTitle) : null;
}
/// <summary>Describes what is to be loaded in order to run a mod.</summary>

View File

@@ -65,6 +65,8 @@ namespace OpenRA
Manifest.LoadCustomData(ObjectCreator);
FluentProvider.Initialize(this, DefaultFileSystem);
if (useLoadScreen)
{
LoadScreen = ObjectCreator.CreateObject<ILoadScreen>(Manifest.LoadScreen.Value);

View File

@@ -182,13 +182,13 @@ namespace OpenRA.Network
if (external != null && external.Version == Version)
{
// Use external mod registration to populate the section header
ModTitle = external.Title;
ModTitle = external.Id;
}
else if (Game.Mods.TryGetValue(Mod, out var mod))
{
// Use internal mod data to populate the section header, but
// on-connect switching must use the external mod plumbing.
ModTitle = mod.Metadata.Title;
ModTitle = mod.Metadata.TitleTranslated;
}
else
{
@@ -199,7 +199,7 @@ namespace OpenRA.Network
.FirstOrDefault(m => m.Id == Mod);
if (guessMod != null)
ModTitle = guessMod.Title;
ModTitle = guessMod.Id;
else
ModTitle = $"Unknown mod: {Mod}";
}
@@ -222,7 +222,7 @@ namespace OpenRA.Network
Map = server.Map.Uid;
Mod = manifest.Id;
Version = manifest.Metadata.Version;
ModTitle = manifest.Metadata.Title;
ModTitle = manifest.Metadata.TitleTranslated;
ModWebsite = manifest.Metadata.Website;
ModIcon32 = manifest.Metadata.WebIcon32;
Protected = !string.IsNullOrEmpty(server.Settings.Password);

View File

@@ -122,7 +122,7 @@ namespace OpenRA.Network
Log.Write("sync", $"Player: {Game.Settings.Player.Name} ({Platform.CurrentPlatform} {Environment.OSVersion} {Platform.RuntimeVersion})");
if (Game.IsHost)
Log.Write("sync", "Player is host.");
Log.Write("sync", $"Game ID: {orderManager.LobbyInfo.GlobalSettings.GameUid} (Mod: {mod.Title} at Version {mod.Version})");
Log.Write("sync", $"Game ID: {orderManager.LobbyInfo.GlobalSettings.GameUid} (Mod: {mod.TitleTranslated} at Version {mod.Version})");
Log.Write("sync", $"Sync for net frame {r.Frame} -------------");
Log.Write("sync", $"SharedRandom: {r.SyncedRandom} (#{r.TotalCount})");
Log.Write("sync", "Synced Traits:");

View File

@@ -31,8 +31,8 @@ namespace OpenRA
if (Game.ModData != null)
{
var mod = Game.ModData.Manifest.Metadata;
Log.Write("exception", $"{mod.Title} mod version {mod.Version}");
var manifest = Game.ModData.Manifest;
Log.Write("exception", $"{manifest.Id} mod version {manifest.Metadata.Version}");
}
if (Game.OrderManager != null && Game.OrderManager.World != null && Game.OrderManager.World.Map != null)