2023-01-22 04:02:33 +00:00
|
|
|
using System;
|
2022-06-18 18:04:24 +00:00
|
|
|
using System.Collections.Generic;
|
2018-03-21 05:34:44 +00:00
|
|
|
using System.IO;
|
|
|
|
using System.Linq;
|
|
|
|
using System.Windows.Forms;
|
|
|
|
|
|
|
|
using PKHeX.Core;
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
namespace PKHeX.WinForms;
|
2018-03-21 05:34:44 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
public static class WinFormsTranslator
|
|
|
|
{
|
2023-12-04 04:13:20 +00:00
|
|
|
private static readonly Dictionary<string, TranslationContext> Context = [];
|
2022-06-18 18:04:24 +00:00
|
|
|
internal static void TranslateInterface(this Control form, string lang) => TranslateForm(form, GetContext(lang));
|
2018-07-27 02:34:27 +00:00
|
|
|
|
2024-03-04 05:13:16 +00:00
|
|
|
private static string GetTranslationFileNameInternal(ReadOnlySpan<char> lang) => $"lang_{lang}";
|
|
|
|
private static string GetTranslationFileNameExternal(ReadOnlySpan<char> lang) => $"lang_{lang}.txt";
|
2021-04-12 01:09:54 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
public static IReadOnlyDictionary<string, string> GetDictionary(string lang) => GetContext(lang).Lookup;
|
2018-03-21 05:34:44 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private static TranslationContext GetContext(string lang)
|
|
|
|
{
|
|
|
|
if (Context.TryGetValue(lang, out var context))
|
2018-03-21 05:34:44 +00:00
|
|
|
return context;
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
var lines = GetTranslationFile(lang);
|
|
|
|
Context.Add(lang, context = new TranslationContext(lines));
|
|
|
|
return context;
|
|
|
|
}
|
2022-02-27 05:45:31 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private static void TranslateForm(Control form, TranslationContext context)
|
|
|
|
{
|
|
|
|
form.SuspendLayout();
|
2022-02-27 05:45:31 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
// Translate Title
|
|
|
|
var formName = form.Name;
|
|
|
|
formName = GetSaneFormName(formName);
|
|
|
|
form.Text = context.GetTranslatedText(formName, form.Text);
|
2022-02-27 05:45:31 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
// Translate Controls
|
|
|
|
var translatable = GetTranslatableControls(form);
|
|
|
|
foreach (var c in translatable)
|
|
|
|
TranslateControl(c, context, formName);
|
2022-02-27 05:45:31 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
form.ResumeLayout();
|
|
|
|
}
|
|
|
|
|
2023-01-26 04:36:47 +00:00
|
|
|
internal static void TranslateControls(IEnumerable<Control> controls)
|
|
|
|
{
|
|
|
|
foreach (var c in controls)
|
|
|
|
{
|
|
|
|
foreach (var context in Context.Values)
|
|
|
|
context.GetTranslatedText(c.Name, c.Text);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private static string GetSaneFormName(string formName)
|
|
|
|
{
|
|
|
|
// Strip out generic form names
|
|
|
|
var degen = formName.IndexOf('`');
|
|
|
|
if (degen != -1)
|
|
|
|
formName = formName[..degen];
|
|
|
|
|
|
|
|
return formName switch
|
2022-04-10 01:12:57 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
nameof(SAV_EventFlags2) => nameof(SAV_EventFlags),
|
|
|
|
_ => formName,
|
|
|
|
};
|
|
|
|
}
|
2022-04-10 01:12:57 +00:00
|
|
|
|
2024-03-04 05:13:16 +00:00
|
|
|
private static void TranslateControl(object c, TranslationContext context, ReadOnlySpan<char> formname)
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
|
|
|
if (c is Control r)
|
|
|
|
{
|
|
|
|
var current = r.Text;
|
|
|
|
var updated = context.GetTranslatedText($"{formname}.{r.Name}", current);
|
|
|
|
if (!ReferenceEquals(current, updated))
|
|
|
|
r.Text = updated;
|
2022-04-10 01:12:57 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
else if (c is ToolStripItem t)
|
2022-02-27 05:45:31 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
var current = t.Text;
|
|
|
|
var updated = context.GetTranslatedText($"{formname}.{t.Name}", current);
|
|
|
|
if (!ReferenceEquals(current, updated))
|
|
|
|
t.Text = updated;
|
2018-03-21 05:34:44 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
2018-03-21 05:34:44 +00:00
|
|
|
|
2024-03-04 05:13:16 +00:00
|
|
|
private static ReadOnlySpan<string> GetTranslationFile(ReadOnlySpan<char> lang)
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
|
|
|
var file = GetTranslationFileNameInternal(lang);
|
2023-12-04 04:13:20 +00:00
|
|
|
// Check to see if the desired translation file exists in the same folder as the executable
|
2022-06-18 18:04:24 +00:00
|
|
|
string externalLangPath = GetTranslationFileNameExternal(file);
|
|
|
|
if (File.Exists(externalLangPath))
|
2018-03-21 05:34:44 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
try { return File.ReadAllLines(externalLangPath); }
|
|
|
|
catch { /* In use? Just return the internal resource. */ }
|
2018-03-21 05:34:44 +00:00
|
|
|
}
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (Util.IsStringListCached(file, out var result))
|
|
|
|
return result;
|
|
|
|
var txt = (string?)Properties.Resources.ResourceManager.GetObject(file);
|
|
|
|
return Util.LoadStringList(file, txt);
|
|
|
|
}
|
|
|
|
|
|
|
|
private static IEnumerable<object> GetTranslatableControls(Control f)
|
|
|
|
{
|
|
|
|
foreach (var z in f.GetChildrenOfType<Control>())
|
2018-03-21 05:34:44 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
switch (z)
|
2018-03-21 05:34:44 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
case ToolStrip menu:
|
|
|
|
foreach (var obj in GetToolStripMenuItems(menu))
|
|
|
|
yield return obj;
|
2018-03-21 05:34:44 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
if (string.IsNullOrWhiteSpace(z.Name))
|
2018-03-21 05:34:44 +00:00
|
|
|
break;
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (z.ContextMenuStrip != null) // control has attached MenuStrip
|
|
|
|
{
|
|
|
|
foreach (var obj in GetToolStripMenuItems(z.ContextMenuStrip))
|
|
|
|
yield return obj;
|
|
|
|
}
|
2018-03-21 05:34:44 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (z is ListControl or TextBoxBase or LinkLabel or NumericUpDown or ContainerControl)
|
|
|
|
break; // undesirable to modify, ignore
|
2018-03-21 05:34:44 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (!string.IsNullOrWhiteSpace(z.Text))
|
|
|
|
yield return z;
|
|
|
|
break;
|
2018-03-21 05:34:44 +00:00
|
|
|
}
|
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private static IEnumerable<T> GetChildrenOfType<T>(this Control control) where T : class
|
|
|
|
{
|
|
|
|
foreach (var child in control.Controls.OfType<Control>())
|
2018-03-21 05:34:44 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
if (child is T childOfT)
|
|
|
|
yield return childOfT;
|
2018-03-21 05:34:44 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (!child.HasChildren) continue;
|
|
|
|
foreach (var descendant in GetChildrenOfType<T>(child))
|
|
|
|
yield return descendant;
|
2018-03-21 05:34:44 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private static IEnumerable<object> GetToolStripMenuItems(ToolStrip menu)
|
|
|
|
{
|
|
|
|
foreach (var i in menu.Items.OfType<ToolStripMenuItem>())
|
2018-03-21 05:34:44 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
if (!string.IsNullOrWhiteSpace(i.Text))
|
|
|
|
yield return i;
|
|
|
|
foreach (var sub in GetToolsStripDropDownItems(i).Where(z => !string.IsNullOrWhiteSpace(z.Text)))
|
|
|
|
yield return sub;
|
2018-03-21 05:34:44 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
2018-07-27 02:34:27 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
private static IEnumerable<ToolStripMenuItem> GetToolsStripDropDownItems(ToolStripDropDownItem item)
|
|
|
|
{
|
|
|
|
foreach (var dropDownItem in item.DropDownItems.OfType<ToolStripMenuItem>())
|
2018-03-21 05:34:44 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
yield return dropDownItem;
|
|
|
|
if (!dropDownItem.HasDropDownItems)
|
|
|
|
continue;
|
|
|
|
foreach (ToolStripMenuItem subItem in GetToolsStripDropDownItems(dropDownItem))
|
|
|
|
yield return subItem;
|
2018-03-21 05:34:44 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
2018-03-21 05:34:44 +00:00
|
|
|
|
2022-02-27 05:45:31 +00:00
|
|
|
#if DEBUG
|
2022-06-18 18:04:24 +00:00
|
|
|
public static void UpdateAll(string baseLanguage, IEnumerable<string> others)
|
|
|
|
{
|
|
|
|
var baseContext = GetContext(baseLanguage);
|
|
|
|
foreach (var lang in others)
|
2018-03-21 05:34:44 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
var c = GetContext(lang);
|
|
|
|
c.UpdateFrom(baseContext);
|
2018-03-21 05:34:44 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
2018-03-21 05:34:44 +00:00
|
|
|
|
2024-03-04 05:13:16 +00:00
|
|
|
public static void DumpAll(ReadOnlySpan<string> banlist)
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
2023-01-22 04:02:33 +00:00
|
|
|
foreach (var (lang, value) in Context)
|
2018-03-21 05:34:44 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
var fn = GetTranslationFileNameExternal(lang);
|
2023-01-22 04:02:33 +00:00
|
|
|
var lines = value.Write();
|
2024-03-04 05:13:16 +00:00
|
|
|
|
|
|
|
// Write a new file.
|
|
|
|
using var fs = new StreamWriter(fn);
|
|
|
|
foreach (var line in lines)
|
|
|
|
{
|
|
|
|
// Ensure line isn't banned.
|
|
|
|
if (IsBannedContains(line, banlist))
|
|
|
|
continue;
|
|
|
|
fs.WriteLine(line);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private static bool IsBannedContains(ReadOnlySpan<char> line, ReadOnlySpan<string> banlist)
|
|
|
|
{
|
|
|
|
foreach (var banned in banlist)
|
|
|
|
{
|
|
|
|
if (banned.AsSpan().Contains(line, StringComparison.Ordinal))
|
|
|
|
return true;
|
2018-03-21 05:34:44 +00:00
|
|
|
}
|
2024-03-04 05:13:16 +00:00
|
|
|
return false;
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
2018-04-03 03:55:10 +00:00
|
|
|
|
2024-03-04 05:13:16 +00:00
|
|
|
private static bool IsBannedStartsWith(ReadOnlySpan<char> line, ReadOnlySpan<string> banlist)
|
|
|
|
{
|
|
|
|
foreach (var banned in banlist)
|
|
|
|
{
|
|
|
|
if (line.StartsWith(banned, StringComparison.Ordinal))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
public static void LoadAllForms(IEnumerable<Type> types, ReadOnlySpan<string> banlist)
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
|
|
|
foreach (var t in types)
|
2018-04-03 03:55:10 +00:00
|
|
|
{
|
2024-03-04 05:13:16 +00:00
|
|
|
if (t.BaseType == typeof(Form) && IsBannedStartsWith(t.Name, banlist))
|
|
|
|
continue;
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
var constructors = t.GetConstructors();
|
|
|
|
if (constructors.Length == 0)
|
|
|
|
{ System.Diagnostics.Debug.WriteLine($"No constructors: {t.Name}"); continue; }
|
|
|
|
var argCount = constructors[0].GetParameters().Length;
|
|
|
|
try
|
2018-04-03 03:55:10 +00:00
|
|
|
{
|
2023-08-28 06:05:50 +00:00
|
|
|
_ = (Form?)Activator.CreateInstance(t, new object[argCount]);
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
|
|
|
// This is a debug utility method, will always be logging. Shouldn't ever fail.
|
|
|
|
catch
|
|
|
|
{
|
|
|
|
System.Diagnostics.Debug.Write($"Failed to create a new form {t}");
|
2018-04-03 03:55:10 +00:00
|
|
|
}
|
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
2018-04-04 01:46:32 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
public static void SetRemovalMode(bool status = true)
|
|
|
|
{
|
|
|
|
foreach (TranslationContext c in Context.Values)
|
2018-04-04 01:46:32 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
c.RemoveUsedKeys = status;
|
|
|
|
c.AddNew = !status;
|
2018-04-04 01:46:32 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
2018-04-04 01:46:32 +00:00
|
|
|
|
2024-03-04 05:13:16 +00:00
|
|
|
public static void RemoveAll(string defaultLanguage, ReadOnlySpan<string> banlist)
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
|
|
|
var badKeys = Context[defaultLanguage];
|
2024-03-04 05:13:16 +00:00
|
|
|
var split = GetSkips(banlist, badKeys);
|
2022-06-18 18:04:24 +00:00
|
|
|
foreach (var c in Context)
|
2018-04-04 01:46:32 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
var lang = c.Key;
|
|
|
|
var fn = GetTranslationFileNameExternal(lang);
|
|
|
|
var lines = File.ReadAllLines(fn);
|
|
|
|
var result = lines.Where(l => !split.Any(s => l.StartsWith(s + TranslationContext.Separator)));
|
|
|
|
File.WriteAllLines(fn, result);
|
2018-04-04 01:46:32 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
2021-04-12 01:09:54 +00:00
|
|
|
|
2024-03-04 05:13:16 +00:00
|
|
|
private static string[] GetSkips(ReadOnlySpan<string> banlist, TranslationContext badKeys)
|
|
|
|
{
|
|
|
|
List<string> split = [];
|
|
|
|
foreach (var line in badKeys.Write())
|
|
|
|
{
|
|
|
|
var index = line.IndexOf(TranslationContext.Separator);
|
|
|
|
if (index < 0)
|
|
|
|
continue;
|
|
|
|
var key = line.AsSpan(0, index);
|
|
|
|
if (IsBannedStartsWith(key, banlist))
|
|
|
|
split.Add(key.ToString());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (split.Count == 0)
|
|
|
|
return [];
|
|
|
|
return [..split];
|
|
|
|
}
|
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
public static void LoadSettings<T>(string defaultLanguage, bool add = true)
|
|
|
|
{
|
|
|
|
var context = (Dictionary<string, string>)Context[defaultLanguage].Lookup;
|
|
|
|
var props = typeof(T).GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
|
|
|
foreach (var prop in props)
|
2021-04-12 01:09:54 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
var p = prop.PropertyType.GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
|
|
|
|
foreach (var x in p)
|
2021-04-12 01:09:54 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
var individual = (LocalizedDescriptionAttribute[])x.GetCustomAttributes(typeof(LocalizedDescriptionAttribute), false);
|
|
|
|
foreach (var v in individual)
|
2021-04-12 01:09:54 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
var hasKey = context.ContainsKey(v.Key);
|
|
|
|
if (add)
|
|
|
|
{
|
|
|
|
if (!hasKey)
|
|
|
|
context.Add(v.Key, v.Fallback);
|
|
|
|
}
|
|
|
|
else
|
2021-04-12 01:09:54 +00:00
|
|
|
{
|
2022-06-18 18:04:24 +00:00
|
|
|
if (hasKey)
|
|
|
|
context.Remove(v.Key);
|
2021-04-12 01:09:54 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-03-21 05:34:44 +00:00
|
|
|
}
|
2022-06-18 18:04:24 +00:00
|
|
|
#endif
|
|
|
|
}
|
2018-03-21 05:34:44 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
public sealed class TranslationContext
|
|
|
|
{
|
|
|
|
public bool AddNew { private get; set; }
|
|
|
|
public bool RemoveUsedKeys { private get; set; }
|
|
|
|
public const char Separator = '=';
|
2023-12-04 04:13:20 +00:00
|
|
|
private readonly Dictionary<string, string> Translation = [];
|
2022-06-18 18:04:24 +00:00
|
|
|
public IReadOnlyDictionary<string, string> Lookup => Translation;
|
2018-07-27 02:34:27 +00:00
|
|
|
|
2023-01-22 04:02:33 +00:00
|
|
|
public TranslationContext(ReadOnlySpan<string> content, char separator = Separator)
|
|
|
|
{
|
|
|
|
foreach (var line in content)
|
|
|
|
LoadLine(line, separator);
|
|
|
|
}
|
|
|
|
|
|
|
|
private void LoadLine(ReadOnlySpan<char> line, char separator = Separator)
|
2022-06-18 18:04:24 +00:00
|
|
|
{
|
2023-01-22 04:02:33 +00:00
|
|
|
var split = line.IndexOf(separator);
|
|
|
|
if (split < 0)
|
|
|
|
return; // ignore
|
|
|
|
var key = line[..split].ToString();
|
|
|
|
var value = line[(split + 1)..].ToString();
|
|
|
|
Translation.TryAdd(key, value);
|
2022-06-18 18:04:24 +00:00
|
|
|
}
|
2018-03-21 05:34:44 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
public string? GetTranslatedText(string val, string? fallback)
|
|
|
|
{
|
|
|
|
if (RemoveUsedKeys)
|
|
|
|
Translation.Remove(val);
|
2018-03-21 05:34:44 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (Translation.TryGetValue(val, out var translated))
|
|
|
|
return translated;
|
2018-03-21 05:34:44 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
if (fallback != null && AddNew)
|
|
|
|
Translation.Add(val, fallback);
|
|
|
|
return fallback;
|
|
|
|
}
|
2018-03-21 05:34:44 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
public IEnumerable<string> Write(char separator = Separator)
|
|
|
|
{
|
|
|
|
return Translation.Select(z => $"{z.Key}{separator}{z.Value}").OrderBy(z => z.Contains('.')).ThenBy(z => z);
|
|
|
|
}
|
2018-03-21 05:34:44 +00:00
|
|
|
|
2022-06-18 18:04:24 +00:00
|
|
|
public void UpdateFrom(TranslationContext other)
|
|
|
|
{
|
|
|
|
bool oldAdd = AddNew;
|
|
|
|
AddNew = true;
|
|
|
|
foreach (var kvp in other.Translation)
|
|
|
|
GetTranslatedText(kvp.Key, kvp.Value);
|
|
|
|
AddNew = oldAdd;
|
2018-03-21 05:34:44 +00:00
|
|
|
}
|
|
|
|
}
|