mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-03-21 17:48:28 -05:00
Allow EntitySearchSetup to be translated (rearrange the initialization, no need to retain/defer). Rename Gen9a's gender label (previously blacklisted as an "auto-updating" label). Other gender/Stat labels currently blacklisted in DevUtil probably need to get refactored to be enums/etc, but I currently lack the time/patience to understand those editors well enough to properly support the refactoring needed. json exports: add newline at end to match the default editorconfig settings and general convention. we don't want textfiles loading in as a string[] with an empty string last entry.
331 lines
12 KiB
C#
331 lines
12 KiB
C#
#if DEBUG
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Text.Json;
|
|
using System.Windows.Forms;
|
|
using PKHeX.Core;
|
|
using PKHeX.WinForms.Controls;
|
|
|
|
namespace PKHeX.WinForms;
|
|
|
|
public static class DevUtil
|
|
{
|
|
public static void AddDeveloperControls(ToolStripDropDownItem t, List<IPlugin> plugins)
|
|
{
|
|
t.DropDownItems.Add(GetTranslationUpdater(Keys.D));
|
|
t.DropDownItems.Add(GetPogoPickleReload(Keys.P));
|
|
t.DropDownItems.Add(GetHexImporter(Keys.I));
|
|
t.DropDownItems.Add(GetPluginInfo(Keys.L, plugins));
|
|
}
|
|
|
|
private static string DefaultLanguage => Main.CurrentLanguage;
|
|
|
|
public static bool IsUpdatingTranslations { get; private set; }
|
|
|
|
/// <summary>
|
|
/// Call this to update all translatable resources (Program Messages, Legality Text, Program GUI)
|
|
/// </summary>
|
|
private static void UpdateAll()
|
|
{
|
|
if (DialogResult.Yes != WinFormsUtil.Prompt(MessageBoxButtons.YesNo, "Update translation files with current values?"))
|
|
return;
|
|
IsUpdatingTranslations = true;
|
|
DumpStringsLegality();
|
|
DumpStringsMessage();
|
|
UpdateTranslations();
|
|
IsUpdatingTranslations = false;
|
|
}
|
|
|
|
private static ToolStripMenuItem GetHexImporter(Keys key)
|
|
{
|
|
var ti = GetHiddenMenu(key);
|
|
ti.Click += (_, _) => OpenFileFromClipboardHex();
|
|
return ti;
|
|
}
|
|
|
|
private static ToolStripMenuItem GetTranslationUpdater(Keys key)
|
|
{
|
|
var ti = GetHiddenMenu(key);
|
|
ti.Click += (_, _) => UpdateAll();
|
|
return ti;
|
|
}
|
|
|
|
private static ToolStripMenuItem GetPogoPickleReload(Keys key)
|
|
{
|
|
var ti = GetHiddenMenu(key);
|
|
ti.Click += (_, _) => EncountersGO.Reload();
|
|
return ti;
|
|
}
|
|
|
|
private static ToolStripMenuItem GetPluginInfo(Keys key, List<IPlugin> plugins)
|
|
{
|
|
var ti = GetHiddenMenu(key);
|
|
ti.Click += (_, _) => DisplayPluginList(plugins);
|
|
return ti;
|
|
}
|
|
|
|
private static ToolStripMenuItem GetHiddenMenu(Keys key) => new()
|
|
{
|
|
ShortcutKeys = Keys.Control | Keys.Alt | key,
|
|
Visible = false,
|
|
};
|
|
|
|
private static void OpenFileFromClipboardHex()
|
|
{
|
|
var hex = Clipboard.GetText().Trim();
|
|
if (string.IsNullOrEmpty(hex))
|
|
{
|
|
WinFormsUtil.Alert("Clipboard is empty.");
|
|
return;
|
|
}
|
|
try
|
|
{
|
|
var data = Convert.FromHexString(hex.Replace(" ", ""));
|
|
Application.OpenForms.OfType<Main>().First().OpenFile(data, "", "");
|
|
}
|
|
catch (FormatException)
|
|
{
|
|
WinFormsUtil.Alert("Clipboard does not contain valid hex data.");
|
|
}
|
|
}
|
|
|
|
private static void DisplayPluginList(List<IPlugin> plugins)
|
|
{
|
|
var text = new StringBuilder();
|
|
|
|
text.AppendLine($"Loaded {plugins.Count} plugins:");
|
|
if (plugins.Count == 0)
|
|
{
|
|
text.AppendLine("None.");
|
|
WinFormsUtil.Alert(text.ToString());
|
|
return;
|
|
}
|
|
|
|
List<(IPlugin Plugin, string Group)> loaded = [];
|
|
foreach (var p in plugins)
|
|
{
|
|
var assembly = p.GetType().Assembly;
|
|
var fullName = assembly.FullName;
|
|
if (fullName != null)
|
|
{
|
|
var culture = fullName.IndexOf("Culture", StringComparison.Ordinal);
|
|
if (culture != -1)
|
|
fullName = fullName[..(culture - 2)];
|
|
if (fullName.EndsWith(".0"))
|
|
fullName = fullName[..^2];
|
|
}
|
|
loaded.Add(new(p, fullName ?? "Unknown"));
|
|
}
|
|
|
|
foreach (var group in loaded.GroupBy(z => z.Group).OrderBy(z => z.Key))
|
|
{
|
|
text.AppendLine(group.Key);
|
|
foreach (var p in group.OrderBy(z => z.Plugin.Name))
|
|
text.AppendLine($"- {p.Plugin.Name}");
|
|
}
|
|
|
|
WinFormsUtil.Alert(text.ToString());
|
|
}
|
|
|
|
private static void UpdateTranslations()
|
|
{
|
|
var assembly = System.Reflection.Assembly.GetExecutingAssembly();
|
|
var types = assembly.GetTypes();
|
|
|
|
// Trigger a translation then dump all.
|
|
foreach (var lang in GameLanguage.AllSupportedLanguages) // get all languages ready to go
|
|
_ = WinFormsTranslator.GetDictionary(lang);
|
|
WinFormsTranslator.SetUpdateMode();
|
|
WinFormsTranslator.LoadSettings<PKHeXSettings>(DefaultLanguage);
|
|
WinFormsTranslator.LoadEnums(EnumTypesToTranslate, DefaultLanguage);
|
|
WinFormsTranslator.LoadAllForms(types, LoadBanlist); // populate with every possible control
|
|
WinFormsTranslator.TranslateControls(GetExtraControls(), DefaultLanguage);
|
|
var dir = GetResourcePath("PKHeX.WinForms", "Resources", "text");
|
|
WinFormsTranslator.DumpAll(DefaultLanguage, Banlist, dir); // dump current to file
|
|
WinFormsTranslator.SetUpdateMode(false);
|
|
|
|
// Move translated files from the debug exe loc to their project location
|
|
var files = Directory.GetFiles(Application.StartupPath);
|
|
foreach (var f in files)
|
|
{
|
|
var fn = Path.GetFileName(f);
|
|
if (!fn.EndsWith(".txt"))
|
|
continue;
|
|
if (!fn.StartsWith("lang_"))
|
|
continue;
|
|
|
|
var loc = Path.Combine(dir, fn);
|
|
if (File.Exists(loc))
|
|
File.Delete(loc);
|
|
File.Move(f, loc, true);
|
|
}
|
|
|
|
Application.Exit();
|
|
}
|
|
|
|
/// <summary>
|
|
/// All enum types that should be translated in the WinForms GUI.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Each enum's defined values will be dumped and available for translation.
|
|
/// </remarks>
|
|
private static readonly Type[] EnumTypesToTranslate =
|
|
[
|
|
typeof(StatusCondition),
|
|
typeof(StatusType),
|
|
typeof(PokeSize),
|
|
typeof(PokeSizeDetailed),
|
|
|
|
typeof(PassPower5),
|
|
typeof(Funfest5Mission),
|
|
typeof(OPower6Index),
|
|
typeof(OPower6FieldType),
|
|
typeof(OPower6BattleType),
|
|
typeof(PlayerBattleStyle7),
|
|
typeof(PlayerSkinColor7),
|
|
typeof(Stamp7),
|
|
typeof(FestivalPlazaFacilityColor),
|
|
typeof(PlayerSkinColor8),
|
|
typeof(BattlePassType),
|
|
|
|
typeof(EventVarType),
|
|
typeof(NamedEventType),
|
|
typeof(StorageSlotType),
|
|
];
|
|
|
|
/// <summary>
|
|
/// Create fake controls that may not be currently present in the form, but are used for localization stubs.
|
|
/// </summary>
|
|
private static IEnumerable<Control> GetExtraControls()
|
|
{
|
|
yield return new Label { Name = $"{nameof(SAV_Misc3)}.L_CurrentSwapped" };
|
|
yield return new Label { Name = $"{nameof(SAV_Misc3)}.L_RecordSwapped" };
|
|
yield return new Label { Name = $"{nameof(SAV_Misc3)}.L_Championships" };
|
|
yield return new Label { Name = $"{nameof(SAV_Misc3)}.L_RecordCleared" };
|
|
yield return new Label { Name = $"{nameof(SAV_Misc3)}.L_CurrentStreak" };
|
|
yield return new Label { Name = $"{nameof(SAV_Misc3)}.L_RecordStreak" };
|
|
}
|
|
|
|
/// <summary>
|
|
/// Forms that should not be translated, or are dynamic and should not be included in the dump.
|
|
/// </summary>
|
|
private static readonly string[] LoadBanlist =
|
|
[
|
|
nameof(SplashScreen),
|
|
nameof(PokePreview),
|
|
];
|
|
|
|
/// <summary>
|
|
/// Controls that should not be translated, or are dynamic and should not be included in the dump.
|
|
/// </summary>
|
|
private static readonly string[] Banlist =
|
|
[
|
|
"Gender=", // editor gender labels
|
|
"BTN_Shinytize", // ☆
|
|
"Hidden_", // Hidden controls
|
|
"CAL_", // calendar controls now expose Text, don't care.
|
|
".Count", // enum count
|
|
$"{nameof(QR)}.L_", // Box/Slot/Count don't bother
|
|
$"{nameof(Main)}.L_SizeH", // height rating
|
|
$"{nameof(Main)}.L_SizeW", // weight rating
|
|
$"{nameof(Main)}.L_SizeS", // scale rating
|
|
$"{nameof(Main)}.L_Characteristic=", // Characteristic (dynamic)
|
|
$"{nameof(Main)}.L_Potential", // ★☆☆☆ IV judge evaluation
|
|
$"{nameof(SAV_HoneyTree)}.L_Tree0", // dynamic, don't bother
|
|
$"{nameof(SAV_Misc3)}.BTN_Symbol", // symbols should stay as their current character
|
|
$"{nameof(SAV_BlockDump8)}.L_BlockName", // Block name (dynamic)
|
|
$"{nameof(SAV_PokedexResearchEditorLA)}.L_", // Dynamic label
|
|
$"{nameof(SAV_OPower)}.L_", // Dynamic label
|
|
$"{nameof(SAV_Pokedex9a)}.CHK_SeenMega", // Dynamic text checkbox
|
|
$"{nameof(SAV_Misc3)}.L_Stat", // Dynamic labels
|
|
$"{nameof(SAV_Donut9a)}.L_Stat", // Dynamic labels
|
|
|
|
SlotList.DynamicLabelPrefix,
|
|
$"{nameof(StorageSlotType)}.{nameof(StorageSlotType.None)}",
|
|
$"{nameof(StorageSlotType)}.{nameof(StorageSlotType.Box)}",
|
|
$"{nameof(StorageSlotType)}.{nameof(StorageSlotType.Party)}",
|
|
$"{nameof(StorageSlotType)}.{nameof(StorageSlotType.FusedCalyrex)}",
|
|
$"{nameof(StorageSlotType)}.{nameof(StorageSlotType.FusedKyurem)}",
|
|
$"{nameof(StorageSlotType)}.{nameof(StorageSlotType.FusedNecrozmaS)}",
|
|
$"{nameof(StorageSlotType)}.{nameof(StorageSlotType.FusedNecrozmaM)}",
|
|
$"{nameof(StorageSlotType)}.{nameof(StorageSlotType.FusedCalyrex)}",
|
|
];
|
|
|
|
// paths should match the project structure, so that the files are in the correct place when the logic updates them.
|
|
private static void DumpStringsMessage() => DumpStrings(typeof(MessageStrings), false, "PKHeX.Core", "Resources", "text", "program");
|
|
private static void DumpStringsLegality()
|
|
{
|
|
ReadOnlySpan<string> rel = ["PKHeX.Core", "Resources", "localize"];
|
|
DumpJson(EncounterDisplayLocalization.Cache, rel);
|
|
DumpJson(MoveSourceLocalization.Cache, rel);
|
|
DumpJson(LegalityCheckLocalization.Cache, rel);
|
|
DumpJson(MoveSourceLocalization.Cache, rel);
|
|
}
|
|
|
|
private static void DumpJson<T>(LocalizationStorage<T> set, ReadOnlySpan<string> rel) where T : notnull
|
|
{
|
|
var dir = GetResourcePath([.. rel, set.Name]);
|
|
var all = set.GetAll();
|
|
foreach (var (lang, entries) in all)
|
|
{
|
|
var location = Path.Combine(dir, set.GetFileName(lang));
|
|
var json = JsonSerializer.Serialize(entries, set.Info);
|
|
File.WriteAllText(location, json + Environment.NewLine);
|
|
}
|
|
}
|
|
|
|
private static void DumpStrings(Type t, bool sorted, params ReadOnlySpan<string> rel)
|
|
{
|
|
var dir = GetResourcePath(rel);
|
|
DumpStrings(t, sorted, DefaultLanguage, dir);
|
|
foreach (var lang in GameLanguage.AllSupportedLanguages)
|
|
DumpStrings(t, sorted, lang, dir);
|
|
}
|
|
|
|
private static void DumpStrings(Type t, bool sorted, string lang, string dir)
|
|
{
|
|
LocalizationUtil.SetLocalization(t, lang);
|
|
var entries = LocalizationUtil.GetLocalization(t);
|
|
IEnumerable<string> export = entries.OrderBy(GetName); // sorted lines
|
|
|
|
if (!sorted)
|
|
export = entries;
|
|
|
|
var location = GetFileLocationInText(t.Name, dir, lang);
|
|
File.WriteAllLines(location, export);
|
|
LocalizationUtil.SetLocalization(t, DefaultLanguage);
|
|
|
|
static string GetName(string line)
|
|
{
|
|
var index = line.IndexOf('=');
|
|
if (index == -1)
|
|
return line;
|
|
return line[..index];
|
|
}
|
|
}
|
|
|
|
private static string GetFileLocationInText(string fileType, string dir, string lang)
|
|
{
|
|
var fn = $"{fileType}_{lang}.txt";
|
|
return Path.Combine(dir, fn);
|
|
}
|
|
|
|
private static string GetResourcePath(params ReadOnlySpan<string> subdir)
|
|
{
|
|
// Starting from the executable path, crawl upwards until we get to the repository/sln root
|
|
const string repo = "PKHeX";
|
|
var path = Application.StartupPath;
|
|
while (true)
|
|
{
|
|
var parent = Directory.GetParent(path) ?? throw new DirectoryNotFoundException(path);
|
|
path = parent.FullName;
|
|
if (path.EndsWith(repo))
|
|
return Path.Combine(path, Path.Combine(subdir));
|
|
}
|
|
}
|
|
}
|
|
#endif
|