From bb17cfa17990bd3cb8d6842db75f1d6ead824207 Mon Sep 17 00:00:00 2001 From: RoosterDragon Date: Mon, 23 Sep 2024 19:58:33 +0100 Subject: [PATCH] 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. --- OpenRA.Game/ExternalMods.cs | 2 - OpenRA.Game/Game.cs | 8 +- OpenRA.Game/Manifest.cs | 20 ++-- OpenRA.Game/ModData.cs | 2 + OpenRA.Game/Network/GameServer.cs | 8 +- OpenRA.Game/Network/SyncReport.cs | 2 +- OpenRA.Game/Support/ExceptionHandler.cs | 4 +- OpenRA.Mods.Cnc/CncLoadScreen.cs | 9 +- .../FileSystem/DefaultFileSystemLoader.cs | 7 +- .../Lint/CheckFluentReferences.cs | 94 ++++++++++--------- OpenRA.Mods.Common/Lint/CheckFluentSyntax.cs | 5 +- .../LoadScreens/LogoStripeLoadScreen.cs | 12 ++- .../LoadScreens/ModContentLoadScreen.cs | 8 +- OpenRA.Mods.Common/ModContent.cs | 4 + .../UtilityCommands/CheckYaml.cs | 2 +- .../Widgets/Logic/ConnectionLogic.cs | 2 +- .../Installation/InstallFromSourceLogic.cs | 8 +- .../Logic/Installation/ModContentLogic.cs | 29 ++++-- .../Installation/ModContentPromptLogic.cs | 24 +++-- .../Widgets/Logic/MainMenuLogic.cs | 7 +- OpenRA.Server/Program.cs | 3 - mods/cnc/languages/en.ftl | 6 ++ mods/cnc/languages/modcontent/en.ftl | 13 +++ mods/cnc/mod.yaml | 21 +++-- mods/d2k/languages/en.ftl | 6 ++ mods/d2k/languages/modcontent/en.ftl | 12 +++ mods/d2k/mod.yaml | 19 ++-- mods/modcontent/content.yaml | 8 +- mods/modcontent/languages/en.ftl | 2 + mods/modcontent/mod.yaml | 3 +- mods/ra/languages/en.ftl | 6 ++ mods/ra/languages/modcontent/en.ftl | 16 ++++ mods/ra/mod.yaml | 27 +++--- mods/ts/languages/en.ftl | 6 ++ mods/ts/languages/modcontent/en.ftl | 12 +++ mods/ts/mod.yaml | 19 ++-- 36 files changed, 292 insertions(+), 144 deletions(-) create mode 100644 mods/cnc/languages/en.ftl create mode 100644 mods/cnc/languages/modcontent/en.ftl create mode 100644 mods/d2k/languages/en.ftl create mode 100644 mods/d2k/languages/modcontent/en.ftl create mode 100644 mods/modcontent/languages/en.ftl create mode 100644 mods/ra/languages/en.ftl create mode 100644 mods/ra/languages/modcontent/en.ftl create mode 100644 mods/ts/languages/en.ftl create mode 100644 mods/ts/languages/modcontent/en.ftl diff --git a/OpenRA.Game/ExternalMods.cs b/OpenRA.Game/ExternalMods.cs index d79ce87b52..2cb1c42cf9 100644 --- a/OpenRA.Game/ExternalMods.cs +++ b/OpenRA.Game/ExternalMods.cs @@ -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(", ")) })); diff --git a/OpenRA.Game/Game.cs b/OpenRA.Game/Game.cs index d61023407f..e71d7c7a63 100644 --- a/OpenRA.Game/Game.cs +++ b/OpenRA.Game/Game.cs @@ -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; diff --git a/OpenRA.Game/Manifest.cs b/OpenRA.Game/Manifest.cs index 3b086f00f5..c3e4dbd8a8 100644 --- a/OpenRA.Game/Manifest.cs +++ b/OpenRA.Game/Manifest.cs @@ -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; } /// Describes what is to be loaded in order to run a mod. diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index 35155b26aa..200bc578bd 100644 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -65,6 +65,8 @@ namespace OpenRA Manifest.LoadCustomData(ObjectCreator); + FluentProvider.Initialize(this, DefaultFileSystem); + if (useLoadScreen) { LoadScreen = ObjectCreator.CreateObject(Manifest.LoadScreen.Value); diff --git a/OpenRA.Game/Network/GameServer.cs b/OpenRA.Game/Network/GameServer.cs index b9645cd1da..ce4fe4f3e3 100644 --- a/OpenRA.Game/Network/GameServer.cs +++ b/OpenRA.Game/Network/GameServer.cs @@ -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); diff --git a/OpenRA.Game/Network/SyncReport.cs b/OpenRA.Game/Network/SyncReport.cs index 19b18cfcf5..4c563a17b6 100644 --- a/OpenRA.Game/Network/SyncReport.cs +++ b/OpenRA.Game/Network/SyncReport.cs @@ -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:"); diff --git a/OpenRA.Game/Support/ExceptionHandler.cs b/OpenRA.Game/Support/ExceptionHandler.cs index 87d1034fc5..cdffe39ff3 100644 --- a/OpenRA.Game/Support/ExceptionHandler.cs +++ b/OpenRA.Game/Support/ExceptionHandler.cs @@ -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) diff --git a/OpenRA.Mods.Cnc/CncLoadScreen.cs b/OpenRA.Mods.Cnc/CncLoadScreen.cs index 2dbb3f5745..5b811d7d26 100644 --- a/OpenRA.Mods.Cnc/CncLoadScreen.cs +++ b/OpenRA.Mods.Cnc/CncLoadScreen.cs @@ -19,6 +19,9 @@ namespace OpenRA.Mods.Cnc { public sealed class CncLoadScreen : SheetLoadScreen { + [FluentReference] + const string Loading = "loadscreen-loading"; + int loadTick; Sprite nodLogo, gdiLogo, evaLogo, brightBlock, dimBlock; @@ -31,11 +34,15 @@ namespace OpenRA.Mods.Cnc int lastDensity; Size lastResolution; + string message = ""; + public override void Init(ModData modData, Dictionary info) { base.Init(modData, info); versionText = modData.Manifest.Metadata.Version; + + message = FluentProvider.GetString(Loading); } public override void DisplayInner(Renderer r, Sheet s, int density) @@ -89,7 +96,7 @@ namespace OpenRA.Mods.Cnc if (r.Fonts != null) { var loadingFont = r.Fonts["BigBold"]; - var loadingText = Info["Text"]; + var loadingText = message; var loadingPos = new float2((bounds.Width - loadingFont.Measure(loadingText).X) / 2, barY); loadingFont.DrawText(loadingText, loadingPos, Color.Gray); diff --git a/OpenRA.Mods.Common/FileSystem/DefaultFileSystemLoader.cs b/OpenRA.Mods.Common/FileSystem/DefaultFileSystemLoader.cs index 8cd69d1cd6..f20753b930 100644 --- a/OpenRA.Mods.Common/FileSystem/DefaultFileSystemLoader.cs +++ b/OpenRA.Mods.Common/FileSystem/DefaultFileSystemLoader.cs @@ -48,7 +48,12 @@ namespace OpenRA.Mods.Common.FileSystem if (contentInstalled) return false; - Game.InitializeMod(content.ContentInstallerMod, new Arguments("Content.Mod=" + modData.Manifest.Id)); + string translationPath; + using (var fs = (FileStream)modData.DefaultFileSystem.Open(content.Translation)) + translationPath = fs.Name; + Game.InitializeMod( + content.ContentInstallerMod, + new Arguments(new[] { "Content.Mod=" + modData.Manifest.Id, "Content.TranslationFile=" + translationPath })); return true; } } diff --git a/OpenRA.Mods.Common/Lint/CheckFluentReferences.cs b/OpenRA.Mods.Common/Lint/CheckFluentReferences.cs index 825132d503..599dce9a70 100644 --- a/OpenRA.Mods.Common/Lint/CheckFluentReferences.cs +++ b/OpenRA.Mods.Common/Lint/CheckFluentReferences.cs @@ -43,16 +43,17 @@ namespace OpenRA.Mods.Common.Lint var mapTranslations = FieldLoader.GetValue("value", map.TranslationDefinitions.Value); - foreach (var language in GetModLanguages(modData)) + var allModTranslations = modData.Manifest.Translations.Append(modData.Manifest.Get().Translation).ToArray(); + foreach (var language in GetModLanguages(allModTranslations)) { // Check keys and variables are not missing across all language files. // But for maps we don't warn on unused keys. They might be unused on *this* map, // but the mod or another map may use them and we don't have sight of that. CheckKeys( - modData.Manifest.Translations.Concat(mapTranslations), map.Open, usedKeys, + allModTranslations.Concat(mapTranslations), map.Open, usedKeys, language, _ => false, emitError, emitWarning); - var modFluentBundle = new FluentBundle(language, modData.Manifest.Translations, modData.DefaultFileSystem, _ => { }); + var modFluentBundle = new FluentBundle(language, allModTranslations, modData.DefaultFileSystem, _ => { }); var mapFluentBundle = new FluentBundle(language, mapTranslations, map, error => emitError(error.Message)); foreach (var group in usedKeys.KeysWithContext) @@ -78,14 +79,15 @@ namespace OpenRA.Mods.Common.Lint foreach (var context in usedKeys.EmptyKeyContexts) emitWarning($"Empty key in mod translation files required by {context}"); - foreach (var language in GetModLanguages(modData)) + var allModTranslations = modData.Manifest.Translations.Append(modData.Manifest.Get().Translation).ToArray(); + foreach (var language in GetModLanguages(allModTranslations)) { Console.WriteLine($"Testing language: {language}"); CheckModWidgets(modData, usedKeys, testedFields); // With the fully populated keys, check keys and variables are not missing and not unused across all language files. var keyWithAttrs = CheckKeys( - modData.Manifest.Translations, modData.DefaultFileSystem.Open, usedKeys, + allModTranslations, modData.DefaultFileSystem.Open, usedKeys, language, file => !modData.Manifest.AllowUnusedTranslationsInExternalPackages || @@ -113,9 +115,9 @@ namespace OpenRA.Mods.Common.Lint $"`{field.ReflectedType.Name}.{field.Name}` - previous warnings may be incorrect"); } - static IEnumerable GetModLanguages(ModData modData) + static IEnumerable GetModLanguages(IEnumerable translations) { - return modData.Manifest.Translations + return translations .Select(filename => FilenameRegex.Match(filename).Groups["language"].Value) .Distinct() .OrderBy(l => l); @@ -249,51 +251,55 @@ namespace OpenRA.Mods.Common.Lint .Where(t => t.IsSubclassOf(typeof(TraitInfo)) || t.IsSubclassOf(typeof(Warhead))) .SelectMany(t => t.GetFields().Where(f => f.HasAttribute()))); - // HACK: Need to hardcode the custom loader for GameSpeeds. - var gameSpeeds = modData.Manifest.Get(); - var gameSpeedNameField = typeof(GameSpeed).GetField(nameof(GameSpeed.Name)); - var gameSpeedFluentReference = Utility.GetCustomAttributes(gameSpeedNameField, true)[0]; - testedFields.Add(gameSpeedNameField); - foreach (var speed in gameSpeeds.Speeds.Values) - usedKeys.Add(speed.Name, gameSpeedFluentReference, $"`{nameof(GameSpeed)}.{nameof(GameSpeed.Name)}`"); + // TODO: linter does not work with LoadUsing + GetUsedTranslationKeysFromFieldsWithTranslationReferenceAttribute( + usedKeys, testedFields, Utility.GetFields(typeof(GameSpeed)), modData.Manifest.Get().Speeds.Values); // TODO: linter does not work with LoadUsing - foreach (var actorInfo in modData.DefaultRules.Actors) + GetUsedTranslationKeysFromFieldsWithTranslationReferenceAttribute( + usedKeys, testedFields, + Utility.GetFields(typeof(ResourceRendererInfo.ResourceTypeInfo)), + modData.DefaultRules.Actors + .SelectMany(actorInfo => actorInfo.Value.TraitInfos()) + .SelectMany(info => info.ResourceTypes.Values)); + + const BindingFlags Binding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; + var constFields = modData.ObjectCreator.GetTypes().SelectMany(modType => modType.GetFields(Binding)).Where(f => f.IsLiteral); + GetUsedTranslationKeysFromFieldsWithTranslationReferenceAttribute( + usedKeys, testedFields, constFields, new[] { (object)null }); + + var modMetadataFields = typeof(ModMetadata).GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + GetUsedTranslationKeysFromFieldsWithTranslationReferenceAttribute( + usedKeys, testedFields, modMetadataFields, new[] { modData.Manifest.Metadata }); + + var modContent = modData.Manifest.Get(); + GetUsedTranslationKeysFromFieldsWithTranslationReferenceAttribute( + usedKeys, testedFields, Utility.GetFields(typeof(ModContent)), new[] { modContent }); + GetUsedTranslationKeysFromFieldsWithTranslationReferenceAttribute( + usedKeys, testedFields, Utility.GetFields(typeof(ModContent.ModPackage)), modContent.Packages.Values); + + return (usedKeys, testedFields); + } + + static void GetUsedTranslationKeysFromFieldsWithTranslationReferenceAttribute( + Keys usedKeys, List testedFields, + IEnumerable newFields, IEnumerable objects) + { + var fieldsWithAttribute = + newFields + .Select(f => (Field: f, FluentReference: Utility.GetCustomAttributes(f, true).SingleOrDefault())) + .Where(x => x.FluentReference != null) + .ToArray(); + testedFields.AddRange(fieldsWithAttribute.Select(x => x.Field)); + foreach (var obj in objects) { - foreach (var info in actorInfo.Value.TraitInfos()) + foreach (var (field, fluentReference) in fieldsWithAttribute) { - var resourceTypeNameField = typeof(ResourceRendererInfo.ResourceTypeInfo).GetField(nameof(ResourceRendererInfo.ResourceTypeInfo.Name)); - var resourceTypeFluentReference = Utility.GetCustomAttributes(resourceTypeNameField, true)[0]; - testedFields.Add(resourceTypeNameField); - foreach (var resourceTypes in info.ResourceTypes) - usedKeys.Add( - resourceTypes.Value.Name, - resourceTypeFluentReference, - $"`{nameof(ResourceRendererInfo.ResourceTypeInfo)}.{nameof(ResourceRendererInfo.ResourceTypeInfo.Name)}`"); - } - } - - foreach (var modType in modData.ObjectCreator.GetTypes()) - { - const BindingFlags Binding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; - foreach (var field in modType.GetFields(Binding)) - { - // Checking for constant string fields. - if (!field.IsLiteral) - continue; - - var fluentReference = Utility.GetCustomAttributes(field, true).SingleOrDefault(); - if (fluentReference == null) - continue; - - testedFields.Add(field); - var keys = LintExts.GetFieldValues(null, field, fluentReference.DictionaryReference); + var keys = LintExts.GetFieldValues(obj, field, fluentReference.DictionaryReference); foreach (var key in keys) usedKeys.Add(key, fluentReference, $"`{field.ReflectedType.Name}.{field.Name}`"); } } - - return (usedKeys, testedFields); } static void CheckModWidgets(ModData modData, Keys usedKeys, List testedFields) diff --git a/OpenRA.Mods.Common/Lint/CheckFluentSyntax.cs b/OpenRA.Mods.Common/Lint/CheckFluentSyntax.cs index 1be773feb9..aa6360243b 100644 --- a/OpenRA.Mods.Common/Lint/CheckFluentSyntax.cs +++ b/OpenRA.Mods.Common/Lint/CheckFluentSyntax.cs @@ -30,10 +30,11 @@ namespace OpenRA.Mods.Common.Lint void ILintPass.Run(Action emitError, Action emitWarning, ModData modData) { - Run(emitError, emitWarning, modData.DefaultFileSystem, modData.Manifest.Translations); + var allModTranslations = modData.Manifest.Translations.Append(modData.Manifest.Get().Translation); + Run(emitError, emitWarning, modData.DefaultFileSystem, allModTranslations); } - static void Run(Action emitError, Action emitWarning, IReadOnlyFileSystem fileSystem, string[] paths) + static void Run(Action emitError, Action emitWarning, IReadOnlyFileSystem fileSystem, IEnumerable paths) { foreach (var path in paths) { diff --git a/OpenRA.Mods.Common/LoadScreens/LogoStripeLoadScreen.cs b/OpenRA.Mods.Common/LoadScreens/LogoStripeLoadScreen.cs index 1bcf46b933..9c87505f23 100644 --- a/OpenRA.Mods.Common/LoadScreens/LogoStripeLoadScreen.cs +++ b/OpenRA.Mods.Common/LoadScreens/LogoStripeLoadScreen.cs @@ -9,7 +9,9 @@ */ #endregion +using System; using System.Collections.Generic; +using System.Linq; using OpenRA.Graphics; using OpenRA.Mods.Common.Widgets; using OpenRA.Primitives; @@ -18,6 +20,9 @@ namespace OpenRA.Mods.Common.LoadScreens { public sealed class LogoStripeLoadScreen : SheetLoadScreen { + [FluentReference] + const string Loading = "loadscreen-loading"; + Rectangle stripeRect; float2 logoPos; Sprite stripe, logo; @@ -26,14 +31,13 @@ namespace OpenRA.Mods.Common.LoadScreens int lastDensity; Size lastResolution; - string[] messages = { "Loading..." }; + string[] messages = Array.Empty(); public override void Init(ModData modData, Dictionary info) { base.Init(modData, info); - if (info.TryGetValue("Text", out var text)) - messages = text.Split(','); + messages = FluentProvider.GetString(Loading).Split(',').Select(x => x.Trim()).ToArray(); } public override void DisplayInner(Renderer r, Sheet s, int density) @@ -59,7 +63,7 @@ namespace OpenRA.Mods.Common.LoadScreens if (logo != null) r.RgbaSpriteRenderer.DrawSprite(logo, logoPos); - if (r.Fonts != null) + if (r.Fonts != null && messages.Length > 0) { var text = messages.Random(Game.CosmeticRandom); var textSize = r.Fonts["Bold"].Measure(text); diff --git a/OpenRA.Mods.Common/LoadScreens/ModContentLoadScreen.cs b/OpenRA.Mods.Common/LoadScreens/ModContentLoadScreen.cs index 84505703cf..93ff7d4505 100644 --- a/OpenRA.Mods.Common/LoadScreens/ModContentLoadScreen.cs +++ b/OpenRA.Mods.Common/LoadScreens/ModContentLoadScreen.cs @@ -52,6 +52,10 @@ namespace OpenRA.Mods.Common.LoadScreens if (modId == null || !Game.Mods.TryGetValue(modId, out var selectedMod)) throw new InvalidOperationException("Invalid or missing Content.Mod argument."); + var translationFilePath = args.GetValue("Content.TranslationFile", null); + if (translationFilePath == null || !File.Exists(translationFilePath)) + throw new InvalidOperationException("Invalid or missing Content.TranslationFile argument."); + var content = selectedMod.Get(Game.ModData.ObjectCreator); Ui.LoadWidget("MODCONTENT_BACKGROUND", Ui.Root, new WidgetArgs()); @@ -63,6 +67,7 @@ namespace OpenRA.Mods.Common.LoadScreens { "continueLoading", () => Game.RunAfterTick(() => Game.InitializeMod(modId, new Arguments())) }, { "mod", selectedMod }, { "content", content }, + { "translationFilePath", translationFilePath }, }; Ui.OpenWindow("CONTENT_PROMPT_PANEL", widgetArgs); @@ -71,9 +76,10 @@ namespace OpenRA.Mods.Common.LoadScreens { var widgetArgs = new WidgetArgs { + { "onCancel", () => Game.RunAfterTick(() => Game.InitializeMod(modId, new Arguments())) }, { "mod", selectedMod }, { "content", content }, - { "onCancel", () => Game.RunAfterTick(() => Game.InitializeMod(modId, new Arguments())) } + { "translationFilePath", translationFilePath }, }; Ui.OpenWindow("CONTENT_PANEL", widgetArgs); diff --git a/OpenRA.Mods.Common/ModContent.cs b/OpenRA.Mods.Common/ModContent.cs index 81fcab8fde..4301153ed6 100644 --- a/OpenRA.Mods.Common/ModContent.cs +++ b/OpenRA.Mods.Common/ModContent.cs @@ -21,6 +21,7 @@ namespace OpenRA { public class ModPackage { + [FluentReference] public readonly string Title; public readonly string Identifier; public readonly string[] TestFiles = Array.Empty(); @@ -100,10 +101,13 @@ namespace OpenRA } } + [FluentReference] public readonly string InstallPromptMessage; public readonly string QuickDownload; + [FluentReference] public readonly string HeaderMessage; public readonly string ContentInstallerMod = "modcontent"; + public readonly string Translation; [FieldLoader.LoadUsing(nameof(LoadPackages))] public readonly Dictionary Packages = new(); diff --git a/OpenRA.Mods.Common/UtilityCommands/CheckYaml.cs b/OpenRA.Mods.Common/UtilityCommands/CheckYaml.cs index 8b31d0d7c0..3828d73dec 100644 --- a/OpenRA.Mods.Common/UtilityCommands/CheckYaml.cs +++ b/OpenRA.Mods.Common/UtilityCommands/CheckYaml.cs @@ -62,7 +62,7 @@ namespace OpenRA.Mods.Common.UtilityCommands var maps = new List<(IReadWritePackage Package, string Map)>(); if (args.Length < 2) { - Console.WriteLine($"Testing mod: {modData.Manifest.Metadata.Title}"); + Console.WriteLine($"Testing mod: {modData.Manifest.Metadata.TitleTranslated}"); // Run all rule checks on the default mod rules. CheckRules(modData, modData.DefaultRules); diff --git a/OpenRA.Mods.Common/Widgets/Logic/ConnectionLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/ConnectionLogic.cs index 79e083b846..44379a9d3e 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/ConnectionLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/ConnectionLogic.cs @@ -193,7 +193,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic var switchButton = panel.Get("SWITCH_BUTTON"); var mod = CurrentServerSettings.ServerExternalMod; - var modTitle = mod.Title; + var modTitle = mod.Id; var modVersion = mod.Version; switchButton.OnClick = () => diff --git a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromSourceLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromSourceLogic.cs index ade0814e0c..2803698bcf 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromSourceLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Installation/InstallFromSourceLogic.cs @@ -87,6 +87,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic readonly ModData modData; readonly ModContent content; readonly Dictionary sources; + readonly FluentBundle externalFluentBundle; readonly Widget panel; readonly LabelWidget titleLabel; @@ -116,11 +117,13 @@ namespace OpenRA.Mods.Common.Widgets.Logic Mode visible = Mode.Progress; [ObjectCreator.UseCtor] - public InstallFromSourceLogic(Widget widget, ModData modData, ModContent content, Dictionary sources) + public InstallFromSourceLogic( + Widget widget, ModData modData, ModContent content, Dictionary sources, FluentBundle externalFluentBundle) { this.modData = modData; this.content = content; this.sources = sources; + this.externalFluentBundle = externalFluentBundle; Log.AddChannel("install", "install.log"); @@ -339,7 +342,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic { var containerWidget = (ContainerWidget)checkboxListTemplate.Clone(); var checkboxWidget = containerWidget.Get("PACKAGE_CHECKBOX"); - checkboxWidget.GetText = () => package.Title; + var title = externalFluentBundle.GetString(package.Title); + checkboxWidget.GetText = () => title; checkboxWidget.IsDisabled = () => package.Required; checkboxWidget.IsChecked = () => selectedPackages[package.Identifier]; checkboxWidget.OnClick = () => selectedPackages[package.Identifier] = !selectedPackages[package.Identifier]; diff --git a/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentLogic.cs index fcd6ba7b7c..c09c74d90b 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentLogic.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using OpenRA.FileSystem; using OpenRA.Widgets; @@ -30,10 +31,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic readonly Dictionary sources = new(); readonly Dictionary downloads = new(); + readonly FluentBundle externalFluentBundle; + bool sourceAvailable; [ObjectCreator.UseCtor] - public ModContentLogic(Widget widget, Manifest mod, ModContent content, Action onCancel) + public ModContentLogic(Widget widget, Manifest mod, ModContent content, Action onCancel, string translationFilePath) { this.content = content; @@ -58,20 +61,25 @@ namespace OpenRA.Mods.Common.Widgets.Logic modFileSystem.UnmountAll(); + externalFluentBundle = new FluentBundle(Game.Settings.Player.Language, File.ReadAllText(translationFilePath), _ => { }); + scrollPanel = panel.Get("PACKAGES"); template = scrollPanel.Get("PACKAGE_TEMPLATE"); var headerTemplate = panel.Get("HEADER_TEMPLATE"); - var headerLines = !string.IsNullOrEmpty(content.HeaderMessage) ? content.HeaderMessage.Replace("\\n", "\n").Split('\n') : Array.Empty(); + var headerLines = + !string.IsNullOrEmpty(content.HeaderMessage) + ? externalFluentBundle.GetString(content.HeaderMessage) + : null; var headerHeight = 0; - foreach (var l in headerLines) + if (headerLines != null) { - var line = (LabelWidget)headerTemplate.Clone(); - line.GetText = () => l; - line.Bounds.Y += headerHeight; - panel.AddChild(line); + var label = (LabelWidget)headerTemplate.Clone(); + label.GetText = () => headerLines; + label.IncreaseHeightToFitCurrentText(); + panel.AddChild(label); - headerHeight += headerTemplate.Bounds.Height; + headerHeight += label.Bounds.Height; } panel.Bounds.Height += headerHeight; @@ -85,7 +93,8 @@ namespace OpenRA.Mods.Common.Widgets.Logic sourceButton.OnClick = () => Ui.OpenWindow("SOURCE_INSTALL_PANEL", new WidgetArgs { { "sources", sources }, - { "content", content } + { "content", content }, + { "externalFluentBundle", externalFluentBundle }, }); var backButton = panel.Get("BACK_BUTTON"); @@ -109,7 +118,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic { var container = template.Clone(); var titleWidget = container.Get("TITLE"); - var title = p.Value.Title; + var title = externalFluentBundle.GetString(p.Value.Title); titleWidget.GetText = () => title; var requiredWidget = container.Get("REQUIRED"); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentPromptLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentPromptLogic.cs index 0499f535a5..14868fb283 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentPromptLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Installation/ModContentPromptLogic.cs @@ -27,14 +27,17 @@ namespace OpenRA.Mods.Common.Widgets.Logic const string Quit = "button-quit"; readonly ModContent content; + readonly FluentBundle externalFluentBundle; bool requiredContentInstalled; [ObjectCreator.UseCtor] - public ModContentPromptLogic(ModData modData, Widget widget, Manifest mod, ModContent content, Action continueLoading) + public ModContentPromptLogic(ModData modData, Widget widget, Manifest mod, ModContent content, Action continueLoading, string translationFilePath) { this.content = content; CheckRequiredContentInstalled(); + externalFluentBundle = new FluentBundle(Game.Settings.Player.Language, File.ReadAllText(translationFilePath), _ => { }); + var continueMessage = FluentProvider.GetString(Continue); var quitMessage = FluentProvider.GetString(Quit); @@ -42,17 +45,17 @@ namespace OpenRA.Mods.Common.Widgets.Logic var headerTemplate = panel.Get("HEADER_TEMPLATE"); var headerLines = !string.IsNullOrEmpty(content.InstallPromptMessage) - ? content.InstallPromptMessage.Replace("\\n", "\n").Split('\n') - : Array.Empty(); + ? externalFluentBundle.GetString(content.InstallPromptMessage) + : null; var headerHeight = 0; - foreach (var l in headerLines) + if (headerLines != null) { - var line = (LabelWidget)headerTemplate.Clone(); - line.GetText = () => l; - line.Bounds.Y += headerHeight; - panel.AddChild(line); + var label = (LabelWidget)headerTemplate.Clone(); + label.GetText = () => headerLines; + label.IncreaseHeightToFitCurrentText(); + panel.AddChild(label); - headerHeight += headerTemplate.Bounds.Height; + headerHeight += label.Bounds.Height; } panel.Bounds.Height += headerHeight; @@ -64,9 +67,10 @@ namespace OpenRA.Mods.Common.Widgets.Logic { Ui.OpenWindow("CONTENT_PANEL", new WidgetArgs { + { "onCancel", CheckRequiredContentInstalled }, { "mod", mod }, { "content", content }, - { "onCancel", CheckRequiredContentInstalled } + { "translationFilePath", translationFilePath }, }); }; diff --git a/OpenRA.Mods.Common/Widgets/Logic/MainMenuLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/MainMenuLogic.cs index 6c26a82cb8..944072f975 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/MainMenuLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/MainMenuLogic.cs @@ -93,7 +93,12 @@ namespace OpenRA.Mods.Common.Widgets.Logic return; var content = modData.Manifest.Get(); - Game.InitializeMod(content.ContentInstallerMod, new Arguments(new[] { "Content.Mod=" + modData.Manifest.Id })); + string translationPath; + using (var fs = (FileStream)modData.DefaultFileSystem.Open(content.Translation)) + translationPath = fs.Name; + Game.InitializeMod( + content.ContentInstallerMod, + new Arguments(new[] { "Content.Mod=" + modData.Manifest.Id, "Content.TranslationFile=" + translationPath })); }); }; } diff --git a/OpenRA.Server/Program.cs b/OpenRA.Server/Program.cs index b2a93aab39..6e27bef7dc 100644 --- a/OpenRA.Server/Program.cs +++ b/OpenRA.Server/Program.cs @@ -93,9 +93,6 @@ namespace OpenRA.Server modData.MapCache.LoadPreviewImages = false; // PERF: Server doesn't need previews, save memory by not loading them. modData.MapCache.LoadMaps(); - // HACK: Related to the above one, initialize fluent so we can resolve lobby option ids to strings. - FluentProvider.Initialize(modData, modData.DefaultFileSystem); - var endpoints = new List { new(IPAddress.IPv6Any, settings.ListenPort), new(IPAddress.Any, settings.ListenPort) }; var server = new Server(endpoints, settings, modData, ServerType.Dedicated); diff --git a/mods/cnc/languages/en.ftl b/mods/cnc/languages/en.ftl new file mode 100644 index 0000000000..51f49060d1 --- /dev/null +++ b/mods/cnc/languages/en.ftl @@ -0,0 +1,6 @@ +## Metadata +mod-title = Tiberian Dawn +mod-windowtitle = OpenRA - Tiberian Dawn + +## CncLoadScreen +loadscreen-loading = Loading diff --git a/mods/cnc/languages/modcontent/en.ftl b/mods/cnc/languages/modcontent/en.ftl new file mode 100644 index 0000000000..e105a78a0c --- /dev/null +++ b/mods/cnc/languages/modcontent/en.ftl @@ -0,0 +1,13 @@ +modcontent-installprompt = + Tiberian Dawn requires artwork and audio from the original game. + + Quick Install will automatically download this content (without music or videos) from a mirror of the 2007 C&C Gold freeware release. + + Advanced Install includes options for copying the music, videos, and other content from an original game disc or digital installation. +modcontent-header = + Game content may be extracted from the original game discs or an existing digital install. OpenRA can also download the base game files from an online mirror of the 2007 freeware release of C&C. +modcontent-package-basefiles = Base Game Files +modcontent-package-basemusic = Base Game Music +modcontent-package-gdi = GDI Campaign Briefings +modcontent-package-nod = Nod Campaign Briefings +modcontent-package-covertopsmusic = Covert Operations Music diff --git a/mods/cnc/mod.yaml b/mods/cnc/mod.yaml index 1f641665f2..a86ed70651 100644 --- a/mods/cnc/mod.yaml +++ b/mods/cnc/mod.yaml @@ -1,9 +1,9 @@ Metadata: - Title: Tiberian Dawn + Title: mod-title Version: {DEV_VERSION} Website: https://www.openra.net WebIcon32: https://www.openra.net/images/icons/cnc_32x32.png - WindowTitle: OpenRA - Tiberian Dawn + WindowTitle: mod-windowtitle PackageFormats: Mix @@ -146,6 +146,7 @@ ChromeLayout: Translations: common|languages/en.ftl common|languages/rules/en.ftl + cnc|languages/en.ftl cnc|languages/chrome/en.ftl cnc|languages/rules/en.ftl @@ -176,7 +177,6 @@ LoadScreen: CncLoadScreen Image: cnc|uibits/chrome.png Image2x: cnc|uibits/chrome-2x.png Image3x: cnc|uibits/chrome-3x.png - Text: Loading ServerTraits: LobbyCommands @@ -278,34 +278,34 @@ GameSpeeds: OrderLatency: 6 ModContent: - InstallPromptMessage: Tiberian Dawn requires artwork and audio from the original game.\n\nQuick Install will automatically download this content (without music\nor videos) from a mirror of the 2007 C&C Gold freeware release.\n\nAdvanced Install includes options for copying the music, videos, and\nother content from an original game disc or digital installation. + InstallPromptMessage: modcontent-installprompt QuickDownload: basefiles - HeaderMessage: Game content may be extracted from the original game discs or an\nexisting digital install. OpenRA can also download the base game\nfiles from an online mirror of the 2007 freeware release of C&C. + HeaderMessage: modcontent-header Packages: ContentPackage@base: - Title: Base Game Files + Title: modcontent-package-basefiles Identifier: base TestFiles: ^SupportDir|Content/cnc/conquer.mix, ^SupportDir|Content/cnc/desert.mix, ^SupportDir|Content/cnc/sounds.mix, ^SupportDir|Content/cnc/speech.mix, ^SupportDir|Content/cnc/temperat.mix, ^SupportDir|Content/cnc/tempicnh.mix, ^SupportDir|Content/cnc/winter.mix Sources: gdi95, nod95, tfd, tuc-steam, tuc-origin, cncr-steam, cncr-origin Required: true Download: basefiles ContentPackage@music: - Title: Base Game Music + Title: modcontent-package-basemusic Identifier: music TestFiles: ^SupportDir|Content/cnc/scores.mix Sources: gdi95, nod95, tfd, tuc-steam, tuc-origin, cncr-steam, cncr-origin ContentPackage@movies-gdi: - Title: GDI Campaign Briefings + Title: modcontent-package-gdi Identifier: movies-gdi TestFiles: ^SupportDir|Content/cnc/movies/visor.vqa, ^SupportDir|Content/cnc/movies/turtkill.vqa, ^SupportDir|Content/cnc/movies/tbrinfo3.vqa, ^SupportDir|Content/cnc/movies/tbrinfo2.vqa, ^SupportDir|Content/cnc/movies/tbrinfo1.vqa, ^SupportDir|Content/cnc/movies/seige.vqa, ^SupportDir|Content/cnc/movies/samsite.vqa, ^SupportDir|Content/cnc/movies/samdie.vqa, ^SupportDir|Content/cnc/movies/sabotage.vqa, ^SupportDir|Content/cnc/movies/retro.vqa, ^SupportDir|Content/cnc/movies/podium.vqa, ^SupportDir|Content/cnc/movies/planecra.vqa, ^SupportDir|Content/cnc/movies/pintle.vqa, ^SupportDir|Content/cnc/movies/paratrop.vqa, ^SupportDir|Content/cnc/movies/nodsweep.vqa, ^SupportDir|Content/cnc/movies/nodlose.vqa, ^SupportDir|Content/cnc/movies/nodflees.vqa, ^SupportDir|Content/cnc/movies/nod1.vqa, ^SupportDir|Content/cnc/movies/nitejump.vqa, ^SupportDir|Content/cnc/movies/napalm.vqa, ^SupportDir|Content/cnc/movies/logo.vqa, ^SupportDir|Content/cnc/movies/landing.vqa, ^SupportDir|Content/cnc/movies/intro2.vqa, ^SupportDir|Content/cnc/movies/hellvaly.vqa, ^SupportDir|Content/cnc/movies/gunboat.vqa, ^SupportDir|Content/cnc/movies/generic.vqa, ^SupportDir|Content/cnc/movies/gdilose.vqa, ^SupportDir|Content/cnc/movies/gdifinb.vqa, ^SupportDir|Content/cnc/movies/gdifina.vqa, ^SupportDir|Content/cnc/movies/gdiend2.vqa, ^SupportDir|Content/cnc/movies/gdiend1.vqa, ^SupportDir|Content/cnc/movies/gdi9.vqa, ^SupportDir|Content/cnc/movies/gdi8b.vqa, ^SupportDir|Content/cnc/movies/gdi8a.vqa, ^SupportDir|Content/cnc/movies/gdi7.vqa, ^SupportDir|Content/cnc/movies/gdi6.vqa, ^SupportDir|Content/cnc/movies/gdi5.vqa, ^SupportDir|Content/cnc/movies/gdi4b.vqa, ^SupportDir|Content/cnc/movies/gdi4a.vqa, ^SupportDir|Content/cnc/movies/gdi3lose.vqa, ^SupportDir|Content/cnc/movies/gdi3.vqa, ^SupportDir|Content/cnc/movies/gdi2.vqa, ^SupportDir|Content/cnc/movies/gdi15.vqa, ^SupportDir|Content/cnc/movies/gdi14.vqa, ^SupportDir|Content/cnc/movies/gdi13.vqa, ^SupportDir|Content/cnc/movies/gdi12.vqa, ^SupportDir|Content/cnc/movies/gdi11.vqa, ^SupportDir|Content/cnc/movies/gdi10.vqa, ^SupportDir|Content/cnc/movies/gdi1.vqa, ^SupportDir|Content/cnc/movies/gameover.vqa, ^SupportDir|Content/cnc/movies/forestkl.vqa, ^SupportDir|Content/cnc/movies/flyy.vqa, ^SupportDir|Content/cnc/movies/flag.vqa, ^SupportDir|Content/cnc/movies/dino.vqa, ^SupportDir|Content/cnc/movies/desolat.vqa, ^SupportDir|Content/cnc/movies/consyard.vqa, ^SupportDir|Content/cnc/movies/cc2tease.vqa, ^SupportDir|Content/cnc/movies/burdet2.vqa, ^SupportDir|Content/cnc/movies/burdet1.vqa, ^SupportDir|Content/cnc/movies/bombflee.vqa, ^SupportDir|Content/cnc/movies/bombaway.vqa, ^SupportDir|Content/cnc/movies/bkground.vqa, ^SupportDir|Content/cnc/movies/bcanyon.vqa, ^SupportDir|Content/cnc/movies/banner.vqa Sources: gdi95, tfd, tuc-steam, tuc-origin, cncr-steam, cncr-origin ContentPackage@movies-nod: - Title: Nod Campaign Briefings + Title: modcontent-package-nod Identifier: movies-nod TestFiles: ^SupportDir|Content/cnc/movies/visor.vqa, ^SupportDir|Content/cnc/movies/trtkil_d.vqa, ^SupportDir|Content/cnc/movies/tiberfx.vqa, ^SupportDir|Content/cnc/movies/tankkill.vqa, ^SupportDir|Content/cnc/movies/tankgo.vqa, ^SupportDir|Content/cnc/movies/sundial.vqa, ^SupportDir|Content/cnc/movies/stealth.vqa, ^SupportDir|Content/cnc/movies/spycrash.vqa, ^SupportDir|Content/cnc/movies/sethpre.vqa, ^SupportDir|Content/cnc/movies/seige.vqa, ^SupportDir|Content/cnc/movies/samsite.vqa, ^SupportDir|Content/cnc/movies/retro.vqa, ^SupportDir|Content/cnc/movies/refint.vqa, ^SupportDir|Content/cnc/movies/obel.vqa, ^SupportDir|Content/cnc/movies/nuke.vqa, ^SupportDir|Content/cnc/movies/nodlose.vqa, ^SupportDir|Content/cnc/movies/nodfinal.vqa, ^SupportDir|Content/cnc/movies/nodend4.vqa, ^SupportDir|Content/cnc/movies/nodend3.vqa, ^SupportDir|Content/cnc/movies/nodend2.vqa, ^SupportDir|Content/cnc/movies/nodend1.vqa, ^SupportDir|Content/cnc/movies/nod9.vqa, ^SupportDir|Content/cnc/movies/nod8.vqa, ^SupportDir|Content/cnc/movies/nod7b.vqa, ^SupportDir|Content/cnc/movies/nod7a.vqa, ^SupportDir|Content/cnc/movies/nod6.vqa, ^SupportDir|Content/cnc/movies/nod5.vqa, ^SupportDir|Content/cnc/movies/nod4b.vqa, ^SupportDir|Content/cnc/movies/nod4a.vqa, ^SupportDir|Content/cnc/movies/nod3.vqa, ^SupportDir|Content/cnc/movies/nod2.vqa, ^SupportDir|Content/cnc/movies/nod1pre.vqa, ^SupportDir|Content/cnc/movies/nod13.vqa, ^SupportDir|Content/cnc/movies/nod12.vqa, ^SupportDir|Content/cnc/movies/nod11.vqa, ^SupportDir|Content/cnc/movies/nod10b.vqa, ^SupportDir|Content/cnc/movies/nod10a.vqa, ^SupportDir|Content/cnc/movies/nod1.vqa, ^SupportDir|Content/cnc/movies/logo.vqa, ^SupportDir|Content/cnc/movies/landing.vqa, ^SupportDir|Content/cnc/movies/kanepre.vqa, ^SupportDir|Content/cnc/movies/intro2.vqa, ^SupportDir|Content/cnc/movies/insites.vqa, ^SupportDir|Content/cnc/movies/generic.vqa, ^SupportDir|Content/cnc/movies/gdi1.vqa, ^SupportDir|Content/cnc/movies/gameover.vqa, ^SupportDir|Content/cnc/movies/forestkl.vqa, ^SupportDir|Content/cnc/movies/flag.vqa, ^SupportDir|Content/cnc/movies/dino.vqa, ^SupportDir|Content/cnc/movies/dessweep.vqa, ^SupportDir|Content/cnc/movies/deskill.vqa, ^SupportDir|Content/cnc/movies/desflees.vqa, ^SupportDir|Content/cnc/movies/consyard.vqa, ^SupportDir|Content/cnc/movies/cc2tease.vqa, ^SupportDir|Content/cnc/movies/bombflee.vqa, ^SupportDir|Content/cnc/movies/bombaway.vqa, ^SupportDir|Content/cnc/movies/bcanyon.vqa, ^SupportDir|Content/cnc/movies/banner.vqa, ^SupportDir|Content/cnc/movies/akira.vqa, ^SupportDir|Content/cnc/movies/airstrk.vqa Sources: nod95, tfd, tuc-steam, tuc-origin, cncr-steam, cncr-origin ContentPackage@music-covertops: - Title: Covert Operations Music + Title: modcontent-package-covertopsmusic Identifier: music-covertops TestFiles: ^SupportDir|Content/cnc/scores-covertops.mix Sources: covertops, tfd, tuc-steam, tuc-origin, cncr-steam, cncr-origin @@ -318,6 +318,7 @@ ModContent: cnc|installer/nod95.yaml cnc|installer/origin.yaml cnc|installer/steam.yaml + Translation: cnc|languages/modcontent/en.ftl DiscordService: ApplicationId: 699223250181292033 diff --git a/mods/d2k/languages/en.ftl b/mods/d2k/languages/en.ftl new file mode 100644 index 0000000000..a57fcae31a --- /dev/null +++ b/mods/d2k/languages/en.ftl @@ -0,0 +1,6 @@ +## Metadata +mod-title = Dune 2000 +mod-windowtitle = OpenRA - Dune 2000 + +## LogoStripLoadScreen +loadscreen-loading = Filling Crates..., Breeding Sandworms..., Fuelling carryalls..., Deploying harvesters..., Preparing thopters..., Summoning mentats... diff --git a/mods/d2k/languages/modcontent/en.ftl b/mods/d2k/languages/modcontent/en.ftl new file mode 100644 index 0000000000..a53c038d40 --- /dev/null +++ b/mods/d2k/languages/modcontent/en.ftl @@ -0,0 +1,12 @@ +modcontent-installprompt = + Dune 2000 requires artwork and audio from the original game. + + Quick Install will automatically download this content (without music or videos) from an online mirror of the game files. + + Advanced Install includes options for copying the music, videos, and other content from an original game disc. +modcontent-header = + The original game content may be copied from an original game disc, or downloaded from an online mirror of the game files. +modcontent-package-basefiles = Base Game Files +modcontent-package-106patch = 1.06 Patch Content +modcontent-package-music = Game Music +modcontent-package-briefings = Campaign Briefings diff --git a/mods/d2k/mod.yaml b/mods/d2k/mod.yaml index 74c8c2cc67..8c3a751f86 100644 --- a/mods/d2k/mod.yaml +++ b/mods/d2k/mod.yaml @@ -1,9 +1,9 @@ Metadata: - Title: Dune 2000 + Title: mod-title Version: {DEV_VERSION} Website: https://www.openra.net WebIcon32: https://www.openra.net/images/icons/d2k_32x32.png - WindowTitle: OpenRA - Dune 2000 + WindowTitle: mod-windowtitle PackageFormats: D2kSoundResources @@ -124,6 +124,7 @@ Translations: common|languages/en.ftl common|languages/chrome/en.ftl common|languages/rules/en.ftl + d2k|languages/en.ftl d2k|languages/chrome/en.ftl d2k|languages/rules/en.ftl @@ -160,7 +161,6 @@ LoadScreen: LogoStripeLoadScreen Image: d2k|uibits/loadscreen.png Image2x: d2k|uibits/loadscreen-2x.png Image3x: d2k|uibits/loadscreen-3x.png - Text: Filling Crates..., Breeding Sandworms..., Fuelling carryalls..., Deploying harvesters..., Preparing thopters..., Summoning mentats... ServerTraits: LobbyCommands @@ -259,31 +259,31 @@ GameSpeeds: OrderLatency: 6 ModContent: - InstallPromptMessage: Dune 2000 requires artwork and audio from the original game.\n\nQuick Install will automatically download this content (without\nmusic or videos) from an online mirror of the game files.\n\nAdvanced Install includes options for copying the music, videos,\nand other content from an original game disc. - HeaderMessage: The original game content may be copied from an original game disc,\nor downloaded from an online mirror of the game files. + InstallPromptMessage: modcontent-installprompt + HeaderMessage: modcontent-header QuickDownload: quickinstall Packages: ContentPackage@base: - Title: Base Game Files + Title: modcontent-package-basefiles Identifier: base TestFiles: ^SupportDir|Content/d2k/v3/BLOXBASE.R16, ^SupportDir|Content/d2k/v3/BLOXBAT.R16, ^SupportDir|Content/d2k/v3/BLOXBGBS.R16, ^SupportDir|Content/d2k/v3/BLOXICE.R16, ^SupportDir|Content/d2k/v3/BLOXTREE.R16, ^SupportDir|Content/d2k/v3/BLOXWAST.R16, ^SupportDir|Content/d2k/v3/SOUND.RS, ^SupportDir|Content/d2k/v3/PALETTE.BIN Sources: d2k, gruntmods Required: true Download: basefiles ContentPackage@patch: - Title: 1.06 Patch Content + Title: modcontent-package-106patch Identifier: patch TestFiles: ^SupportDir|Content/d2k/v3/DATA.R16 Sources: gruntmods Required: true Download: patch106 ContentPackage@music: - Title: Game Music + Title: modcontent-package-music Identifier: music TestFiles: ^SupportDir|Content/d2k/v3/Music/AMBUSH.AUD, ^SupportDir|Content/d2k/v3/Music/WAITGAME.AUD Sources: d2k, gruntmods ContentPackage@movies: - Title: Campaign Briefings + Title: modcontent-package-briefings Identifier: movies TestFiles: ^SupportDir|Content/d2k/v3/Movies/A_BR01_E.VQA Sources: d2k @@ -292,6 +292,7 @@ ModContent: Sources: d2k|installer/d2k.yaml d2k|installer/gruntmods.yaml + Translation: d2k|languages/modcontent/en.ftl DiscordService: ApplicationId: 712711732770111550 diff --git a/mods/modcontent/content.yaml b/mods/modcontent/content.yaml index 096bd6389a..41467a4d5b 100644 --- a/mods/modcontent/content.yaml +++ b/mods/modcontent/content.yaml @@ -21,10 +21,12 @@ Background@CONTENT_PANEL: Align: Center Font: MediumBold Label@HEADER_TEMPLATE: + X: 30 Y: 66 - Width: PARENT_RIGHT + Width: PARENT_RIGHT - 60 Height: 16 Align: Center + WordWrap: true ScrollPanel@PACKAGES: X: 30 Y: 84 @@ -342,10 +344,12 @@ Background@CONTENT_PROMPT_PANEL: Align: Center Font: MediumBold Label@HEADER_TEMPLATE: + X: 30 Y: 65 - Width: PARENT_RIGHT + Width: PARENT_RIGHT - 60 Height: 16 Align: Center + WordWrap: true Button@ADVANCED_BUTTON: X: 30 Y: PARENT_BOTTOM - 52 diff --git a/mods/modcontent/languages/en.ftl b/mods/modcontent/languages/en.ftl new file mode 100644 index 0000000000..95fb3dc7c5 --- /dev/null +++ b/mods/modcontent/languages/en.ftl @@ -0,0 +1,2 @@ +## Metadata +mod-title = Mod Content Manager diff --git a/mods/modcontent/mod.yaml b/mods/modcontent/mod.yaml index 59a9740da4..44124e26f4 100644 --- a/mods/modcontent/mod.yaml +++ b/mods/modcontent/mod.yaml @@ -1,5 +1,5 @@ Metadata: - Title: Mod Content Manager + Title: mod-title Version: {DEV_VERSION} Hidden: true @@ -37,6 +37,7 @@ ChromeMetrics: Translations: common|languages/en.ftl + modcontent|languages/en.ftl Fonts: Tiny: diff --git a/mods/ra/languages/en.ftl b/mods/ra/languages/en.ftl new file mode 100644 index 0000000000..717ee88d1f --- /dev/null +++ b/mods/ra/languages/en.ftl @@ -0,0 +1,6 @@ +## Metadata +mod-title = Red Alert +mod-windowtitle = OpenRA - Red Alert + +## LogoStripeLoadScreen +loadscreen-loading = Filling Crates..., Charging Capacitors..., Reticulating Splines..., Planting Trees..., Building Bridges..., Aging Empires..., Compiling EVA..., Constructing Pylons..., Activating Skynet..., Splitting Atoms... diff --git a/mods/ra/languages/modcontent/en.ftl b/mods/ra/languages/modcontent/en.ftl new file mode 100644 index 0000000000..6e029316c2 --- /dev/null +++ b/mods/ra/languages/modcontent/en.ftl @@ -0,0 +1,16 @@ +modcontent-installprompt = + Red Alert requires artwork and audio from the original game. + + Quick Install will automatically download this content (without music or videos) from a mirror of the 2008 Red Alert freeware release. + + Advanced Install includes options for copying the music, videos, and other content from an original game disc or digital installation. +modcontent-header = + Game content may be extracted from the original game discs or an existing digital install. OpenRA can also download the base game files from an online mirror of the 2008 freeware release of RA. +modcontent-package-basefiles = Base Game Files +modcontent-package-aftermathfiles = Aftermath Expansion Files +modcontent-package-deserttileset = C&C Desert Tileset +modcontent-package-basemusic = Base Game Music +modcontent-package-allied = Allied Campaign Briefings +modcontent-package-soviet = Soviet Campaign Briefings +modcontent-package-counterstrikemusic = Counterstrike Music +modcontent-package-aftermathmusic = Aftermath Music diff --git a/mods/ra/mod.yaml b/mods/ra/mod.yaml index b8dd654b22..439edf9df0 100644 --- a/mods/ra/mod.yaml +++ b/mods/ra/mod.yaml @@ -1,9 +1,9 @@ Metadata: - Title: Red Alert + Title: mod-title Version: {DEV_VERSION} Website: https://www.openra.net WebIcon32: https://www.openra.net/images/icons/ra_32x32.png - WindowTitle: OpenRA - Red Alert + WindowTitle: mod-windowtitle PackageFormats: Mix @@ -142,6 +142,7 @@ Translations: common|languages/en.ftl common|languages/chrome/en.ftl common|languages/rules/en.ftl + ra|languages/en.ftl ra|languages/chrome/en.ftl ra|languages/rules/en.ftl @@ -179,7 +180,6 @@ LoadScreen: LogoStripeLoadScreen Image: ra|uibits/loadscreen.png Image2x: ra|uibits/loadscreen-2x.png Image3x: ra|uibits/loadscreen-3x.png - Text: Filling Crates..., Charging Capacitors..., Reticulating Splines..., Planting Trees..., Building Bridges..., Aging Empires..., Compiling EVA..., Constructing Pylons..., Activating Skynet..., Splitting Atoms... ServerTraits: LobbyCommands @@ -281,53 +281,53 @@ GameSpeeds: OrderLatency: 6 ModContent: - InstallPromptMessage: Red Alert requires artwork and audio from the original game.\n\nQuick Install will automatically download this content (without music\nor videos) from a mirror of the 2008 Red Alert freeware release.\n\nAdvanced Install includes options for copying the music, videos, and\nother content from an original game disc or digital installation. + InstallPromptMessage: modcontent-installprompt QuickDownload: quickinstall - HeaderMessage: Game content may be extracted from the original game discs or an\nexisting digital install. OpenRA can also download the base game\nfiles from an online mirror of the 2008 freeware release of RA. + HeaderMessage: modcontent-header Packages: ContentPackage@base: - Title: Base Game Files + Title: modcontent-package-basefiles Identifier: base TestFiles: ^SupportDir|Content/ra/v2/allies.mix, ^SupportDir|Content/ra/v2/conquer.mix, ^SupportDir|Content/ra/v2/interior.mix, ^SupportDir|Content/ra/v2/hires.mix, ^SupportDir|Content/ra/v2/lores.mix, ^SupportDir|Content/ra/v2/local.mix, ^SupportDir|Content/ra/v2/speech.mix, ^SupportDir|Content/ra/v2/russian.mix, ^SupportDir|Content/ra/v2/snow.mix, ^SupportDir|Content/ra/v2/sounds.mix, ^SupportDir|Content/ra/v2/temperat.mix Sources: allied, soviet, tfd, ra-steam, ra-origin, cncr-steam, cncr-origin Required: true Download: basefiles ContentPackage@aftermathbase: - Title: Aftermath Expansion Files + Title: modcontent-package-aftermathfiles Identifier: aftermathbase TestFiles: ^SupportDir|Content/ra/v2/expand/expand2.mix, ^SupportDir|Content/ra/v2/expand/hires1.mix, ^SupportDir|Content/ra/v2/expand/lores1.mix, ^SupportDir|Content/ra/v2/expand/chrotnk1.aud, ^SupportDir|Content/ra/v2/expand/fixit1.aud, ^SupportDir|Content/ra/v2/expand/jburn1.aud, ^SupportDir|Content/ra/v2/expand/jchrge1.aud, ^SupportDir|Content/ra/v2/expand/jcrisp1.aud, ^SupportDir|Content/ra/v2/expand/jdance1.aud, ^SupportDir|Content/ra/v2/expand/jjuice1.aud, ^SupportDir|Content/ra/v2/expand/jjump1.aud, ^SupportDir|Content/ra/v2/expand/jlight1.aud, ^SupportDir|Content/ra/v2/expand/jpower1.aud, ^SupportDir|Content/ra/v2/expand/jshock1.aud, ^SupportDir|Content/ra/v2/expand/jyes1.aud, ^SupportDir|Content/ra/v2/expand/madchrg2.aud, ^SupportDir|Content/ra/v2/expand/madexplo.aud, ^SupportDir|Content/ra/v2/expand/mboss1.aud, ^SupportDir|Content/ra/v2/expand/mhear1.aud, ^SupportDir|Content/ra/v2/expand/mhotdig1.aud, ^SupportDir|Content/ra/v2/expand/mhowdy1.aud, ^SupportDir|Content/ra/v2/expand/mhuh1.aud, ^SupportDir|Content/ra/v2/expand/mlaff1.aud, ^SupportDir|Content/ra/v2/expand/mrise1.aud, ^SupportDir|Content/ra/v2/expand/mwrench1.aud, ^SupportDir|Content/ra/v2/expand/myeehaw1.aud, ^SupportDir|Content/ra/v2/expand/myes1.aud Sources: aftermath, tfd, ra-steam, ra-origin, cncr-steam, cncr-origin Required: true Download: aftermath ContentPackage@cncdesert: - Title: C&C Desert Tileset + Title: modcontent-package-deserttileset Identifier: cncdesert TestFiles: ^SupportDir|Content/ra/v2/cnc/desert.mix Sources: tfd, cnc-steam, cnc-origin, cnc95, cncr-steam, cncr-origin Required: true Download: cncdesert ContentPackage@music: - Title: Base Game Music + Title: modcontent-package-basemusic Identifier: music TestFiles: ^SupportDir|Content/ra/v2/scores.mix Sources: allied, soviet, tfd, ra-steam, ra-origin, cncr-steam, cncr-origin ContentPackage@movies-allied: - Title: Allied Campaign Briefings + Title: modcontent-package-allied Identifier: movies-allied TestFiles: ^SupportDir|Content/ra/v2/movies/aagun.vqa, ^SupportDir|Content/ra/v2/movies/aftrmath.vqa, ^SupportDir|Content/ra/v2/movies/ally1.vqa, ^SupportDir|Content/ra/v2/movies/ally10.vqa, ^SupportDir|Content/ra/v2/movies/ally10b.vqa, ^SupportDir|Content/ra/v2/movies/ally11.vqa, ^SupportDir|Content/ra/v2/movies/ally12.vqa, ^SupportDir|Content/ra/v2/movies/ally14.vqa, ^SupportDir|Content/ra/v2/movies/ally2.vqa, ^SupportDir|Content/ra/v2/movies/ally4.vqa, ^SupportDir|Content/ra/v2/movies/ally5.vqa, ^SupportDir|Content/ra/v2/movies/ally6.vqa, ^SupportDir|Content/ra/v2/movies/ally8.vqa, ^SupportDir|Content/ra/v2/movies/ally9.vqa, ^SupportDir|Content/ra/v2/movies/allyend.vqa, ^SupportDir|Content/ra/v2/movies/allymorf.vqa, ^SupportDir|Content/ra/v2/movies/apcescpe.vqa, ^SupportDir|Content/ra/v2/movies/assess.vqa, ^SupportDir|Content/ra/v2/movies/battle.vqa, ^SupportDir|Content/ra/v2/movies/binoc.vqa, ^SupportDir|Content/ra/v2/movies/bmap.vqa, ^SupportDir|Content/ra/v2/movies/brdgtilt.vqa, ^SupportDir|Content/ra/v2/movies/crontest.vqa, ^SupportDir|Content/ra/v2/movies/cronfail.vqa, ^SupportDir|Content/ra/v2/movies/destroyr.vqa, ^SupportDir|Content/ra/v2/movies/dud.vqa, ^SupportDir|Content/ra/v2/movies/elevator.vqa, ^SupportDir|Content/ra/v2/movies/flare.vqa, ^SupportDir|Content/ra/v2/movies/frozen.vqa, ^SupportDir|Content/ra/v2/movies/grvestne.vqa, ^SupportDir|Content/ra/v2/movies/landing.vqa, ^SupportDir|Content/ra/v2/movies/masasslt.vqa, ^SupportDir|Content/ra/v2/movies/mcv.vqa, ^SupportDir|Content/ra/v2/movies/mcv_land.vqa, ^SupportDir|Content/ra/v2/movies/montpass.vqa, ^SupportDir|Content/ra/v2/movies/oildrum.vqa, ^SupportDir|Content/ra/v2/movies/overrun.vqa, ^SupportDir|Content/ra/v2/movies/prolog.vqa, ^SupportDir|Content/ra/v2/movies/redintro.vqa, ^SupportDir|Content/ra/v2/movies/shipsink.vqa, ^SupportDir|Content/ra/v2/movies/shorbom1.vqa, ^SupportDir|Content/ra/v2/movies/shorbom2.vqa, ^SupportDir|Content/ra/v2/movies/shorbomb.vqa, ^SupportDir|Content/ra/v2/movies/snowbomb.vqa, ^SupportDir|Content/ra/v2/movies/soviet1.vqa, ^SupportDir|Content/ra/v2/movies/sovtstar.vqa, ^SupportDir|Content/ra/v2/movies/spy.vqa, ^SupportDir|Content/ra/v2/movies/tanya1.vqa, ^SupportDir|Content/ra/v2/movies/tanya2.vqa, ^SupportDir|Content/ra/v2/movies/toofar.vqa, ^SupportDir|Content/ra/v2/movies/trinity.vqa Sources: allied, tfd, ra-steam, ra-origin, cncr-steam, cncr-origin ContentPackage@movies-soviet: - Title: Soviet Campaign Briefings + Title: modcontent-package-soviet Identifier: movies-soviet TestFiles: ^SupportDir|Content/ra/v2/movies/aagun.vqa, ^SupportDir|Content/ra/v2/movies/cronfail.vqa, ^SupportDir|Content/ra/v2/movies/airfield.vqa, ^SupportDir|Content/ra/v2/movies/ally1.vqa, ^SupportDir|Content/ra/v2/movies/allymorf.vqa, ^SupportDir|Content/ra/v2/movies/averted.vqa, ^SupportDir|Content/ra/v2/movies/beachead.vqa, ^SupportDir|Content/ra/v2/movies/bmap.vqa, ^SupportDir|Content/ra/v2/movies/bombrun.vqa, ^SupportDir|Content/ra/v2/movies/countdwn.vqa, ^SupportDir|Content/ra/v2/movies/double.vqa, ^SupportDir|Content/ra/v2/movies/dpthchrg.vqa, ^SupportDir|Content/ra/v2/movies/execute.vqa, ^SupportDir|Content/ra/v2/movies/flare.vqa, ^SupportDir|Content/ra/v2/movies/landing.vqa, ^SupportDir|Content/ra/v2/movies/mcvbrdge.vqa, ^SupportDir|Content/ra/v2/movies/mig.vqa, ^SupportDir|Content/ra/v2/movies/movingin.vqa, ^SupportDir|Content/ra/v2/movies/mtnkfact.vqa, ^SupportDir|Content/ra/v2/movies/nukestok.vqa, ^SupportDir|Content/ra/v2/movies/onthprwl.vqa, ^SupportDir|Content/ra/v2/movies/periscop.vqa, ^SupportDir|Content/ra/v2/movies/prolog.vqa, ^SupportDir|Content/ra/v2/movies/radrraid.vqa, ^SupportDir|Content/ra/v2/movies/redintro.vqa, ^SupportDir|Content/ra/v2/movies/search.vqa, ^SupportDir|Content/ra/v2/movies/sfrozen.vqa, ^SupportDir|Content/ra/v2/movies/sitduck.vqa, ^SupportDir|Content/ra/v2/movies/slntsrvc.vqa, ^SupportDir|Content/ra/v2/movies/snowbomb.vqa, ^SupportDir|Content/ra/v2/movies/snstrafe.vqa, ^SupportDir|Content/ra/v2/movies/sovbatl.vqa, ^SupportDir|Content/ra/v2/movies/sovcemet.vqa, ^SupportDir|Content/ra/v2/movies/sovfinal.vqa, ^SupportDir|Content/ra/v2/movies/soviet1.vqa, ^SupportDir|Content/ra/v2/movies/soviet10.vqa, ^SupportDir|Content/ra/v2/movies/soviet11.vqa, ^SupportDir|Content/ra/v2/movies/soviet12.vqa, ^SupportDir|Content/ra/v2/movies/soviet13.vqa, ^SupportDir|Content/ra/v2/movies/soviet14.vqa, ^SupportDir|Content/ra/v2/movies/soviet2.vqa, ^SupportDir|Content/ra/v2/movies/soviet3.vqa, ^SupportDir|Content/ra/v2/movies/soviet4.vqa, ^SupportDir|Content/ra/v2/movies/soviet5.vqa, ^SupportDir|Content/ra/v2/movies/soviet6.vqa, ^SupportDir|Content/ra/v2/movies/soviet7.vqa, ^SupportDir|Content/ra/v2/movies/soviet8.vqa, ^SupportDir|Content/ra/v2/movies/soviet9.vqa, ^SupportDir|Content/ra/v2/movies/sovmcv.vqa, ^SupportDir|Content/ra/v2/movies/sovtstar.vqa, ^SupportDir|Content/ra/v2/movies/spotter.vqa, ^SupportDir|Content/ra/v2/movies/strafe.vqa, ^SupportDir|Content/ra/v2/movies/take_off.vqa, ^SupportDir|Content/ra/v2/movies/tesla.vqa, ^SupportDir|Content/ra/v2/movies/v2rocket.vqa Sources: soviet, tfd, ra-steam, ra-origin ContentPackage@music-counterstrike: - Title: Counterstrike Music + Title: modcontent-package-counterstrikemusic Identifier: music-counterstrike TestFiles: ^SupportDir|Content/ra/v2/expand/araziod.aud, ^SupportDir|Content/ra/v2/expand/backstab.aud, ^SupportDir|Content/ra/v2/expand/chaos2.aud, ^SupportDir|Content/ra/v2/expand/shut_it.aud, ^SupportDir|Content/ra/v2/expand/2nd_hand.aud, ^SupportDir|Content/ra/v2/expand/twinmix1.aud, ^SupportDir|Content/ra/v2/expand/under3.aud, ^SupportDir|Content/ra/v2/expand/vr2.aud, Sources: counterstrike, ra-steam, ra-origin, cncr-steam, cncr-origin ContentPackage@music-aftermath: - Title: Aftermath Music + Title: modcontent-package-aftermathmusic Identifier: music-aftermath TestFiles: ^SupportDir|Content/ra/v2/expand/await.aud, ^SupportDir|Content/ra/v2/expand/bog.aud, ^SupportDir|Content/ra/v2/expand/float_v2.aud, ^SupportDir|Content/ra/v2/expand/gloom.aud, ^SupportDir|Content/ra/v2/expand/grndwire.aud, ^SupportDir|Content/ra/v2/expand/rpt.aud, ^SupportDir|Content/ra/v2/expand/search.aud, ^SupportDir|Content/ra/v2/expand/traction.aud, ^SupportDir|Content/ra/v2/expand/wastelnd.aud Sources: aftermath, ra-steam, ra-origin, cncr-steam, cncr-origin @@ -342,6 +342,7 @@ ModContent: ra|installer/origin.yaml ra|installer/soviet95.yaml ra|installer/steam.yaml + Translation: ra|languages/modcontent/en.ftl DiscordService: ApplicationId: 699222659766026240 diff --git a/mods/ts/languages/en.ftl b/mods/ts/languages/en.ftl new file mode 100644 index 0000000000..338437dd33 --- /dev/null +++ b/mods/ts/languages/en.ftl @@ -0,0 +1,6 @@ +## Metadata +mod-title = Tiberian Sun +mod-windowtitle = OpenRA - Tiberian Sun + +## LogoStripeLoadScreen +loadscreen-loading = Updating EVA installation..., Changing perspective... diff --git a/mods/ts/languages/modcontent/en.ftl b/mods/ts/languages/modcontent/en.ftl new file mode 100644 index 0000000000..e1e87e5001 --- /dev/null +++ b/mods/ts/languages/modcontent/en.ftl @@ -0,0 +1,12 @@ +modcontent-installprompt = + Tiberian Sun requires artwork and audio from the original game. + + Quick Install will automatically download this content (without music or videos) from a mirror of the 2012 Tiberian Sun freeware release. + + Advanced Install includes options for copying the music, videos, and other content from an original game disc or digital installation. +modcontent-header = + Game content may be extracted from the original game discs or an existing digital install. OpenRA can also download the base game files from an online mirror of the 2012 freeware release of TS. +modcontent-package-basefiles = Base Game Files +modcontent-package-basemusic = Base Game Music +modcontent-package-firestormfiles = Firestorm Expansion Files +modcontent-package-firestormmusic = Firestorm Expansion Music diff --git a/mods/ts/mod.yaml b/mods/ts/mod.yaml index 812097a827..abff1aa774 100644 --- a/mods/ts/mod.yaml +++ b/mods/ts/mod.yaml @@ -1,9 +1,9 @@ Metadata: - Title: Tiberian Sun + Title: mod-title Version: {DEV_VERSION} Website: https://www.openra.net WebIcon32: https://www.openra.net/images/icons/ts_32x32.png - WindowTitle: OpenRA - Tiberian Sun + WindowTitle: mod-windowtitle PackageFormats: Mix @@ -187,6 +187,7 @@ Translations: common|languages/en.ftl common|languages/chrome/en.ftl common|languages/rules/en.ftl + ts|languages/en.ftl ts|languages/chrome/en.ftl ts|languages/rules/en.ftl @@ -216,7 +217,6 @@ Hotkeys: LoadScreen: LogoStripeLoadScreen Image: ts|uibits/loadscreen.png - Text: Updating EVA installation..., Changing perspective... ServerTraits: LobbyCommands @@ -312,31 +312,31 @@ GameSpeeds: OrderLatency: 6 ModContent: - InstallPromptMessage: Tiberian Sun requires artwork and audio from the original game.\n\nQuick Install will automatically download this content (without music\nor videos) from a mirror of the 2012 Tiberian Sun freeware release.\n\nAdvanced Install includes options for copying the music, videos, and\nother content from an original game disc or digital installation. + InstallPromptMessage: modcontent-installprompt QuickDownload: quickinstall - HeaderMessage: Game content may be extracted from the original game discs or an\nexisting digital install. OpenRA can also download the base game\nfiles from an online mirror of the 2012 freeware release of TS. + HeaderMessage: modcontent-header Packages: ContentPackage@tibsun: - Title: Base Game Files + Title: modcontent-package-basefiles Identifier: tibsun TestFiles: ^SupportDir|Content/ts/cache.mix, ^SupportDir|Content/ts/conquer.mix, ^SupportDir|Content/ts/isosnow.mix, ^SupportDir|Content/ts/isotemp.mix, ^SupportDir|Content/ts/local.mix, ^SupportDir|Content/ts/sidec01.mix, ^SupportDir|Content/ts/sidec02.mix, ^SupportDir|Content/ts/sno.mix, ^SupportDir|Content/ts/snow.mix, ^SupportDir|Content/ts/sounds.mix, ^SupportDir|Content/ts/speech01.mix, ^SupportDir|Content/ts/tem.mix, ^SupportDir|Content/ts/temperat.mix Sources: tibsun, tfd, steam, origin Required: true Download: basefiles ContentPackage@tibsun-music: - Title: Base Game Music + Title: modcontent-package-basemusic Identifier: tibsun-music TestFiles: ^SupportDir|Content/ts/scores.mix Sources: tibsun, tfd, steam, origin ContentPackage@fstorm: - Title: Firestorm Expansion Files + Title: modcontent-package-firestormfiles Identifier: fstorm TestFiles: ^SupportDir|Content/ts/firestorm/e01sc01.mix, ^SupportDir|Content/ts/firestorm/e01sc02.mix, ^SupportDir|Content/ts/firestorm/e01vox01.mix, ^SupportDir|Content/ts/firestorm/e01vox02.mix Sources: tfd, steam, origin, fstorm Required: true Download: fstorm ContentPackage@fstorm-music: - Title: Firestorm Expansion Music + Title: modcontent-package-firestormmusic Identifier: fstorm-music Sources: tfd, steam, origin, fstorm TestFiles: ^SupportDir|Content/ts/firestorm/scores01.mix @@ -348,6 +348,7 @@ ModContent: ts|installer/steam.yaml ts|installer/origin.yaml ts|installer/tibsun.yaml + Translation: ts|languages/modcontent/en.ftl DiscordService: ApplicationId: 712713986558394399