mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
feat: discover forge translation mod sources
Agent-Logs-Url: https://github.com/MCCTeam/Minecraft-Console-Client/sessions/3b2dd335-d070-4067-b864-5584bb94571b Co-authored-by: milutinke <441903+milutinke@users.noreply.github.com>
This commit is contained in:
parent
67066a145d
commit
7ce4f82871
4 changed files with 295 additions and 28 deletions
|
|
@ -273,10 +273,19 @@ namespace MinecraftClient.Protocol.Message
|
||||||
public Dictionary<string, string> Translations { get; init; } = [];
|
public Dictionary<string, string> Translations { get; init; } = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private sealed class ForgeModTranslationCacheEntry
|
||||||
|
{
|
||||||
|
public string CacheVersion { get; init; } = string.Empty;
|
||||||
|
public string Language { get; init; } = string.Empty;
|
||||||
|
public string SourceHash { get; init; } = string.Empty;
|
||||||
|
public Dictionary<string, Dictionary<string, string>> TranslationsByModId { get; init; } = [];
|
||||||
|
}
|
||||||
|
|
||||||
private const long MaxResourcePackDownloadBytes = 256L * 1024 * 1024;
|
private const long MaxResourcePackDownloadBytes = 256L * 1024 * 1024;
|
||||||
private const int ResourcePackDownloadBufferSize = 81920;
|
private const int ResourcePackDownloadBufferSize = 81920;
|
||||||
private const string ResourcePackTranslationCacheVersion = "1";
|
private const string ResourcePackTranslationCacheVersion = "1";
|
||||||
private const string ForgeModTranslationDirectory = "mods";
|
private const string ForgeModTranslationCacheVersion = "1";
|
||||||
|
private const string LocalForgeModTranslationDirectory = "mods";
|
||||||
|
|
||||||
private static readonly List<TranslationLayer> ForgeModTranslationLayers = [];
|
private static readonly List<TranslationLayer> ForgeModTranslationLayers = [];
|
||||||
private static readonly List<TranslationLayer> ResourcePackTranslationLayers = [];
|
private static readonly List<TranslationLayer> ResourcePackTranslationLayers = [];
|
||||||
|
|
@ -485,7 +494,7 @@ namespace MinecraftClient.Protocol.Message
|
||||||
|
|
||||||
ForgeModTranslationLayers.Clear();
|
ForgeModTranslationLayers.Clear();
|
||||||
|
|
||||||
if (!Config.Main.Advanced.LoadForgeModTranslations || !Directory.Exists(ForgeModTranslationDirectory))
|
if (!Config.Main.Advanced.LoadForgeModTranslations)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
HashSet<string> requestedModIds = new(
|
HashSet<string> requestedModIds = new(
|
||||||
|
|
@ -500,22 +509,24 @@ namespace MinecraftClient.Protocol.Message
|
||||||
Dictionary<string, Dictionary<string, string>> translationsByModId =
|
Dictionary<string, Dictionary<string, string>> translationsByModId =
|
||||||
new(StringComparer.OrdinalIgnoreCase);
|
new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
foreach (string modJarPath in Directory.EnumerateFiles(ForgeModTranslationDirectory, "*.jar")
|
foreach (string modDirectory in GetForgeModTranslationDirectories())
|
||||||
.OrderBy(static path => path, StringComparer.OrdinalIgnoreCase))
|
|
||||||
{
|
{
|
||||||
try
|
foreach (string modJarPath in Directory.EnumerateFiles(modDirectory, "*.jar")
|
||||||
{
|
.OrderBy(static path => path, StringComparer.OrdinalIgnoreCase))
|
||||||
using FileStream modJarStream = File.OpenRead(modJarPath);
|
|
||||||
MergeForgeModTranslations(modJarStream, requestedModIds, translationsByModId);
|
|
||||||
}
|
|
||||||
catch (IOException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
catch (InvalidDataException)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
catch (JsonException)
|
|
||||||
{
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
MergeForgeModTranslations(modJarPath, requestedModIds, translationsByModId);
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (InvalidDataException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -712,23 +723,38 @@ namespace MinecraftClient.Protocol.Message
|
||||||
ResourcePackTranslationLayers.Add(new TranslationLayer(packIdentifier, translations));
|
ResourcePackTranslationLayers.Add(new TranslationLayer(packIdentifier, translations));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void MergeForgeModTranslations(Stream modJarStream, HashSet<string> requestedModIds,
|
private static void MergeForgeModTranslations(string modJarPath, HashSet<string> requestedModIds,
|
||||||
Dictionary<string, Dictionary<string, string>> translationsByModId)
|
Dictionary<string, Dictionary<string, string>> translationsByModId)
|
||||||
{
|
{
|
||||||
using ZipArchive archive = new(modJarStream, ZipArchiveMode.Read, leaveOpen: true);
|
string sourceHash = ComputeFileSha256(modJarPath);
|
||||||
|
string cacheFilePath = GetForgeModTranslationCacheFilePath(sourceHash);
|
||||||
|
if (!TryLoadCachedForgeModTranslations(cacheFilePath, sourceHash,
|
||||||
|
out Dictionary<string, Dictionary<string, string>>? cachedTranslations))
|
||||||
|
{
|
||||||
|
using FileStream modJarStream = File.OpenRead(modJarPath);
|
||||||
|
cachedTranslations = ExtractForgeModTranslations(modJarStream);
|
||||||
|
SaveCachedForgeModTranslations(cacheFilePath, sourceHash, cachedTranslations);
|
||||||
|
}
|
||||||
|
|
||||||
HashSet<string> archiveModIds = GetForgeModIds(archive)
|
Dictionary<string, Dictionary<string, string>> archiveTranslations = cachedTranslations
|
||||||
.Where(requestedModIds.Contains)
|
.Where(static entry => entry.Value.Count > 0)
|
||||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
.Where(entry => requestedModIds.Contains(entry.Key))
|
||||||
|
.ToDictionary(static entry => entry.Key, static entry => entry.Value, StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
if (archiveModIds.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Dictionary<string, Dictionary<string, string>> archiveTranslations = ExtractForgeModTranslations(archive, archiveModIds);
|
|
||||||
foreach (var (modId, translations) in archiveTranslations)
|
foreach (var (modId, translations) in archiveTranslations)
|
||||||
translationsByModId[modId] = translations;
|
translationsByModId[modId] = translations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, Dictionary<string, string>> ExtractForgeModTranslations(Stream modJarStream)
|
||||||
|
{
|
||||||
|
using ZipArchive archive = new(modJarStream, ZipArchiveMode.Read, leaveOpen: true);
|
||||||
|
HashSet<string> archiveModIds = GetForgeModIds(archive);
|
||||||
|
if (archiveModIds.Count == 0)
|
||||||
|
return new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
return ExtractForgeModTranslations(archive, archiveModIds);
|
||||||
|
}
|
||||||
|
|
||||||
private static HashSet<string> GetForgeModIds(ZipArchive archive)
|
private static HashSet<string> GetForgeModIds(ZipArchive archive)
|
||||||
{
|
{
|
||||||
ZipArchiveEntry? modsTomlEntry = archive.GetEntry("META-INF/mods.toml")
|
ZipArchiveEntry? modsTomlEntry = archive.GetEntry("META-INF/mods.toml")
|
||||||
|
|
@ -853,6 +879,211 @@ namespace MinecraftClient.Protocol.Message
|
||||||
return language.Trim().ToLowerInvariant().Replace('-', '_');
|
return language.Trim().ToLowerInvariant().Replace('-', '_');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> GetForgeModTranslationDirectories()
|
||||||
|
{
|
||||||
|
string? configuredPath = Config.Main.Advanced.ForgeModTranslationPath?.Trim();
|
||||||
|
if (!string.IsNullOrWhiteSpace(configuredPath))
|
||||||
|
{
|
||||||
|
string overridePath = Path.GetFullPath(configuredPath);
|
||||||
|
if (Directory.Exists(overridePath))
|
||||||
|
yield return overridePath;
|
||||||
|
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
HashSet<string> yieldedPaths = new(PathComparer);
|
||||||
|
|
||||||
|
foreach (string candidate in GetDefaultForgeModTranslationDirectories())
|
||||||
|
{
|
||||||
|
string fullPath = Path.GetFullPath(candidate);
|
||||||
|
if (Directory.Exists(fullPath) && yieldedPaths.Add(fullPath))
|
||||||
|
yield return fullPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Config.Main.Advanced.AutoDiscoverForgeModTranslationSources)
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
foreach (string candidate in DiscoverLauncherForgeModTranslationDirectories())
|
||||||
|
{
|
||||||
|
string fullPath = Path.GetFullPath(candidate);
|
||||||
|
if (Directory.Exists(fullPath) && yieldedPaths.Add(fullPath))
|
||||||
|
yield return fullPath;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> GetDefaultForgeModTranslationDirectories()
|
||||||
|
{
|
||||||
|
yield return LocalForgeModTranslationDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> DiscoverLauncherForgeModTranslationDirectories()
|
||||||
|
{
|
||||||
|
if (TryGetOfficialMinecraftModsDirectory(out string? officialModsDirectory))
|
||||||
|
yield return officialModsDirectory;
|
||||||
|
|
||||||
|
foreach (string prismModsDirectory in EnumerateInstanceModsDirectories(GetPrismLauncherInstancesDirectory()))
|
||||||
|
yield return prismModsDirectory;
|
||||||
|
|
||||||
|
foreach (string curseForgeModsDirectory in EnumerateInstanceModsDirectories(GetCurseForgeInstancesDirectory(), "mods"))
|
||||||
|
yield return curseForgeModsDirectory;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<string> EnumerateInstanceModsDirectories(string? instancesDirectory, params string[] relativeModsPaths)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(instancesDirectory) || !Directory.Exists(instancesDirectory))
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
string[] instanceDirectories;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
instanceDirectories = Directory.GetDirectories(instancesDirectory);
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (string instanceDirectory in instanceDirectories)
|
||||||
|
{
|
||||||
|
foreach (string relativeModsPath in relativeModsPaths.Length > 0
|
||||||
|
? relativeModsPaths
|
||||||
|
: [Path.Combine(".minecraft", "mods"), Path.Combine("minecraft", "mods"), "mods"])
|
||||||
|
{
|
||||||
|
string modsDirectory = Path.Combine(instanceDirectory, relativeModsPath);
|
||||||
|
if (Directory.Exists(modsDirectory))
|
||||||
|
yield return modsDirectory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetOfficialMinecraftModsDirectory([NotNullWhen(true)] out string? modsDirectory)
|
||||||
|
{
|
||||||
|
modsDirectory = null;
|
||||||
|
string? userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||||
|
if (string.IsNullOrWhiteSpace(userProfile))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
string baseMinecraftDirectory = OperatingSystem.IsWindows()
|
||||||
|
? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), ".minecraft")
|
||||||
|
: Path.Combine(userProfile, ".minecraft");
|
||||||
|
|
||||||
|
modsDirectory = Path.Combine(baseMinecraftDirectory, "mods");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? GetPrismLauncherInstancesDirectory()
|
||||||
|
{
|
||||||
|
string? userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||||
|
if (string.IsNullOrWhiteSpace(userProfile))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (OperatingSystem.IsWindows())
|
||||||
|
return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "PrismLauncher", "instances");
|
||||||
|
|
||||||
|
if (OperatingSystem.IsMacOS())
|
||||||
|
return Path.Combine(userProfile, "Library", "Application Support", "PrismLauncher", "instances");
|
||||||
|
|
||||||
|
return Path.Combine(userProfile, ".local", "share", "PrismLauncher", "instances");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? GetCurseForgeInstancesDirectory()
|
||||||
|
{
|
||||||
|
string? userProfile = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
|
||||||
|
if (string.IsNullOrWhiteSpace(userProfile))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return Path.Combine(userProfile, "curseforge", "minecraft", "Instances");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly StringComparer PathComparer =
|
||||||
|
OperatingSystem.IsWindows() ? StringComparer.OrdinalIgnoreCase : StringComparer.Ordinal;
|
||||||
|
|
||||||
|
private static string ComputeFileSha256(string filePath)
|
||||||
|
{
|
||||||
|
using FileStream stream = File.OpenRead(filePath);
|
||||||
|
return Convert.ToHexString(SHA256.HashData(stream)).ToLowerInvariant();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryLoadCachedForgeModTranslations(string cacheFilePath, string sourceHash,
|
||||||
|
[NotNullWhen(true)] out Dictionary<string, Dictionary<string, string>>? translationsByModId)
|
||||||
|
{
|
||||||
|
translationsByModId = null;
|
||||||
|
|
||||||
|
if (!File.Exists(cacheFilePath))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using FileStream cacheFile = File.OpenRead(cacheFilePath);
|
||||||
|
ForgeModTranslationCacheEntry? cacheEntry =
|
||||||
|
JsonSerializer.Deserialize<ForgeModTranslationCacheEntry>(cacheFile);
|
||||||
|
|
||||||
|
if (cacheEntry is not null
|
||||||
|
&& cacheEntry.CacheVersion == ForgeModTranslationCacheVersion
|
||||||
|
&& cacheEntry.Language.Equals(Config.Main.Advanced.Language, StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& cacheEntry.SourceHash.Equals(sourceHash, StringComparison.OrdinalIgnoreCase)
|
||||||
|
&& cacheEntry.TranslationsByModId.Count > 0)
|
||||||
|
{
|
||||||
|
translationsByModId = cacheEntry.TranslationsByModId.ToDictionary(
|
||||||
|
static entry => entry.Key,
|
||||||
|
static entry => new Dictionary<string, string>(entry.Value, StringComparer.Ordinal),
|
||||||
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
File.Delete(cacheFilePath);
|
||||||
|
}
|
||||||
|
catch (IOException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SaveCachedForgeModTranslations(string cacheFilePath, string sourceHash,
|
||||||
|
Dictionary<string, Dictionary<string, string>> translationsByModId)
|
||||||
|
{
|
||||||
|
if (translationsByModId.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string? cacheDirectory = Path.GetDirectoryName(cacheFilePath);
|
||||||
|
if (string.IsNullOrEmpty(cacheDirectory))
|
||||||
|
return;
|
||||||
|
|
||||||
|
Directory.CreateDirectory(cacheDirectory);
|
||||||
|
|
||||||
|
ForgeModTranslationCacheEntry cacheEntry = new()
|
||||||
|
{
|
||||||
|
CacheVersion = ForgeModTranslationCacheVersion,
|
||||||
|
Language = Config.Main.Advanced.Language,
|
||||||
|
SourceHash = sourceHash,
|
||||||
|
TranslationsByModId = translationsByModId.ToDictionary(
|
||||||
|
static entry => entry.Key,
|
||||||
|
static entry => new Dictionary<string, string>(entry.Value, StringComparer.Ordinal),
|
||||||
|
StringComparer.OrdinalIgnoreCase)
|
||||||
|
};
|
||||||
|
|
||||||
|
File.WriteAllText(cacheFilePath, JsonSerializer.Serialize(cacheEntry), Encoding.UTF8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetForgeModTranslationCacheFilePath(string sourceHash)
|
||||||
|
{
|
||||||
|
return Path.Combine("lang", "forgemods", $"{sourceHash}.{NormalizeLanguageCode(Config.Main.Advanced.Language)}.json");
|
||||||
|
}
|
||||||
|
|
||||||
private static bool TryLoadCachedResourcePackTranslations(string cacheFilePath, Uri resourcePackUri, string hash,
|
private static bool TryLoadCachedResourcePackTranslations(string cacheFilePath, Uri resourcePackUri, string hash,
|
||||||
[NotNullWhen(true)] out Dictionary<string, string>? translations)
|
[NotNullWhen(true)] out Dictionary<string, string>? translations)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -688,7 +688,13 @@ Usage examples: "/tell <mybot> reco Player2", "/connect <serverip> P
|
||||||
<value>Load translations from server resource packs and cache extracted language entries locally for faster reuse.</value>
|
<value>Load translations from server resource packs and cache extracted language entries locally for faster reuse.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Main.Advanced.load_forge_mod_translations" xml:space="preserve">
|
<data name="Main.Advanced.load_forge_mod_translations" xml:space="preserve">
|
||||||
<value>Load translations from local Forge mod jars in the mods folder when their mod IDs are announced by the server.</value>
|
<value>Load translations from local Forge mod jars when their mod IDs are announced by the server. Disabled by default.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Main.Advanced.auto_discover_forge_mod_translation_sources" xml:space="preserve">
|
||||||
|
<value>Also scan standard Minecraft, Prism Launcher, and CurseForge instance mod folders for translation jars when available.</value>
|
||||||
|
</data>
|
||||||
|
<data name="Main.Advanced.forge_mod_translation_path" xml:space="preserve">
|
||||||
|
<value>Optional path to a mods folder. When set, MCC loads Forge mod translations from this folder instead of automatic discovery.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="Main.Advanced.mc_forge" xml:space="preserve">
|
<data name="Main.Advanced.mc_forge" xml:space="preserve">
|
||||||
<value>Use "auto", "no" or "force". Force-enabling only works for MC 1.13+.</value>
|
<value>Use "auto", "no" or "force". Force-enabling only works for MC 1.13+.</value>
|
||||||
|
|
|
||||||
|
|
@ -680,6 +680,8 @@ namespace MinecraftClient
|
||||||
if (Advanced.TcpTimeout < 1)
|
if (Advanced.TcpTimeout < 1)
|
||||||
Advanced.TcpTimeout = 1;
|
Advanced.TcpTimeout = 1;
|
||||||
|
|
||||||
|
Advanced.ForgeModTranslationPath = Advanced.ForgeModTranslationPath.Trim();
|
||||||
|
|
||||||
if (Advanced.MovementSpeed < 1)
|
if (Advanced.MovementSpeed < 1)
|
||||||
Advanced.MovementSpeed = 1;
|
Advanced.MovementSpeed = 1;
|
||||||
|
|
||||||
|
|
@ -779,7 +781,13 @@ namespace MinecraftClient
|
||||||
public bool LoadResourcePackTranslations = true;
|
public bool LoadResourcePackTranslations = true;
|
||||||
|
|
||||||
[TomlInlineComment("$Main.Advanced.load_forge_mod_translations$")]
|
[TomlInlineComment("$Main.Advanced.load_forge_mod_translations$")]
|
||||||
public bool LoadForgeModTranslations = true;
|
public bool LoadForgeModTranslations = false;
|
||||||
|
|
||||||
|
[TomlInlineComment("$Main.Advanced.auto_discover_forge_mod_translation_sources$")]
|
||||||
|
public bool AutoDiscoverForgeModTranslationSources = true;
|
||||||
|
|
||||||
|
[TomlInlineComment("$Main.Advanced.forge_mod_translation_path$")]
|
||||||
|
public string ForgeModTranslationPath = "";
|
||||||
|
|
||||||
// [TomlInlineComment("$Main.Advanced.console_title$")]
|
// [TomlInlineComment("$Main.Advanced.console_title$")]
|
||||||
public string ConsoleTitle = "%username%@%serverip% - Minecraft Console Client";
|
public string ConsoleTitle = "%username%@%serverip% - Minecraft Console Client";
|
||||||
|
|
|
||||||
|
|
@ -283,12 +283,34 @@ Coordinate = { x = 145, y = 64, z = 2045 }
|
||||||
|
|
||||||
- **Description:**
|
- **Description:**
|
||||||
|
|
||||||
Set this to `false` to ignore translations from local Forge mod jars. When enabled, MCC looks in the local `mods` folder, reads matching `assets/<modId>/lang/<locale>.json` files from jars announced by the server, and falls back to `en_us` when the selected locale is missing.
|
Set this to `true` to load translations from local Forge mod jars for the mod IDs announced by the server. MCC first checks the folder from `ForgeModTranslationPath` when it is set. Otherwise it checks the local `mods` folder, and if `AutoDiscoverForgeModTranslationSources` is enabled it also scans standard launcher folders such as `.minecraft/mods`, Prism Launcher instances, and CurseForge instances. MCC falls back to `en_us` when the selected locale is missing, and caches parsed results by jar hash.
|
||||||
|
|
||||||
|
- **Type:** `boolean`
|
||||||
|
|
||||||
|
- **Default:** `false`
|
||||||
|
|
||||||
|
#### `AutoDiscoverForgeModTranslationSources`
|
||||||
|
|
||||||
|
- **Description:**
|
||||||
|
|
||||||
|
Set this to `false` to stop scanning launcher-managed mod folders outside the current working directory. This setting only matters when `LoadForgeModTranslations` is enabled and `ForgeModTranslationPath` is empty.
|
||||||
|
|
||||||
- **Type:** `boolean`
|
- **Type:** `boolean`
|
||||||
|
|
||||||
- **Default:** `true`
|
- **Default:** `true`
|
||||||
|
|
||||||
|
#### `ForgeModTranslationPath`
|
||||||
|
|
||||||
|
- **Description:**
|
||||||
|
|
||||||
|
Optional path to a mods folder. When this is set, MCC loads Forge mod translations from that folder instead of using automatic discovery.
|
||||||
|
|
||||||
|
If automatic discovery does not find the mod you need, copy that mod jar into the configured folder, or into the local `mods` folder next to MCC, and MCC will read translations from there without modifying the jar.
|
||||||
|
|
||||||
|
- **Type:** `string`
|
||||||
|
|
||||||
|
- **Default:** `""`
|
||||||
|
|
||||||
#### `ConsoleTitle`
|
#### `ConsoleTitle`
|
||||||
|
|
||||||
- **Description:**
|
- **Description:**
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue