From 93bd98f43207025b2ef3e0eec0fb2728436c5685 Mon Sep 17 00:00:00 2001 From: Kittenji <41535779+ChrisFeline@users.noreply.github.com> Date: Thu, 25 Jul 2024 18:31:28 -0400 Subject: [PATCH] Localization (#38) * First Localization Base * Localize Main Window * Localize Settings window * Add localized strings to Delete Entry * Localized Entry Tooltips * Localize Webhook Content * Localize Objectives Window * Translate Hardcoded Strings * Remove test exception * Remove Debug Strings * Auto detect language * Add Language Dropdown Also added Spanish localization * Implement Fast Replacement * Save current language setting * Fix Fallback Language * Create CONTRIBUTE.md * Update CONTRIBUTE.md * Fix settings window spawning out of bounds * Fix Objectives window out of bounds * Move localization contributions to README * Update CONTRIBUTE.md * Update README --- Localization/CONTRIBUTE.md | 13 +++ Localization/LANG.cs | 169 +++++++++++++++++++++++++++ Localization/Language/en-US.json | 167 ++++++++++++++++++++++++++ Localization/Language/es-ES.json | 167 ++++++++++++++++++++++++++ Models/AppSettings.cs | 22 +++- Models/Entry.cs | 21 +++- Models/Objective.cs | 5 +- Models/SaveData.cs | 9 +- Program.cs | 10 +- README.md | 14 +++ ToNSaveManager.csproj | 4 + Utils/Discord/DSWebHook.cs | 29 +++-- Windows/EditWindow.cs | 12 +- Windows/MainWindow.Designer.cs | 89 +++++++------- Windows/MainWindow.cs | 48 ++++++-- Windows/MainWindow.resx | 2 +- Windows/ObjectivesWindow.Designer.cs | 6 +- Windows/ObjectivesWindow.cs | 24 +++- Windows/ObjectivesWindow.resx | 5 +- Windows/SettingsWindow.Designer.cs | 103 +++++++++------- Windows/SettingsWindow.cs | 110 ++++++++++------- 21 files changed, 858 insertions(+), 171 deletions(-) create mode 100644 Localization/CONTRIBUTE.md create mode 100644 Localization/LANG.cs create mode 100644 Localization/Language/en-US.json create mode 100644 Localization/Language/es-ES.json diff --git a/Localization/CONTRIBUTE.md b/Localization/CONTRIBUTE.md new file mode 100644 index 0000000..c8468d2 --- /dev/null +++ b/Localization/CONTRIBUTE.md @@ -0,0 +1,13 @@ +> ## How to contribute with translations? +> - Fork clone the [localization](https://github.com/ChrisFeline/ToNSaveManager/tree/localization) branch. +> - Create a copy of the `en-US.json` language file into `/Localization/Language` +> - Rename it to your local ISO language name. For example `ja-JP.json` +> - Translate the strings contained within this file into your target language. +> * Keep important string replacement tokens like: `{0}`, `{1}` or `$$MAIN.SETTINGS$$` etc... +> - Create a pull request. +> * Do **NOT** create a pull request into the `main` branch. +> * Make sure the only edited file is the new added language `.json` file, any other contribution in the source code unrelated to this translation will be rejected. +> ### OR +> - Download the file [`en-US.json`](#) from this repo. +> - Rename it to your local ISO language name. For example `ja-JP.json` +> - You can [contact me](#-contact) on discord and I'll review the changes. \ No newline at end of file diff --git a/Localization/LANG.cs b/Localization/LANG.cs new file mode 100644 index 0000000..34b7a3b --- /dev/null +++ b/Localization/LANG.cs @@ -0,0 +1,169 @@ +using Newtonsoft.Json; +using System.Diagnostics; +using System.Reflection; +using System.Text.RegularExpressions; +using ToNSaveManager.Windows; + +namespace ToNSaveManager.Localization { + internal static class LANG { + public struct LangKey { + public string Key; + public string Name; + public string Chars; + + public override string ToString() { + return Name; + } + } + + const string PREF_DEFAULT_KEY = "en-US"; + + static Dictionary> LanguageData = new Dictionary>(); + + static Dictionary SelectedLang = new Dictionary(); + internal static string SelectedKey { get; private set; } = PREF_DEFAULT_KEY; + + static Dictionary SelectedDefault = new Dictionary(); + static string SelectedDefaultKey = string.Empty; + + internal static List AvailableLang { get; private set; } = new List(); + static readonly Regex ReplacePattern = new Regex(@"\$\$(?.*?)\$\$", RegexOptions.Compiled); + const string PatternSearch = "$$"; + + private static string? D(string key, params string[] args) { +#if DEBUG + // if (!key.EndsWith(".TT")) Debug.WriteLine($"Missing key '{key}' in language pack '{SelectedKey}'"); +#endif + + if (SelectedDefault.ContainsKey(key)) { + return args.Length > 0 ? string.Format(SelectedDefault[key], args) : SelectedDefault[key]; + } + +#if DEBUG + if (!key.EndsWith(".TT")) Debug.WriteLine($"Invalid language key '{key}'"); +#endif + return null; + } + + public static string? S(string key, params string[] args) { + string? result; + if (SelectedLang.ContainsKey(key)) { + result = args.Length > 0 ? string.Format(SelectedLang[key], args) : SelectedLang[key]; + } else { + result = D(key, args); + } + + if (!string.IsNullOrEmpty(result) && result.Contains(PatternSearch)) { + result = ReplacePattern.Replace(result, (v) => { + string k = v.Groups["key"].Value; + return S(k) ?? v.Value; + }); + } + + return result; + } + + public static (string?, string?) T(string key, params string[] args) { + return (S(key, args), S(key + ".TT", args)); + } + + public static void C(Control control, string key, ToolTip? toolTip = null) { + (string? tx, string? tt) = T(key); + if (!string.IsNullOrEmpty(tx)) control.Text = tx; + if (!string.IsNullOrEmpty(tt)) { + if (toolTip == null) TooltipUtil.Set(control, tt); + else toolTip.SetToolTip(control, tt); + } + } + public static void C(ToolStripItem item, string key) { + (string? text, string? tooltip) = T(key); + if (!string.IsNullOrEmpty(text)) item.Text = text; + if (!string.IsNullOrEmpty(tooltip)) item.ToolTipText = tooltip; + } + + internal static void Select(string key) { + Debug.WriteLine("Selecting language key: " + key); + SelectedLang = LanguageData.ContainsKey(key) ? LanguageData[key] : LanguageData[key = SelectedDefaultKey]; + SelectedKey = key; + } + + internal static string FindLanguageKey() { + var currentCulture = System.Globalization.CultureInfo.CurrentUICulture; + string langName = currentCulture.TwoLetterISOLanguageName; + string fullLangName = currentCulture.Name; + + string[] languageKeys = LanguageData.Keys.ToArray(); + + string foundKey = SelectedDefaultKey; + for (int i = 0; i < 2; i++) { + foreach (string key in languageKeys) { + bool check = i == 0 ? + key.Equals(fullLangName, StringComparison.OrdinalIgnoreCase) : + key.StartsWith(langName); + + if (check) { + foundKey = key; + i = 3; + break; + } + } + } + + return foundKey; + } + + internal static void ReloadAll() { + MainWindow.Instance?.LocalizeContent(); + EditWindow.Instance?.LocalizeContent(); + ObjectivesWindow.Instance?.LocalizeContent(); + SettingsWindow.Instance?.LocalizeContent(); + } + + internal static void Initialize() { + Assembly assembly = Assembly.GetExecutingAssembly(); + string[] streamNames = assembly.GetManifestResourceNames(); + + string? firstKey = null; + foreach (string name in streamNames) { + string[] split = name.Split('.'); + if (name.EndsWith(".json") && Array.IndexOf(split, "Localization") > 0 && Array.IndexOf(split, "Language") > 0) { + using (Stream? stream = assembly.GetManifestResourceStream(name)) { + if (stream != null) { + using (StreamReader reader = new StreamReader(stream)) { + string json = reader.ReadToEnd(); + var obj = JsonConvert.DeserializeObject>(json); + if (obj != null) { + string key = split[split.Length - 2]; + LanguageData.Add(key, obj); + + if (key == PREF_DEFAULT_KEY) { + SelectedDefault = obj; + SelectedDefaultKey = key; + Select(key); + + Debug.WriteLine("Found default language."); + } + + if (string.IsNullOrEmpty(firstKey)) firstKey = key; + + Debug.WriteLine("Added language with key: " + key); + AvailableLang.Add(new LangKey() { Key = key, Chars = obj["DISPLAY_INIT"], Name = obj["DISPLAY_NAME"] }); + } + } + } + } + } + } + + if (string.IsNullOrEmpty(SelectedDefaultKey) && !string.IsNullOrEmpty(firstKey)) { + Debug.WriteLine("Default prefered language not found, using " + firstKey); + + SelectedDefault = LanguageData[firstKey]; + SelectedDefaultKey = firstKey; + Select(SelectedDefaultKey); + } else if (string.IsNullOrEmpty(firstKey)) { + throw new Exception("Could not load any language pack."); + } + } + } +} diff --git a/Localization/Language/en-US.json b/Localization/Language/en-US.json new file mode 100644 index 0000000..218c341 --- /dev/null +++ b/Localization/Language/en-US.json @@ -0,0 +1,167 @@ +{ + "DISPLAY_INIT": "EN", + "DISPLAY_NAME": "English", + + "EDIT.SAVE": "Save", + "EDIT.OK": "OK", + "EDIT.CANCEL": "Cancel", + + "MESSAGE.WRITE_SETTINGS_ERROR": "An error ocurred while trying to write your settings to a file.\n\nMake sure that the program contains permissions to write files in the current folder it's located at.", + "MESSAGE.COPY_FILES_ERROR": "An error ocurred while trying to copy your files to the selected location.\n\nMake sure that the program contains permissions to write files to the destination.\nPath: {0}", + "MESSAGE.IMPORT_SAVE_ERROR": "Error trying to import your save.", + "MESSAGE.WRITE_SAVE_ERROR": "An error ocurred while trying to write your saves to a file.\n\nMake sure that the program contains permissions to write files to the destination.\nPath: {0}", + "MESSAGE.SAVE_LOCATION_RESET": "Save data location has been reset to default.", + "MESSAGE.SAVE_LOCATION_RESET.TITLE": "Reset Custom Data Location", + "MESSAGE.UPDATE_AVAILABLE": "A new update have been released on GitHub.\n\nWould you like to automatically download and update to the new version?", + "MESSAGE.UPDATE_AVAILABLE.TITLE": "New update available", + "MESSAGE.UPDATE_UNAVAILABLE": "No updates are currently available.", + "MESSAGE.UPDATE_UNAVAILABLE.TITLE": "No updates available", + + "MESSAGE.COPY_TO_CLIPBOARD": "Copied to clipboard!\n\nYou can now paste the code in game.", + "MESSAGE.COPY_TO_CLIPBOARD.TITLE": "Copied", + + "MAIN.SETTINGS": "Settings", + "MAIN.SETTINGS.TT": "Configure other options under the settings menu.", + "MAIN.OBJECTIVES": "Objectives", + "MAIN.OBJECTIVES.TT": "Keep track of in game unlockables.", + "MAIN.WIKI": "Wiki", + "MAIN.WIKI.TT": "Visit the wiki \"terror.moe\" for more useful information.", + "MAIN.SUPPORT.TT": "Buy Me A Coffee ❤️", + + "MAIN.CTX_IMPORT": "Import", + "MAIN.CTX_IMPORT.TITLE": "Import Save Code", + + "MAIN.CTX_RENAME": "Rename", + "MAIN.CTX_RENAME.TITLE": "Set Collection Name", + + "MAIN.CTX_DELETE": "Delete", + + "MAIN.CTX_DELETE_ALL.TITLE": "Deleting Collection: {0}", + "MAIN.CTX_DELETE_ALL.SUBTITLE": "Are you SURE that you want to delete this collection?\n\nEvery save code from '{0}' will be permanently deleted.\n\nThis operation is not reversible!", + + "MAIN.CTX_DELETE_ENTRY.TITLE": "Deleting Entry: {0}", + "MAIN.CTX_DELETE_ENTRY.SUBTITLE": "Are you SURE that you want to delete this entry?\n\nYour save '{0}' will be permanently deleted.\n\nThis operation is not reversible!", + + "MAIN.CTX_ADD_TO": "Add to", + "MAIN.CTX_ADD_TO.NEW": "New Collection", + "MAIN.CTX_ADD_TO.NEW.TT": "Add this entry to a new collection.", + "MAIN.CTX_EDIT_NOTE": "Edit note", + "MAIN.CTX_EDIT_NOTE.TITLE": "Note Editor", + "MAIN.CTX_BACKUP": "Backup", + "MAIN.CTX_BACKUP.TT": "Force upload a backup of this code to Discord as a file... Requires \"$$SETTINGS.DISCORDWEBHOOKENABLED$$\" to be enabled under \"$$MAIN.SETTINGS$$\"", + + "MAIN.ENTRY_NOTE": "Note:", + "MAIN.ENTRY_ROUND": "Round Type:", + "MAIN.ENTRY_TERRORS": "Terrors in round:", + "MAIN.ENTRY_PLAYERS": "Players in room:", + + "SETTINGS.CHECK_UPDATE": "Check For Updates", + "SETTINGS.OPEN_DATA_BTN": "Data", + "SETTINGS.OPEN_DATA_BTN.TT": "Open save data folder.", + "SETTINGS.VERSION": "Current Version: {0}", + "SETTINGS.CUSTOM_DATA_FOLDER": "Custom Data Folder", + "SETTINGS.CUSTOM_DATA_PICK_FOLDER": "Pick Folder", + "SETTINGS.CUSTOM_DATA_RESET_DEFAULT": "Reset to Default", + + "SETTINGS.GROUP.GENERAL": "General", + "SETTINGS.SKIPPARSEDLOGS": "Skip Parsed Logs (!)", + "SETTINGS.SKIPPARSEDLOGS.TT": "Skip old parsed log files that were already processed and saved.\nOnly disable this if you accidentally deleted a save code.", + "SETTINGS.AUTOCOPY": "Auto Clipboard Copy", + "SETTINGS.AUTOCOPY.TT": "Automatically copy new save codes to clipboard.", + "SETTINGS.SAVENAMES": "Collect Player Names", + "SETTINGS.SAVENAMES.TT": "Save codes will show players in the instance at the time of saving.", + "SETTINGS.SAVEROUNDINFO": "Save Round Info", + "SETTINGS.SAVEROUNDINFO.TT": "Save codes will display the last round type and terror names.", + "SETTINGS.SAVEROUNDNOTE": "Terror Name Notes", + "SETTINGS.SAVEROUNDNOTE.TT": "Automatically set survived terror names as note.", + "SETTINGS.SHOWWINLOSE": "Show [R][W][D] Tags", + "SETTINGS.SHOWWINLOSE.TT": "Entries will show a [R], [W] or [D] tag based on the source that triggered the save.", + "SETTINGS.OSCENABLED": "Send OSC Parameters", + "SETTINGS.OSCENABLED.TT": "Sends avatar parameters to VRChat using OSC.\nRight click this entry to open documentation about parameter names and types.", + + "SETTINGS.DISCORDWEBHOOKENABLED": "Auto Discord Backup (Webhook)", + "SETTINGS.DISCORDWEBHOOKENABLED.TT": "Automatically saves your new codes to a Discord channel using a webhook integration.", + "SETTINGS.DISCORDWEBHOOK.TITLE": "Discord Webhook URL", + "SETTINGS.DISCORDWEBHOOKINVALID": "The URL your provided does not match a discord webhook url.\n\nMake sure you created your webhook and copied the url correctly.", + "SETTINGS.DISCORDWEBHOOKINVALID.TITLE": "Invalid Webhook URL", + + "SETTINGS.DISCORDWEBHOOK.LABEL_PLAYER": "**Player**: `{0}`", + "SETTINGS.DISCORDWEBHOOK.LABEL_ROUND": "**Round Type**: `{0}`", + "SETTINGS.DISCORDWEBHOOK.LABEL_TERRORS": "**Terrors in Round**: `{0}`", + "SETTINGS.DISCORDWEBHOOK.LABEL_TERRORS_SPLIT": "`, `", + "SETTINGS.DISCORDWEBHOOK.LABEL_COUNT": "**Player Count**: `{0}`", + "SETTINGS.DISCORDWEBHOOK.LABEL_NOTE": "**Note**: `{0}`", + + "SETTINGS.GROUP.NOTIFICATIONS": "Notifications", + + "SETTINGS.XSOVERLAY": "XSOverlay Popup", + "SETTINGS.XSOVERLAY.TT": "XSOverlay popup notifications when saving.", + "SETTINGS.XSOVERLAY.MESSAGE": "ToN: Save Data Stored", + "SETTINGS.XSOVERLAY.TOGGLE": "ToN: Notifications Enabled", + + "SETTINGS.PLAYAUDIO": "Play Audio ({0})", + "SETTINGS.PLAYAUDIO.TT": "Double click to select custom audio file.\nRight click to reset back to 'default.wav'", + "SETTINGS.PLAYAUDIO.TITLE": "Select Custom Audio", + + "SETTINGS.GROUP.TIME_FORMAT": "Time Formatting", + "SETTINGS.USE24HOUR": "24 Hour Time", + "SETTINGS.SHOWSECONDS": "Show Seconds", + "SETTINGS.INVERTMD": "Invert Month/Day", + "SETTINGS.SHOWDATE": "Right Panel Date", + "SETTINGS.SHOWDATE.TT": "Entries on the right panel will display a full date.", + + "SETTINGS.GROUP.STYLE": "Style", + "SETTINGS.COLORFULOBJECTIVES": "Colorful Objectives", + "SETTINGS.COLORFULOBJECTIVES.TT": "Items in the 'Objectives' window will show colors that correspond to those of the items in the game.", + + "OBJECTIVES.TITLE": "ToN Objectives", + "OBJECTIVES.EVENT_ITEMS_UNLOCKS": "Event Items Unlocks", + "OBJECTIVES.SEALED_SWORD": "Sealed Sword", + "OBJECTIVES.SEALED_SWORD.TT": "Found in Museum. Break case with a stun tool.", + "OBJECTIVES.GRAN_FAUST": "Gran Faust", + "OBJECTIVES.GRAN_FAUST.TT": "Survive Arkus with '$$OBJECTIVES.SEALED_SWORD$$'.", + "OBJECTIVES.DIVINE_AVENGER": "Divine Avenger", + "OBJECTIVES.DIVINE_AVENGER.TT": "Survive Arkus with '$$OBJECTIVES.SEALED_SWORD$$' after hitting them at least two times.", + "OBJECTIVES.MAXWELL": "Maxwell", + "OBJECTIVES.MAXWELL.TT": "Found in Its Maze. (spawns once per round)", + "OBJECTIVES.ROCK": "Rock", + "OBJECTIVES.ROCK.TT": "Survive Fusion Pilot.", + "OBJECTIVES.ILLUMINA": "Illumina", + "OBJECTIVES.ILLUMINA.TT": "Survive Bliss.", + "OBJECTIVES.REDBULL": "Redbull", + "OBJECTIVES.REDBULL.TT": "Survive Roblander.", + "OBJECTIVES.OMORI_PLUSH": "Omori Plush", + "OBJECTIVES.OMORI_PLUSH.TT": "Survive Something.", + "OBJECTIVES.PARADISE_LOST": "Paradise Lost", + "OBJECTIVES.PARADISE_LOST.TT": "Beat the shit out of Apostles.", + "OBJECTIVES.ITEM_SKIN_UNLOCKS": "Item Skin Unlocks", + "OBJECTIVES.RED_MEDKIT": "Red Medkit", + "OBJECTIVES.RED_MEDKIT.TT": "Survive Virus with Medkit.", + "OBJECTIVES.PSYCHO_COIL": "Psycho Coil", + "OBJECTIVES.PSYCHO_COIL.TT": "Survive Psychosis with Glow Coil.", + "OBJECTIVES.BLOODY_TELEPORTER": "Bloody Teleporter", + "OBJECTIVES.BLOODY_TELEPORTER.TT": "Survive a Bloodbath round with Teleporter.", + "OBJECTIVES.PALE_SUITCASE": "Pale Suitcase", + "OBJECTIVES.PALE_SUITCASE.TT": "Survive an Alternate round with Teleporter.", + "OBJECTIVES.THORN_HACKER": "Thorn Hacker", + "OBJECTIVES.THORN_HACKER.TT": "Survive Pandora with Teleporter.", + "OBJECTIVES.BLOODY_COIL": "Bloody Coil", + "OBJECTIVES.BLOODY_COIL.TT": "Survive a Bloodbath round with Speed Coil.", + "OBJECTIVES.BLOODY_BAT": "Bloody Bat", + "OBJECTIVES.BLOODY_BAT.TT": "Survive a Bloodbath round with Metal Bat.", + "OBJECTIVES.METAL_PIPE": "Metal Pipe", + "OBJECTIVES.METAL_PIPE.TT": "Survive an Alternate round with Metal Bat.", + "OBJECTIVES.COLORABLE_BAT": "Colorable Bat", + "OBJECTIVES.COLORABLE_BAT.TT": "Survive a Cracked round with Metal Bat.", + "OBJECTIVES.JUSTITIA": "Justitia", + "OBJECTIVES.JUSTITIA.TT": "Survive a Midnight round with Metal Bat.", + "OBJECTIVES.TWILIGHT_COIL": "Twilight Coil", + "OBJECTIVES.TWILIGHT_COIL.TT": "Survive Apocalypse Bird with Chaos Coil.", + "OBJECTIVES.PALE_PISTOL": "Pale Pistol", + "OBJECTIVES.PALE_PISTOL.TT": "Survive an Alternate round with Antique Revolver.", + "OBJECTIVES.WINTERFEST_UNLOCKS": "Winterfest Unlocks", + "OBJECTIVES.SNOWY_SPEED_COIL": "Snowy Speed Coil", + "OBJECTIVES.SNOWY_SPEED_COIL.TT": "There's something in the snow for you.", + "OBJECTIVES.TORCH_OF_OBSESSION": "Torch Of Obsession", + "OBJECTIVES.TORCH_OF_OBSESSION.TT": "Survive the Cold Night." +} \ No newline at end of file diff --git a/Localization/Language/es-ES.json b/Localization/Language/es-ES.json new file mode 100644 index 0000000..abb3b46 --- /dev/null +++ b/Localization/Language/es-ES.json @@ -0,0 +1,167 @@ +{ + "DISPLAY_INIT": "ES", + "DISPLAY_NAME": "Español", + + "EDIT.SAVE": "Guardar", + "EDIT.OK": "OK", + "EDIT.CANCEL": "Cancelar", + + "MESSAGE.WRITE_SETTINGS_ERROR": "Se produjo un error al intentar escribir su configuración en un archivo.\n\nAsegúrese de que el programa contenga permisos para escribir archivos en la carpeta actual en la que se encuentra.", + "MESSAGE.COPY_FILES_ERROR": "Ocurrió un error al intentar copiar sus archivos a la ubicación seleccionada.\n\nAsegúrese de que el programa contenga permisos para escribir archivos en el destino.\nPath: {0}", + "MESSAGE.IMPORT_SAVE_ERROR": "Error al intentar importar tu archivo de guardado.", + "MESSAGE.WRITE_SAVE_ERROR": "Se produjo un error al intentar escribir tus códigos de guardado en un archivo.\n\nAsegúrese de que el programa contenga permisos para escribir archivos en el destino.\nPath: {0}", + "MESSAGE.SAVE_LOCATION_RESET": "La ubicación de los datos guardados se ha restablecido a su valor predeterminado.", + "MESSAGE.SAVE_LOCATION_RESET.TITLE": "Restablecer Ubicación de Datos", + "MESSAGE.UPDATE_AVAILABLE": "Se ha encontrado una nueva actualización en GitHub.\n\n¿Le gustaría descargar y actualizar automáticamente a la nueva versión?", + "MESSAGE.UPDATE_AVAILABLE.TITLE": "Nueva actualización disponible", + "MESSAGE.UPDATE_UNAVAILABLE": "Actualmente no hay actualizaciones disponibles.", + "MESSAGE.UPDATE_UNAVAILABLE.TITLE": "No hay actualizaciones disponibles", + + "MESSAGE.COPY_TO_CLIPBOARD": "¡Copiado al portapapeles!\n\nAhora puedes pegar el código en el juego.", + "MESSAGE.COPY_TO_CLIPBOARD.TITLE": "Copiado", + + "MAIN.SETTINGS": "Ajustes", + "MAIN.SETTINGS.TT": "Configure otras opciones en el menú de ajustes.", + "MAIN.OBJECTIVES": "Objetivos", + "MAIN.OBJECTIVES.TT": "Lista para seguir los elementos desbloqueables del juego.", + "MAIN.WIKI": "Wiki", + "MAIN.WIKI.TT": "Visita la wiki \"terror.moe\" para obtener más información útil.", + "MAIN.SUPPORT.TT": "Cómprame Un Café ❤️", + + "MAIN.CTX_IMPORT": "Importar", + "MAIN.CTX_IMPORT.TITLE": "Importar Código", + + "MAIN.CTX_RENAME": "Renombrar", + "MAIN.CTX_RENAME.TITLE": "Escribe un nombre para nueva colección", + + "MAIN.CTX_DELETE": "Borrar", + + "MAIN.CTX_DELETE_ALL.TITLE": "Borrar Colección: {0}", + "MAIN.CTX_DELETE_ALL.SUBTITLE": "¿Está SEGURO de que desea eliminar esta colección?\n\nCada código guardado en '{0}' se eliminará permanentemente.\n\n¡Esta operación no es reversible!", + + "MAIN.CTX_DELETE_ENTRY.TITLE": "Borrar Entrada: {0}", + "MAIN.CTX_DELETE_ENTRY.SUBTITLE": "¿Está SEGURO de que desea eliminar esta entrada?\n\nSu código de guardado '{0}' se eliminará permanentemente.\n\n¡Esta operación no es reversible!", + + "MAIN.CTX_ADD_TO": "Agregar a", + "MAIN.CTX_ADD_TO.NEW": "Nueva Colección", + "MAIN.CTX_ADD_TO.NEW.TT": "Añade esta entrada a una nueva colección.", + "MAIN.CTX_EDIT_NOTE": "Editar nota", + "MAIN.CTX_EDIT_NOTE.TITLE": "Editar Nota", + "MAIN.CTX_BACKUP": "Respaldar", + "MAIN.CTX_BACKUP.TT": "Crea una copia de seguridad de este código en Discord subiendo un archivo... Requiere tener habilitado \"$$SETTINGS.DISCORDWEBHOOKENABLED$$\" en \"$$MAIN.SETTINGS$$\"", + + "MAIN.ENTRY_NOTE": "Nota:", + "MAIN.ENTRY_ROUND": "Tipo de ronda:", + "MAIN.ENTRY_TERRORS": "Terrors en ronda:", + "MAIN.ENTRY_PLAYERS": "Jugadores en la sala:", + + "SETTINGS.CHECK_UPDATE": "Buscar Actualizaciones", + "SETTINGS.OPEN_DATA_BTN": "Datos", + "SETTINGS.OPEN_DATA_BTN.TT": "Abra la carpeta de datos guardados.", + "SETTINGS.VERSION": "Versión actual: {0}", + "SETTINGS.CUSTOM_DATA_FOLDER": "Carpeta de Datos Personalizada", + "SETTINGS.CUSTOM_DATA_PICK_FOLDER": "Seleccionar Carpeta", + "SETTINGS.CUSTOM_DATA_RESET_DEFAULT": "Restablecer Predeterminado", + + "SETTINGS.GROUP.GENERAL": "General", + "SETTINGS.SKIPPARSEDLOGS": "Omitir Logs Analizados (!)", + "SETTINGS.SKIPPARSEDLOGS.TT": "Omitir archivos de logs antiguos analizados que ya fueron procesados ​​y guardados.\nDesactive esto solo si eliminó accidentalmente un código de guardado.", + "SETTINGS.AUTOCOPY": "Auto Copiar al Portapapeles", + "SETTINGS.AUTOCOPY.TT": "Automáticamente copia nuevos códigos de guardado al portapapeles.", + "SETTINGS.SAVENAMES": "Guardar Nombres de Jugadores", + "SETTINGS.SAVENAMES.TT": "Los códigos de guardado mostrarán a los jugadores que se encontraban en la instancia en el momento de guardar..", + "SETTINGS.SAVEROUNDINFO": "Guardar Información de Ronda", + "SETTINGS.SAVEROUNDINFO.TT": "Los códigos de guardado mostrarán el tipo de ronda y los nombres de los Terrors mas reciente..", + "SETTINGS.SAVEROUNDNOTE": "Nombre de Terrors en Notas", + "SETTINGS.SAVEROUNDNOTE.TT": "Establecer automáticamente los nombres de los Terrors sobrevividos como nota.", + "SETTINGS.SHOWWINLOSE": "Mostrar tags [R][W][D]", + "SETTINGS.SHOWWINLOSE.TT": "Las entradas mostrarán una [R], [W] o [D] basado en la fuente que inició el guardado.", + "SETTINGS.OSCENABLED": "Enviar Parámetros OSC", + "SETTINGS.OSCENABLED.TT": "Envía parámetros de avatar a VRChat usando OSC.\nHaga clic derecho en esta entrada para abrir la documentación como nombres y tipos de parámetros..", + + "SETTINGS.DISCORDWEBHOOKENABLED": "Copia de Seguridad en Discord", + "SETTINGS.DISCORDWEBHOOKENABLED.TT": "Guarda automáticamente tus nuevos códigos en un canal de Discord mediante una integración de webhook.", + "SETTINGS.DISCORDWEBHOOK.TITLE": "URL del Webhook de Discord", + "SETTINGS.DISCORDWEBHOOKINVALID": "La URL que proporcionó no coincide con la URL de Webhook de Discord.\n\nAsegúrate de haber creado tu webhook y haber copiado la URL correctamente.", + "SETTINGS.DISCORDWEBHOOKINVALID.TITLE": "URL de Webhook no válida", + + "SETTINGS.DISCORDWEBHOOK.LABEL_PLAYER": "**Jugador**: `{0}`", + "SETTINGS.DISCORDWEBHOOK.LABEL_ROUND": "**Tipo de ronda**: `{0}`", + "SETTINGS.DISCORDWEBHOOK.LABEL_TERRORS": "**Terrors en ronda**: `{0}`", + "SETTINGS.DISCORDWEBHOOK.LABEL_TERRORS_SPLIT": "`, `", + "SETTINGS.DISCORDWEBHOOK.LABEL_COUNT": "**Jugadores en la sala**: `{0}`", + "SETTINGS.DISCORDWEBHOOK.LABEL_NOTE": "**Nota**: `{0}`", + + "SETTINGS.GROUP.NOTIFICATIONS": "Notificaciones", + + "SETTINGS.XSOVERLAY": "XSOverlay", + "SETTINGS.XSOVERLAY.TT": "Notificaciones emergentes para XSOverlay al guardar.", + "SETTINGS.XSOVERLAY.MESSAGE": "ToN: Datos Guardados", + "SETTINGS.XSOVERLAY.TOGGLE": "ToN: Notificaciones Activadas", + + "SETTINGS.PLAYAUDIO": "Reproducir Audio ({0})", + "SETTINGS.PLAYAUDIO.TT": "Haga doble clic para seleccionar un archivo de audio personalizado.\nHaga clic derecho para restablecer a 'default.wav'", + "SETTINGS.PLAYAUDIO.TITLE": "Seleccionar Audio Personalizado", + + "SETTINGS.GROUP.TIME_FORMAT": "Formato de hora", + "SETTINGS.USE24HOUR": "Horario de 24 horas", + "SETTINGS.SHOWSECONDS": "Mostrar segundos", + "SETTINGS.INVERTMD": "Invertir Mes/Día", + "SETTINGS.SHOWDATE": "Fecha en Panel Derecho", + "SETTINGS.SHOWDATE.TT": "Las entradas en el panel derecho mostrarán una fecha completa.", + + "SETTINGS.GROUP.STYLE": "Estilo", + "SETTINGS.COLORFULOBJECTIVES": "Objetivos Coloridos", + "SETTINGS.COLORFULOBJECTIVES.TT": "Los elementos en la ventana '$$MAIN.OBJECTIVES$$' mostrarán colores que corresponden a los de los elementos del juego.", + + "OBJECTIVES.TITLE": "Objetivos", + "OBJECTIVES.EVENT_ITEMS_UNLOCKS": "Desbloqueos de Eventos", + "OBJECTIVES.SEALED_SWORD": "Sealed Sword", + "OBJECTIVES.SEALED_SWORD.TT": "Encontrado en el Museo. Rompe la caja de cristal con una herramienta que pueda golpear.", + "OBJECTIVES.GRAN_FAUST": "Gran Faust", + "OBJECTIVES.GRAN_FAUST.TT": "Sobrevive Arkus con '$$OBJECTIVES.SEALED_SWORD$$'.", + "OBJECTIVES.DIVINE_AVENGER": "Divine Avenger", + "OBJECTIVES.DIVINE_AVENGER.TT": "Sobrevive a Arkus con '$$OBJECTIVES.SEALED_SWORD$$' después de golpearlo al menos dos veces.", + "OBJECTIVES.MAXWELL": "Maxwell", + "OBJECTIVES.MAXWELL.TT": "Se puede encontrar en el mapa 'Its Maze' (aparece una vez por ronda)", + "OBJECTIVES.ROCK": "Rock", + "OBJECTIVES.ROCK.TT": "Sobrevive el Terror: 'Fusion Pilot'", + "OBJECTIVES.ILLUMINA": "Illumina", + "OBJECTIVES.ILLUMINA.TT": "Sobrevive el Terror: 'Bliss'", + "OBJECTIVES.REDBULL": "Redbull", + "OBJECTIVES.REDBULL.TT": "Sobrevive el Terror: 'Roblander'", + "OBJECTIVES.OMORI_PLUSH": "Omori Plush", + "OBJECTIVES.OMORI_PLUSH.TT": "Sobrevive el terror: 'Something'", + "OBJECTIVES.PARADISE_LOST": "Paradise Lost", + "OBJECTIVES.PARADISE_LOST.TT": "Golpea como loco a los Apóstoles.", + "OBJECTIVES.ITEM_SKIN_UNLOCKS": "Desbloqueos de Aspectos", + "OBJECTIVES.RED_MEDKIT": "Red Medkit", + "OBJECTIVES.RED_MEDKIT.TT": "Sobrevive 'Virus' con un 'Medkit'", + "OBJECTIVES.PSYCHO_COIL": "Psycho Coil", + "OBJECTIVES.PSYCHO_COIL.TT": "Sobrevive 'Psychosis' con un 'Glow Coil'", + "OBJECTIVES.BLOODY_TELEPORTER": "Bloody Teleporter", + "OBJECTIVES.BLOODY_TELEPORTER.TT": "Sobrevive la ronda 'Bloodbath' con un 'Teleporter'", + "OBJECTIVES.PALE_SUITCASE": "Pale Suitcase", + "OBJECTIVES.PALE_SUITCASE.TT": "Sobrevive una ronda 'Alternate' con un 'Teleporter'", + "OBJECTIVES.THORN_HACKER": "Thorn Hacker", + "OBJECTIVES.THORN_HACKER.TT": "Sobrevive 'Pandora' con un 'Teleporter'", + "OBJECTIVES.BLOODY_COIL": "Bloody Coil", + "OBJECTIVES.BLOODY_COIL.TT": "Sobrevive una ronda 'Bloodbath' con un 'Speed Coil'", + "OBJECTIVES.BLOODY_BAT": "Bloody Bat", + "OBJECTIVES.BLOODY_BAT.TT": "Sobrevive una ronda 'Bloodbath' con un 'Metal Bat' (Bate de Metal)", + "OBJECTIVES.METAL_PIPE": "Metal Pipe", + "OBJECTIVES.METAL_PIPE.TT": "Sobrevive una ronda 'Alternate' con un 'Metal Bat' (Bate de Metal)", + "OBJECTIVES.COLORABLE_BAT": "Colorable Bat", + "OBJECTIVES.COLORABLE_BAT.TT": "Sobrevive una ronda 'Cracked' con un 'Metal Bat' (Bate de Metal)", + "OBJECTIVES.JUSTITIA": "Justitia", + "OBJECTIVES.JUSTITIA.TT": "Sobrevive una ronda 'Midnight' con un 'Metal Bat' (Bate de Metal)", + "OBJECTIVES.TWILIGHT_COIL": "Twilight Coil", + "OBJECTIVES.TWILIGHT_COIL.TT": "Sobrevive 'Apocalypse Bird' con un 'Chaos Coil'", + "OBJECTIVES.PALE_PISTOL": "Pale Pistol", + "OBJECTIVES.PALE_PISTOL.TT": "Sobrevive una ronda 'Alternate' con un 'Antique Revolver'", + "OBJECTIVES.WINTERFEST_UNLOCKS": "Desbloqueos del Festival de Invierno", + "OBJECTIVES.SNOWY_SPEED_COIL": "Snowy Speed Coil", + "OBJECTIVES.SNOWY_SPEED_COIL.TT": "Hay algo en la nieve para ti.", + "OBJECTIVES.TORCH_OF_OBSESSION": "Torch Of Obsession", + "OBJECTIVES.TORCH_OF_OBSESSION.TT": "Sobrevive una ronda 'Cold Night' (Noche Fría)" +} \ No newline at end of file diff --git a/Models/AppSettings.cs b/Models/AppSettings.cs index 316e55a..5bb10df 100644 --- a/Models/AppSettings.cs +++ b/Models/AppSettings.cs @@ -1,4 +1,6 @@ using Newtonsoft.Json; +using System.Diagnostics; +using ToNSaveManager.Localization; namespace ToNSaveManager.Models { @@ -100,6 +102,11 @@ static Settings() /// public bool RecordInstanceLogs { get; set; } = false; + /// + /// Used internally for language selection. + /// + public string SelectedLanguage { get; set; } = string.Empty; + /// /// Import a settings instance from the local json file /// @@ -131,7 +138,18 @@ public static Settings Import() settings = null; } - return settings ?? new Settings(); + if (settings == null) + settings = new Settings(); + + string selectedLanguage = settings.SelectedLanguage; + + if (string.IsNullOrEmpty(selectedLanguage)) { + selectedLanguage = LANG.FindLanguageKey(); + } + + LANG.Select(selectedLanguage); + + return settings; } private void TryExport() @@ -143,7 +161,7 @@ private void TryExport() } catch (Exception e) { - MessageBox.Show("An error ocurred while trying to write your settings to a file.\n\nMake sure that the program contains permissions to write files in the current folder it's located at.\n\n" + e, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBox.Show((LANG.S("MESSAGE.WRITE_SETTINGS_ERROR") ?? "An error ocurred while trying to write your settings to a file.\n\nMake sure that the program contains permissions to write files in the current folder it's located at.") + "\n\n" + e, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } } diff --git a/Models/Entry.cs b/Models/Entry.cs index 1435f87..73eb06c 100644 --- a/Models/Entry.cs +++ b/Models/Entry.cs @@ -1,6 +1,7 @@ using Newtonsoft.Json; using System.Diagnostics; using System.Text; +using ToNSaveManager.Localization; namespace ToNSaveManager.Models { @@ -30,6 +31,18 @@ internal static string GetDateFormat(bool isEntry = false) internal class Entry { + static string TextNote = "Note:"; + static string TextRound = "Round Type:"; + static string TextTerrors = "Terrors in round:"; + static string TextPlayers = "Players in room:"; + + internal static void LocalizeContent() { + TextNote = LANG.S("MAIN.ENTRY_NOTE") ?? "Note:"; + TextRound = LANG.S("MAIN.ENTRY_ROUND") ?? "Round Type:"; + TextTerrors = LANG.S("MAIN.ENTRY_TERRORS") ?? "Terrors in round:"; + TextPlayers = LANG.S("MAIN.ENTRY_PLAYERS") ?? "Players in room:"; + } + public string Note = string.Empty; public DateTime Timestamp; @@ -86,7 +99,7 @@ public string GetTooltip(bool showPlayers, bool showTerrors, bool showNote = tru { sb.AppendLine(); sb.AppendLine(); - sb.Append("Note: \n- "); + sb.Append(TextNote + " \n- "); sb.Append(Note); } if (showTerrors && RResult != ToNRoundResult.R) @@ -97,11 +110,11 @@ public string GetTooltip(bool showPlayers, bool showTerrors, bool showNote = tru // sb.AppendLine("Round info: " + (RResult == ToNRoundResult.W ? "Survived" : "Died")); if (!string.IsNullOrEmpty(RType)) - sb.AppendLine("Round type: " + RType); + sb.AppendLine(TextRound + " " + RType); if (RTerrors != null && RTerrors.Length > 0) { - sb.AppendLine("Terrors in round:"); + sb.AppendLine(TextTerrors); sb.AppendJoin("- ", RTerrors); } } @@ -109,7 +122,7 @@ public string GetTooltip(bool showPlayers, bool showTerrors, bool showNote = tru { sb.AppendLine(); sb.AppendLine(); - sb.AppendLine("Players in room:"); + sb.AppendLine(TextPlayers); sb.Append(Players); } return sb.ToString(); diff --git a/Models/Objective.cs b/Models/Objective.cs index ceed9eb..b00150c 100644 --- a/Models/Objective.cs +++ b/Models/Objective.cs @@ -16,6 +16,9 @@ public LegacyObjective() internal class Objective { + [JsonIgnore] public string? DisplayName { get; set; } + [JsonIgnore] public string? Tooltip { get; set; } + [JsonIgnore] public bool IsCompleted { get => !IsSeparator && MainWindow.SaveData.GetCompleted(Name); set @@ -60,7 +63,7 @@ public static Objective Separator(string name) public override string ToString() { - return Name; + return string.IsNullOrEmpty(DisplayName) ? Name : DisplayName; } public static List ImportFromMemory() diff --git a/Models/SaveData.cs b/Models/SaveData.cs index 5a44473..44352dc 100644 --- a/Models/SaveData.cs +++ b/Models/SaveData.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.Linq; using System.Xml.Linq; +using ToNSaveManager.Localization; namespace ToNSaveManager.Models { @@ -188,7 +189,7 @@ public static void SetDataLocation(bool reset) Directory.Move(currentDatabase, currentDatabase + ".backup_" + DateTimeOffset.UtcNow.ToUnixTimeSeconds()); } catch (Exception e) { - MessageBox.Show($"An error ocurred while trying to copy your files to the selected location.\n\nMake sure that the program contains permissions to write files to the destination.\nPath: {selectedFolder}\n\n" + e, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBox.Show(LANG.S("MESSAGE.COPY_FILES_ERROR", selectedFolder) ?? $"An error ocurred while trying to copy your files to the selected location.\n\nMake sure that the program contains permissions to write files to the destination.\nPath: {selectedFolder}" + "\n\n" + e, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } @@ -196,7 +197,7 @@ public static void SetDataLocation(bool reset) Settings.Get.DataLocation = reset ? null : Destination; Settings.Export(); - if (reset) MessageBox.Show("Save data location has been reset to default.", "Reset Custom Data Location"); + if (reset) MessageBox.Show( LANG.S("MESSAGE.SAVE_LOCATION_RESET") ?? "Save data location has been reset to default.", LANG.S("MESSAGE.SAVE_LOCATION_RESET.TITLE") ?? "Reset Custom Data Location"); } public static SaveData Import() @@ -244,7 +245,7 @@ public static SaveData Import() } catch (Exception ex) { - MessageBox.Show("Error trying to import your save:\n\n" + ex, "Import Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBox.Show((LANG.S("MESSAGE.IMPORT_SAVE_ERROR") ?? "Error trying to import your save.") + "\n\n" + ex, "Import Error", MessageBoxButtons.OK, MessageBoxIcon.Error); if (!Program.CreateFileBackup(filePath)) { @@ -367,7 +368,7 @@ public void Export(bool force = false) } catch (Exception e) { - MessageBox.Show($"An error ocurred while trying to write your saves to a file.\n\nMake sure that the program contains permissions to write files to the destination.\nPath: {Destination}\n\n" + e, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBox.Show((LANG.S("MESSAGE.WRITE_SAVE_ERROR", Destination) ?? $"An error ocurred while trying to write your saves to a file.\n\nMake sure that the program contains permissions to write files to the destination.\nPath: {Destination}") + "\n\n" + e, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } diff --git a/Program.cs b/Program.cs index d1253f6..8c01253 100644 --- a/Program.cs +++ b/Program.cs @@ -3,6 +3,7 @@ using System.Drawing.Text; using System.Reflection; using System.Runtime.InteropServices; +using ToNSaveManager.Localization; using ToNSaveManager.Models; using ToNSaveManager.Utils; @@ -36,6 +37,8 @@ internal static bool CheckMutex() [STAThread] static void Main(string[] args) { + LANG.Initialize(); + UpdateWindow.RunPostUpdateCheck(args); if (CheckMutex()) @@ -62,8 +65,9 @@ static void Main(string[] args) Debug.WriteLine(ProgramDirectory); - if (!StartCheckForUpdate()) + if (!StartCheckForUpdate()) { Application.Run(new MainWindow()); + } } static readonly PrivateFontCollection FontCollection = new PrivateFontCollection(); @@ -136,7 +140,7 @@ internal static bool StartCheckForUpdate(bool showUpToDate = false) body = "\n\n" + release.body.Substring(start, end - start).Trim(); } - DialogResult result = MessageBox.Show($"A new update have been released on GitHub.\n\nWould you like to automatically download and update to the new version?" + body, "New update available", MessageBoxButtons.YesNo, MessageBoxIcon.Information); + DialogResult result = MessageBox.Show((LANG.S("MESSAGE.UPDATE_AVAILABLE") ?? "A new update have been released on GitHub.\n\nWould you like to automatically download and update to the new version?") + body, LANG.S("MESSAGE.UPDATE_AVAILABLE.TITLE") ?? "New update available", MessageBoxButtons.YesNo, MessageBoxIcon.Information); if (result == DialogResult.Yes) { UpdateWindow updateWindow = new UpdateWindow(release, asset); @@ -149,7 +153,7 @@ internal static bool StartCheckForUpdate(bool showUpToDate = false) } } else if (showUpToDate) { - MessageBox.Show($"No updates are currently available.", "No updates available", MessageBoxButtons.OK, MessageBoxIcon.Information); + MessageBox.Show(LANG.S("MESSAGE.UPDATE_UNAVAILABLE") ?? "No updates are currently available.", LANG.S("MESSAGE.UPDATE_UNAVAILABLE.TITLE") ?? "No updates available", MessageBoxButtons.OK, MessageBoxIcon.Information); } return false; diff --git a/README.md b/README.md index c852b4c..b5735b4 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,20 @@ If your parameters are not being received properly... try resetting the OSC conf > We are allowed to read these files since it does not modify or alter the game in any way. > **This is not a mod or a cheat.** +> ## How to contribute with translations? +> - Fork clone the [localization](https://github.com/ChrisFeline/ToNSaveManager/tree/localization) branch. +> - Create a copy of the `en-US.json` language file into `/Localization/Language` +> - Rename it to your local ISO language name. For example `ja-JP.json` +> - Translate the strings contained within this file into your target language. +> * Keep important string replacement tokens like: `{0}`, `{1}` or `$$MAIN.SETTINGS$$` etc... +> - Create a pull request. +> * Do **NOT** create a pull request into the `main` branch. +> * Make sure the only edited file is the new added language `.json` file, any other contribution in the source code unrelated to this translation will be rejected. +> ### OR +> - Download the file [`en-US.json`](#) from this repo. +> - Rename it to your local ISO language name. For example `ja-JP.json` +> - You can [contact me](#-contact) on discord and I'll review the changes. + > ### Please do NOT message Beyond about suggestions or problems with this tool. > You can report problems or suggestions under the [Issues](https://github.com/ChrisFeline/ToNSaveManager/issues) tab on this repo. Alternatively see contact information below. diff --git a/ToNSaveManager.csproj b/ToNSaveManager.csproj index ecc2ce3..fe5fa6a 100644 --- a/ToNSaveManager.csproj +++ b/ToNSaveManager.csproj @@ -12,6 +12,8 @@ + + @@ -24,6 +26,8 @@ + + diff --git a/Utils/Discord/DSWebHook.cs b/Utils/Discord/DSWebHook.cs index 5abba6e..4a153d7 100644 --- a/Utils/Discord/DSWebHook.cs +++ b/Utils/Discord/DSWebHook.cs @@ -7,6 +7,7 @@ namespace ToNSaveManager.Utils.Discord { using Models; + using Localization; internal class Payload { @@ -17,8 +18,22 @@ internal class Payload public Embed Embed => Embeds[0]; } - internal static class DSWebHook - { + internal static class DSWebHook { + static string LABEL_PLAYER = "**Player**: `{0}`"; + static string LABEL_ROUND = "**Round Type**: `{0}`"; + static string LABEL_TERRORS = "**Terrors in Round**: `{0}`"; + static string LABEL_TERRORS_SPLIT = "`, `"; + static string LABEL_COUNT = "**Player Count**: `{0}`"; + static string LABEL_NOTE = "**Note**: `{0}`"; + internal static void LocalizeContent() { + LABEL_PLAYER = LANG.S("SETTINGS.DISCORDWEBHOOK.LABEL_PLAYER") ?? "**Player**: `{0}`"; + LABEL_ROUND = LANG.S("SETTINGS.DISCORDWEBHOOK.LABEL_ROUND") ?? "**Round Type**: `{0}`"; + LABEL_TERRORS = LANG.S("SETTINGS.DISCORDWEBHOOK.LABEL_TERRORS") ?? "**Terrors in Round**: `{0}`"; + LABEL_TERRORS_SPLIT = LANG.S("SETTINGS.DISCORDWEBHOOK.LABEL_TERRORS_SPLIT") ?? "**Terrors in Round**: `{0}`"; + LABEL_COUNT = LANG.S("SETTINGS.DISCORDWEBHOOK.LABEL_COUNT") ?? "**Player Count**: `{0}`"; + LABEL_NOTE = LANG.S("SETTINGS.DISCORDWEBHOOK.LABEL_NOTE") ?? "**Note**: `{0}`"; + } + static Entry? LastEntry; static readonly Embed[] Embeds = new Embed[1]; @@ -79,30 +94,30 @@ private static async Task Send(string webhookUrl) if (entry.Parent != null && !string.IsNullOrEmpty(entry.Parent.DisplayName)) { // if (EmbedData.Description.Length > 0) EmbedData.Description += "\n"; - EmbedData.Description += "**Player**: `" + entry.Parent.DisplayName + "`"; + EmbedData.Description += string.Format(LABEL_PLAYER, entry.Parent.DisplayName); } if (!string.IsNullOrEmpty(entry.RType)) { if (EmbedData.Description.Length > 0) EmbedData.Description += "\n"; - EmbedData.Description += "**Round Type**: `" + entry.RType + "`"; + EmbedData.Description += string.Format(LABEL_ROUND, entry.RType); } if (entry.RTerrors != null && entry.RTerrors.Length > 0) { if (EmbedData.Description.Length > 0) EmbedData.Description += "\n"; - EmbedData.Description += "**Terrors in Round**: `" + string.Join("`, `", entry.RTerrors) + "`"; + EmbedData.Description += string.Format(LABEL_TERRORS, string.Join(LABEL_TERRORS_SPLIT, entry.RTerrors)); } if (entry.PlayerCount > 0) { if (EmbedData.Description.Length > 0) EmbedData.Description += "\n"; - EmbedData.Description += $"**Player Count**: `{entry.PlayerCount}`"; + EmbedData.Description += string.Format(LABEL_COUNT, entry.PlayerCount); } if (ignoreDuplicate && !string.IsNullOrEmpty(entry.Note)) { if (EmbedData.Description.Length > 0) EmbedData.Description += "\n"; - EmbedData.Description += $"**Note**: `{entry.Note.Replace('`', '\'')}`"; + EmbedData.Description += string.Format(LABEL_NOTE, entry.Note.Replace('`', '\'')); } string payloadData = JsonConvert.SerializeObject(PayloadData, JsonSettings); diff --git a/Windows/EditWindow.cs b/Windows/EditWindow.cs index 250b939..58caa97 100644 --- a/Windows/EditWindow.cs +++ b/Windows/EditWindow.cs @@ -1,4 +1,6 @@ -namespace ToNSaveManager +using ToNSaveManager.Localization; + +namespace ToNSaveManager { public struct EditResult { @@ -8,7 +10,7 @@ public struct EditResult public partial class EditWindow : Form { - static EditWindow Instance = new EditWindow(); + internal static EditWindow Instance = new EditWindow(); public static Size GetSize() => Instance.Size; string Content @@ -53,6 +55,12 @@ private void EditWindow_Shown(object sender, EventArgs e) { // Focus the text input please!!! Instance.textBox1.Focus(); + LocalizeContent(); + } + + internal void LocalizeContent() { + button1.Text = LANG.S("EDIT.SAVE"); + button2.Text = LANG.S("EDIT.CANCEL"); } } } diff --git a/Windows/MainWindow.Designer.cs b/Windows/MainWindow.Designer.cs index 5500cd2..43c8fd6 100644 --- a/Windows/MainWindow.Designer.cs +++ b/Windows/MainWindow.Designer.cs @@ -26,8 +26,7 @@ protected override void Dispose(bool disposing) /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// - private void InitializeComponent() - { + private void InitializeComponent() { components = new System.ComponentModel.Container(); System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainWindow)); listBoxKeys = new ListBox(); @@ -45,8 +44,8 @@ private void InitializeComponent() toolStripMenuItem1 = new ToolStripSeparator(); deleteToolStripMenuItem = new ToolStripMenuItem(); btnSettings = new Button(); - button1 = new Button(); - button2 = new Button(); + btnObjectives = new Button(); + linkWiki = new Button(); splitContainer1 = new SplitContainer(); linkSupport = new Button(); ctxMenuEntries.SuspendLayout(); @@ -102,7 +101,7 @@ private void InitializeComponent() // ctxMenuEntries.Items.AddRange(new ToolStripItem[] { ctxMenuEntriesCopyTo, ctxMenuEntriesNote, ctxMenuEntriesBackup, toolStripMenuItem2, ctxMenuEntriesDelete }); ctxMenuEntries.Name = "ctxMenuEntries"; - ctxMenuEntries.Size = new Size(181, 120); + ctxMenuEntries.Size = new Size(124, 98); ctxMenuEntries.Closed += ctxMenuEntries_Closed; ctxMenuEntries.Opened += ctxMenuEntries_Opened; // @@ -110,7 +109,7 @@ private void InitializeComponent() // ctxMenuEntriesCopyTo.DropDownItems.AddRange(new ToolStripItem[] { ctxMenuEntriesNew }); ctxMenuEntriesCopyTo.Name = "ctxMenuEntriesCopyTo"; - ctxMenuEntriesCopyTo.Size = new Size(180, 22); + ctxMenuEntriesCopyTo.Size = new Size(123, 22); ctxMenuEntriesCopyTo.Text = "Add to"; // // ctxMenuEntriesNew @@ -124,7 +123,7 @@ private void InitializeComponent() // ctxMenuEntriesNote // ctxMenuEntriesNote.Name = "ctxMenuEntriesNote"; - ctxMenuEntriesNote.Size = new Size(180, 22); + ctxMenuEntriesNote.Size = new Size(123, 22); ctxMenuEntriesNote.Text = "Edit Note"; ctxMenuEntriesNote.Click += ctxMenuEntriesNote_Click; // @@ -132,7 +131,7 @@ private void InitializeComponent() // ctxMenuEntriesBackup.Enabled = false; ctxMenuEntriesBackup.Name = "ctxMenuEntriesBackup"; - ctxMenuEntriesBackup.Size = new Size(180, 22); + ctxMenuEntriesBackup.Size = new Size(123, 22); ctxMenuEntriesBackup.Text = "Backup"; ctxMenuEntriesBackup.ToolTipText = "Force upload a backup of this code to Discord as a file, requires Auto Discord Backup to be enabled in settings."; ctxMenuEntriesBackup.Click += ctxMenuEntriesBackup_Click; @@ -140,12 +139,12 @@ private void InitializeComponent() // toolStripMenuItem2 // toolStripMenuItem2.Name = "toolStripMenuItem2"; - toolStripMenuItem2.Size = new Size(177, 6); + toolStripMenuItem2.Size = new Size(120, 6); // // ctxMenuEntriesDelete // ctxMenuEntriesDelete.Name = "ctxMenuEntriesDelete"; - ctxMenuEntriesDelete.Size = new Size(180, 22); + ctxMenuEntriesDelete.Size = new Size(123, 22); ctxMenuEntriesDelete.Text = "Delete"; ctxMenuEntriesDelete.Click += ctxMenuEntriesDelete_Click; // @@ -197,37 +196,37 @@ private void InitializeComponent() btnSettings.UseVisualStyleBackColor = false; btnSettings.Click += btnSettings_Click; // - // button1 - // - button1.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; - button1.BackColor = Color.FromArgb(46, 52, 64); - button1.FlatAppearance.BorderColor = Color.FromArgb(122, 122, 122); - button1.FlatStyle = FlatStyle.Flat; - button1.ForeColor = Color.White; - button1.Location = new Point(0, 223); - button1.Name = "button1"; - button1.Size = new Size(229, 24); - button1.TabIndex = 0; - button1.TabStop = false; - button1.Text = "Objectives"; - button1.UseVisualStyleBackColor = false; - button1.Click += btnObjectives_Click; - // - // button2 - // - button2.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; - button2.BackColor = Color.FromArgb(46, 52, 64); - button2.FlatAppearance.BorderColor = Color.FromArgb(122, 122, 122); - button2.FlatStyle = FlatStyle.Flat; - button2.ForeColor = Color.White; - button2.Location = new Point(235, 223); - button2.Name = "button2"; - button2.Size = new Size(58, 24); - button2.TabIndex = 3; - button2.TabStop = false; - button2.Text = "Wiki"; - button2.UseVisualStyleBackColor = false; - button2.Click += linkWiki_Clicked; + // btnObjectives + // + btnObjectives.Anchor = AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; + btnObjectives.BackColor = Color.FromArgb(46, 52, 64); + btnObjectives.FlatAppearance.BorderColor = Color.FromArgb(122, 122, 122); + btnObjectives.FlatStyle = FlatStyle.Flat; + btnObjectives.ForeColor = Color.White; + btnObjectives.Location = new Point(0, 223); + btnObjectives.Name = "btnObjectives"; + btnObjectives.Size = new Size(229, 24); + btnObjectives.TabIndex = 0; + btnObjectives.TabStop = false; + btnObjectives.Text = "Objectives"; + btnObjectives.UseVisualStyleBackColor = false; + btnObjectives.Click += btnObjectives_Click; + // + // linkWiki + // + linkWiki.Anchor = AnchorStyles.Bottom | AnchorStyles.Right; + linkWiki.BackColor = Color.FromArgb(46, 52, 64); + linkWiki.FlatAppearance.BorderColor = Color.FromArgb(122, 122, 122); + linkWiki.FlatStyle = FlatStyle.Flat; + linkWiki.ForeColor = Color.White; + linkWiki.Location = new Point(235, 223); + linkWiki.Name = "linkWiki"; + linkWiki.Size = new Size(58, 24); + linkWiki.TabIndex = 3; + linkWiki.TabStop = false; + linkWiki.Text = "Wiki"; + linkWiki.UseVisualStyleBackColor = false; + linkWiki.Click += linkWiki_Clicked; // // splitContainer1 // @@ -244,8 +243,8 @@ private void InitializeComponent() // splitContainer1.Panel2.Controls.Add(linkSupport); splitContainer1.Panel2.Controls.Add(listBoxEntries); - splitContainer1.Panel2.Controls.Add(button1); - splitContainer1.Panel2.Controls.Add(button2); + splitContainer1.Panel2.Controls.Add(btnObjectives); + splitContainer1.Panel2.Controls.Add(linkWiki); splitContainer1.Size = new Size(505, 247); splitContainer1.SplitterDistance = 178; splitContainer1.TabIndex = 0; @@ -308,8 +307,8 @@ private void InitializeComponent() private ToolStripMenuItem ctxMenuEntriesDelete; private ToolStripMenuItem importToolStripMenuItem; private Button btnSettings; - private Button button1; - private Button button2; + private Button btnObjectives; + private Button linkWiki; private SplitContainer splitContainer1; private ToolStripMenuItem ctxMenuEntriesBackup; private Button linkSupport; diff --git a/Windows/MainWindow.cs b/Windows/MainWindow.cs index 6713ec2..7abde66 100644 --- a/Windows/MainWindow.cs +++ b/Windows/MainWindow.cs @@ -8,6 +8,7 @@ using OnLineArgs = ToNSaveManager.Utils.LogWatcher.OnLineArgs; using LogContext = ToNSaveManager.Utils.LogWatcher.LogContext; using ToNSaveManager.Utils.Discord; +using ToNSaveManager.Localization; namespace ToNSaveManager { @@ -68,6 +69,8 @@ private void mainWindow_Loaded(object sender, EventArgs e) private void mainWindow_Shown(object sender, EventArgs e) { + LocalizeContent(); + if (Started) return; FirstImport(); @@ -81,6 +84,26 @@ private void mainWindow_Shown(object sender, EventArgs e) LilOSC.SendData(true); } + + internal void LocalizeContent() { + LANG.C(btnSettings, "MAIN.SETTINGS"); + LANG.C(btnObjectives, "MAIN.OBJECTIVES"); + LANG.C(linkWiki, "MAIN.WIKI"); + LANG.C(linkSupport, "MAIN.SUPPORT"); + + LANG.C(importToolStripMenuItem, "MAIN.CTX_IMPORT"); // .TITLE + LANG.C(renameToolStripMenuItem, "MAIN.CTX_RENAME"); // .TITLE + LANG.C(deleteToolStripMenuItem, "MAIN.CTX_DELETE"); // .TITLE + + LANG.C(ctxMenuEntriesCopyTo, "MAIN.CTX_ADD_TO"); + LANG.C(ctxMenuEntriesNew, "MAIN.CTX_ADD_TO.NEW"); + LANG.C(ctxMenuEntriesNote, "MAIN.CTX_EDIT_NOTE"); + LANG.C(ctxMenuEntriesBackup, "MAIN.CTX_BACKUP"); + LANG.C(ctxMenuEntriesDelete, "MAIN.CTX_DELETE"); + + Entry.LocalizeContent(); + DSWebHook.LocalizeContent(); + } #endregion #region ListBox Keys @@ -117,7 +140,7 @@ private void ctxMenuKeysImport_Click(object sender, EventArgs e) History h = (History)listBoxKeys.SelectedItem; if (!h.IsCustom) return; - EditResult edit = EditWindow.Show(string.Empty, "Import Code", this); + EditResult edit = EditWindow.Show(string.Empty, LANG.S("MAIN.CTX_IMPORT.TITLE") ?? "Import Title", this); if (edit.Accept && !string.IsNullOrWhiteSpace(edit.Text)) { string content = edit.Text.Trim(); @@ -131,7 +154,7 @@ private void ctxMenuKeysRename_Click(object sender, EventArgs e) if (listBoxKeys.SelectedItem == null) return; History h = (History)listBoxKeys.SelectedItem; - EditResult edit = EditWindow.Show(h.Name, "Set Collection Name", this); + EditResult edit = EditWindow.Show(h.Name, LANG.S("MAIN.CTX_RENAME.TITLE") ?? "Set Collection Name", this); if (edit.Accept && !string.IsNullOrWhiteSpace(edit.Text)) { string title = edit.Text.Trim(); @@ -151,7 +174,10 @@ private void ctxMenuKeysDelete_Click(object sender, EventArgs e) if (selectedIndex != -1 && listBoxKeys.SelectedItem != null) { History h = (History)listBoxKeys.SelectedItem; - DialogResult result = MessageBox.Show($"Are you SURE that you want to delete this entry?\n\nEvery code from '{h}' will be permanently deleted.\nThis operation is not reversible!", "Deleting Entry: " + h.ToString(), MessageBoxButtons.OKCancel, MessageBoxIcon.Warning); + DialogResult result = MessageBox.Show( + LANG.S("MAIN.CTX_DELETE_ALL.SUBTITLE", h.ToString()) ?? $"Are you SURE that you want to delete this entry?\n\nEvery code from '{h}' will be permanently deleted.\nThis operation is not reversible!", + LANG.S("MAIN.CTX_DELETE_ALL.TITLE", h.ToString()) ?? "Deleting Entry: " + h.ToString(), + MessageBoxButtons.OKCancel, MessageBoxIcon.Warning); if (result == DialogResult.OK) { @@ -196,7 +222,7 @@ private void listBoxEntries_MouseUp(object sender, MouseEventArgs e) { Entry entry = (Entry)listBoxEntries.SelectedItem; entry.CopyToClipboard(); - MessageBox.Show("Copied to clipboard!\n\nYou can now paste the code in game.", "Copied", MessageBoxButtons.OK, MessageBoxIcon.Information); + MessageBox.Show(LANG.S("MESSAGE.COPY_TO_CLIPBOARD") ?? "Copied to clipboard!\n\nYou can now paste the code in game.", LANG.S("MESSAGE.COPY_TO_CLIPBOARD.TITLE") ?? "Copied", MessageBoxButtons.OK, MessageBoxIcon.Information); } listBoxEntries.SelectedIndex = -1; @@ -307,7 +333,7 @@ private void ctxMenuEntriesNote_Click(object sender, EventArgs e) { if (ContextEntry != null) { - EditResult edit = EditWindow.Show(ContextEntry.Note, "Note Editor", this); + EditResult edit = EditWindow.Show(ContextEntry.Note, LANG.S("MAIN.CTX_EDIT_NOTE.TITLE") ?? "Note Editor", this); if (edit.Accept && !edit.Text.Equals(ContextEntry.Note, StringComparison.Ordinal)) { ContextEntry.Note = edit.Text.Trim(); @@ -338,7 +364,10 @@ private void ctxMenuEntriesDelete_Click(object sender, EventArgs e) History h = (History)listBoxKeys.SelectedItem; if (ContextEntry != null) { - DialogResult result = MessageBox.Show($"Are you SURE that you want to delete this entry?\n\nDate: {ContextEntry.Timestamp}\nNote: {ContextEntry.Note}\n\nThis operation is not reversible!", "Deleting Entry: " + h.ToString(), MessageBoxButtons.OKCancel, MessageBoxIcon.Warning); + DialogResult result = MessageBox.Show( + LANG.S("MAIN.CTX_DELETE_ENTRY.SUBTITLE", h.ToString()) ?? $"Are you SURE that you want to delete this entry?\n\nDate: {ContextEntry.Timestamp}\nNote: {ContextEntry.Note}\n\nThis operation is not reversible!", + LANG.S("MAIN.CTX_DELETE_ENTRY.TITLE", h.ToString()) ?? "Deleting Entry: " + h.ToString(), + MessageBoxButtons.OKCancel, MessageBoxIcon.Warning); if (result == DialogResult.OK) { @@ -443,8 +472,8 @@ internal static void SendXSNotification(bool test = false) const string message = "ToN: Save Data Stored"; const string msgtest = "ToN: Notifications Enabled"; - if (test) XSOverlay.Send(msgtest, 1); - else XSOverlay.Send(message); + if (test) XSOverlay.Send(LANG.S("SETTINGS.XSOVERLAY.TOGGLE") ?? msgtest, 1); + else XSOverlay.Send(LANG.S("SETTINGS.XSOVERLAY.MESSAGE") ?? message); } #endregion @@ -513,7 +542,6 @@ internal void SetBackupButton(bool enabled) const string KILLER_MATRIX_KEYWORD = "Killers have been set - "; const string KILLER_ROUND_TYPE_KEYWORD = " // Round type is "; - private void LogWatcher_OnLine(object? sender, OnLineArgs e) { DateTime timestamp = e.Timestamp; @@ -669,7 +697,7 @@ private void AddCustomEntry(Entry entry, History? collection) { if (collection == null) { - EditResult edit = EditWindow.Show(string.Empty, "Set Collection Name", this); + EditResult edit = EditWindow.Show(string.Empty, LANG.S("MAIN.CTX_RENAME.TITLE") ?? "Set Collection Name", this); if (edit.Accept && !string.IsNullOrWhiteSpace(edit.Text)) { string title = edit.Text.Trim(); diff --git a/Windows/MainWindow.resx b/Windows/MainWindow.resx index 7d1a689..7e2c0df 100644 --- a/Windows/MainWindow.resx +++ b/Windows/MainWindow.resx @@ -133,7 +133,7 @@ xbGEB4S7EpYt+YHZIicUryu20nnz557qhQ3L9sy00mV2MMY4k0yhYZBnlTQ5wrLaorjEZD9awd/u+6fE ZYhrFVMcI2Sw0H0/6g9+d+sm+/uKSQ1RqH32vPduCG5DYcvzvo48r3AM1U9waZf8mUMY+hB9q6R1HUDz BpxflTRjBy42oe0xqzu6L1XLDCST8HYq3zQHrTdQv1Ds7Wefk3uIS1cT17C3Dz0pyV6s8O668t7+POP3 - R/QbeWVyqQwXJNgAAAAJcEhZcwAACw0AAAsNAe0HwCwAAAAHdElNRQfoBw4QKSrn520hAAAAg0lEQVQo + R/QbeWVyqQwXJNgAAAAJcEhZcwAACwwAAAsMAT9AIsgAAAAHdElNRQfoBw4QKSrn520hAAAAg0lEQVQo U53M7QrCMAyF4V7lbLtVRfxAxtwQFMSLf03Tdm4/HLIDh5DyNGZVqN6weUH1RHfXgxukN93HRIB9CL7L FGQ78K30CvVZevx+wMqFCFwBFwU0B5k7mWGCI/DxwimBZq+AUMM2NdMUvRDmQBv8HJZMwSIsUZhxflrO 3/B3jPkAexOGRyThvYIAAAAASUVORK5CYII= diff --git a/Windows/ObjectivesWindow.Designer.cs b/Windows/ObjectivesWindow.Designer.cs index e8e9b42..bfe819b 100644 --- a/Windows/ObjectivesWindow.Designer.cs +++ b/Windows/ObjectivesWindow.Designer.cs @@ -26,9 +26,10 @@ protected override void Dispose(bool disposing) /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// - private void InitializeComponent() - { + private void InitializeComponent() { + components = new System.ComponentModel.Container(); listBox1 = new ListBox(); + toolTip = new ToolTip(components); SuspendLayout(); // // listBox1 @@ -74,5 +75,6 @@ private void InitializeComponent() #endregion private ListBox listBox1; + private ToolTip toolTip; } } \ No newline at end of file diff --git a/Windows/ObjectivesWindow.cs b/Windows/ObjectivesWindow.cs index 6534813..0e0257a 100644 --- a/Windows/ObjectivesWindow.cs +++ b/Windows/ObjectivesWindow.cs @@ -1,11 +1,13 @@ -using ToNSaveManager.Extensions; +using System.Diagnostics; +using ToNSaveManager.Extensions; +using ToNSaveManager.Localization; using ToNSaveManager.Models; namespace ToNSaveManager { public partial class ObjectivesWindow : Form { - static ObjectivesWindow? Instance; + internal static ObjectivesWindow? Instance; static List Objectives = Objective.ImportFromMemory(); public ObjectivesWindow() @@ -27,7 +29,7 @@ public static void Open(Form parent) Instance.StartPosition = FormStartPosition.Manual; Instance.Location = new Point( parent.Location.X + (parent.Width - Instance.Width) / 2, - parent.Location.Y + (parent.Height - Instance.Height) / 2 + Math.Max(parent.Location.Y + (parent.Height - Instance.Height) / 2, 0) ); Instance.Show(); // Don't parent } @@ -37,12 +39,24 @@ internal static void RefreshLists() Instance?.listBox1.Refresh(); } + internal void LocalizeContent() { + LANG.C(this, "OBJECTIVES.TITLE"); + + foreach (Objective obj in listBox1.Items) { + (string? tx, string? tt) = LANG.T("OBJECTIVES." + obj.Name.ToUpperInvariant().Replace(' ', '_')); + if (!string.IsNullOrEmpty(tx)) obj.DisplayName = tx; + if (!string.IsNullOrEmpty(tt)) obj.Tooltip = tt; + } + } + private void ObjectivesWindow_Load(object sender, EventArgs e) { listBox1.Items.Clear(); foreach (Objective ob in Objectives) listBox1.Items.Add(ob); + + LocalizeContent(); } private void listBox1_DrawItem(object sender, DrawItemEventArgs e) @@ -133,12 +147,12 @@ private void listBox1_MouseMove(object sender, MouseEventArgs e) if (index < 0) { - TooltipUtil.Set(listBox1, null); + toolTip.SetToolTip(listBox1, null); return; } Objective objective = (Objective)listBox1.Items[index]; - TooltipUtil.Set(listBox1, objective.Description); + toolTip.SetToolTip(listBox1, objective.Tooltip ?? objective.Description); } } } diff --git a/Windows/ObjectivesWindow.resx b/Windows/ObjectivesWindow.resx index a395bff..1f052d5 100644 --- a/Windows/ObjectivesWindow.resx +++ b/Windows/ObjectivesWindow.resx @@ -18,7 +18,7 @@ System.Resources.ResXResourceReader, System.Windows.Forms, ... System.Resources.ResXResourceWriter, System.Windows.Forms, ... this is my long stringthis is a comment - Blue + Blue [base64 mime encoded serialized .NET Framework object] @@ -117,4 +117,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + 17, 17 + \ No newline at end of file diff --git a/Windows/SettingsWindow.Designer.cs b/Windows/SettingsWindow.Designer.cs index e397e8e..0f2c0fb 100644 --- a/Windows/SettingsWindow.Designer.cs +++ b/Windows/SettingsWindow.Designer.cs @@ -40,7 +40,7 @@ private void InitializeComponent() { groupBoxNotifications = new GroupBox(); checkPlayAudio = new CheckBox(); checkXSOverlay = new CheckBox(); - groupBox1 = new GroupBox(); + groupBoxTime = new GroupBox(); checkShowDate = new CheckBox(); checkInvertMD = new CheckBox(); checkShowSeconds = new CheckBox(); @@ -52,13 +52,14 @@ private void InitializeComponent() { ctxItemPickFolder = new ToolStripMenuItem(); ctxItemResetToDefault = new ToolStripMenuItem(); toolTip = new ToolTip(components); - groupBox2 = new GroupBox(); + groupBoxStyle = new GroupBox(); checkColorObjectives = new CheckBox(); + languageSelectBox = new ComboBox(); groupBoxGeneral.SuspendLayout(); groupBoxNotifications.SuspendLayout(); - groupBox1.SuspendLayout(); + groupBoxTime.SuspendLayout(); ctxData.SuspendLayout(); - groupBox2.SuspendLayout(); + groupBoxStyle.SuspendLayout(); SuspendLayout(); // // groupBoxGeneral @@ -75,7 +76,7 @@ private void InitializeComponent() { groupBoxGeneral.Controls.Add(checkSkipParsedLogs); groupBoxGeneral.Dock = DockStyle.Top; groupBoxGeneral.ForeColor = Color.White; - groupBoxGeneral.Location = new Point(8, 8); + groupBoxGeneral.Location = new Point(8, 31); groupBoxGeneral.Name = "groupBoxGeneral"; groupBoxGeneral.Size = new Size(268, 166); groupBoxGeneral.TabIndex = 0; @@ -189,7 +190,7 @@ private void InitializeComponent() { groupBoxNotifications.Controls.Add(checkXSOverlay); groupBoxNotifications.Dock = DockStyle.Top; groupBoxNotifications.ForeColor = Color.White; - groupBoxNotifications.Location = new Point(8, 174); + groupBoxNotifications.Location = new Point(8, 197); groupBoxNotifications.Name = "groupBoxNotifications"; groupBoxNotifications.Size = new Size(268, 58); groupBoxNotifications.TabIndex = 2; @@ -223,22 +224,22 @@ private void InitializeComponent() { checkXSOverlay.Text = "XSOverlay Popup"; checkXSOverlay.UseVisualStyleBackColor = true; // - // groupBox1 - // - groupBox1.AutoSize = true; - groupBox1.AutoSizeMode = AutoSizeMode.GrowAndShrink; - groupBox1.Controls.Add(checkShowDate); - groupBox1.Controls.Add(checkInvertMD); - groupBox1.Controls.Add(checkShowSeconds); - groupBox1.Controls.Add(check24Hour); - groupBox1.Dock = DockStyle.Top; - groupBox1.ForeColor = Color.White; - groupBox1.Location = new Point(8, 232); - groupBox1.Name = "groupBox1"; - groupBox1.Size = new Size(268, 94); - groupBox1.TabIndex = 3; - groupBox1.TabStop = false; - groupBox1.Text = "Time Formatting"; + // groupBoxTime + // + groupBoxTime.AutoSize = true; + groupBoxTime.AutoSizeMode = AutoSizeMode.GrowAndShrink; + groupBoxTime.Controls.Add(checkShowDate); + groupBoxTime.Controls.Add(checkInvertMD); + groupBoxTime.Controls.Add(checkShowSeconds); + groupBoxTime.Controls.Add(check24Hour); + groupBoxTime.Dock = DockStyle.Top; + groupBoxTime.ForeColor = Color.White; + groupBoxTime.Location = new Point(8, 255); + groupBoxTime.Name = "groupBoxTime"; + groupBoxTime.Size = new Size(268, 94); + groupBoxTime.TabIndex = 3; + groupBoxTime.TabStop = false; + groupBoxTime.Text = "Time Formatting"; // // checkShowDate // @@ -294,7 +295,7 @@ private void InitializeComponent() { btnCheckForUpdates.FlatAppearance.BorderColor = Color.FromArgb(122, 122, 122); btnCheckForUpdates.FlatStyle = FlatStyle.Flat; btnCheckForUpdates.ForeColor = Color.White; - btnCheckForUpdates.Location = new Point(8, 373); + btnCheckForUpdates.Location = new Point(8, 393); btnCheckForUpdates.Name = "btnCheckForUpdates"; btnCheckForUpdates.Size = new Size(209, 24); btnCheckForUpdates.TabIndex = 4; @@ -309,7 +310,7 @@ private void InitializeComponent() { btnOpenData.FlatAppearance.BorderColor = Color.FromArgb(122, 122, 122); btnOpenData.FlatStyle = FlatStyle.Flat; btnOpenData.ForeColor = Color.White; - btnOpenData.Location = new Point(223, 373); + btnOpenData.Location = new Point(223, 393); btnOpenData.Name = "btnOpenData"; btnOpenData.Size = new Size(53, 24); btnOpenData.TabIndex = 5; @@ -351,19 +352,19 @@ private void InitializeComponent() { toolTip.InitialDelay = 500; toolTip.ReshowDelay = 100; // - // groupBox2 + // groupBoxStyle // - groupBox2.AutoSize = true; - groupBox2.AutoSizeMode = AutoSizeMode.GrowAndShrink; - groupBox2.Controls.Add(checkColorObjectives); - groupBox2.Dock = DockStyle.Top; - groupBox2.ForeColor = Color.White; - groupBox2.Location = new Point(8, 326); - groupBox2.Name = "groupBox2"; - groupBox2.Size = new Size(268, 40); - groupBox2.TabIndex = 6; - groupBox2.TabStop = false; - groupBox2.Text = "Style"; + groupBoxStyle.AutoSize = true; + groupBoxStyle.AutoSizeMode = AutoSizeMode.GrowAndShrink; + groupBoxStyle.Controls.Add(checkColorObjectives); + groupBoxStyle.Dock = DockStyle.Top; + groupBoxStyle.ForeColor = Color.White; + groupBoxStyle.Location = new Point(8, 349); + groupBoxStyle.Name = "groupBoxStyle"; + groupBoxStyle.Size = new Size(268, 40); + groupBoxStyle.TabIndex = 6; + groupBoxStyle.TabStop = false; + groupBoxStyle.Text = "Style"; // // checkColorObjectives // @@ -377,6 +378,20 @@ private void InitializeComponent() { checkColorObjectives.Text = "Colorful Objectives"; checkColorObjectives.UseVisualStyleBackColor = true; // + // languageSelectBox + // + languageSelectBox.BackColor = SystemColors.Window; + languageSelectBox.Dock = DockStyle.Top; + languageSelectBox.DropDownStyle = ComboBoxStyle.DropDownList; + languageSelectBox.FlatStyle = FlatStyle.Flat; + languageSelectBox.FormattingEnabled = true; + languageSelectBox.Location = new Point(8, 8); + languageSelectBox.Name = "languageSelectBox"; + languageSelectBox.Size = new Size(268, 23); + languageSelectBox.TabIndex = 7; + languageSelectBox.TabStop = false; + languageSelectBox.SelectedIndexChanged += languageSelectBox_SelectedIndexChanged; + // // SettingsWindow // AutoScaleDimensions = new SizeF(7F, 15F); @@ -384,13 +399,14 @@ private void InitializeComponent() { AutoSize = true; AutoSizeMode = AutoSizeMode.GrowAndShrink; BackColor = Color.FromArgb(46, 52, 64); - ClientSize = new Size(284, 405); - Controls.Add(groupBox2); + ClientSize = new Size(284, 425); + Controls.Add(groupBoxStyle); Controls.Add(btnOpenData); Controls.Add(btnCheckForUpdates); - Controls.Add(groupBox1); + Controls.Add(groupBoxTime); Controls.Add(groupBoxNotifications); Controls.Add(groupBoxGeneral); + Controls.Add(languageSelectBox); ForeColor = Color.White; FormBorderStyle = FormBorderStyle.FixedSingle; MaximizeBox = false; @@ -406,9 +422,9 @@ private void InitializeComponent() { Load += SettingsWindow_Load; groupBoxGeneral.ResumeLayout(false); groupBoxNotifications.ResumeLayout(false); - groupBox1.ResumeLayout(false); + groupBoxTime.ResumeLayout(false); ctxData.ResumeLayout(false); - groupBox2.ResumeLayout(false); + groupBoxStyle.ResumeLayout(false); ResumeLayout(false); PerformLayout(); } @@ -421,13 +437,13 @@ private void InitializeComponent() { private GroupBox groupBoxNotifications; private CheckBox checkPlayAudio; private CheckBox checkXSOverlay; - private GroupBox groupBox1; + private GroupBox groupBoxTime; private CheckBox checkInvertMD; private CheckBox checkShowSeconds; private CheckBox check24Hour; private Button btnOpenData; private ToolTip toolTip; - private GroupBox groupBox2; + private GroupBox groupBoxStyle; private CheckBox checkColorObjectives; private CheckBox checkSaveTerrorsNote; private CheckBox checkSaveTerrors; @@ -440,5 +456,6 @@ private void InitializeComponent() { private ToolStripMenuItem ctxItemResetToDefault; private CheckBox checkDiscordBackup; private CheckBox checkOSCEnabled; + private ComboBox languageSelectBox; } } \ No newline at end of file diff --git a/Windows/SettingsWindow.cs b/Windows/SettingsWindow.cs index cbc3d19..2280418 100644 --- a/Windows/SettingsWindow.cs +++ b/Windows/SettingsWindow.cs @@ -1,5 +1,6 @@ using System.Diagnostics; using ToNSaveManager.Extensions; +using ToNSaveManager.Localization; using ToNSaveManager.Models; using ToNSaveManager.Utils; using ToNSaveManager.Utils.Discord; @@ -9,7 +10,7 @@ namespace ToNSaveManager.Windows { public partial class SettingsWindow : Form { #region Initialization - static SettingsWindow? Instance; + internal static SettingsWindow? Instance; readonly Timer ClickTimer = new Timer() { Interval = 200 }; readonly Stopwatch Stopwatch = new Stopwatch(); @@ -30,16 +31,45 @@ public static void Open(Form parent) { Instance.StartPosition = FormStartPosition.Manual; Instance.Location = new Point( parent.Location.X + (parent.Width - Instance.Width) / 2, - parent.Location.Y + (parent.Height - Instance.Height) / 2 + Math.Max(parent.Location.Y + (parent.Height - Instance.Height) / 2, 0) ); Instance.Show(parent); } #endregion #region Form Events + private Dictionary LocalizedControlCache = new Dictionary(); + + internal void LocalizeContent() { + LANG.C(this, "MAIN.SETTINGS"); + + foreach (KeyValuePair pair in LocalizedControlCache) { + LANG.C(pair.Value, pair.Key, toolTip); + if (pair.Key == "SETTINGS.PLAYAUDIO") PostAudioLocationSet(); + } + + LANG.C(groupBoxGeneral, "SETTINGS.GROUP.GENERAL", toolTip); + LANG.C(groupBoxNotifications, "SETTINGS.GROUP.NOTIFICATIONS", toolTip); + LANG.C(groupBoxTime, "SETTINGS.GROUP.TIME_FORMAT", toolTip); + LANG.C(groupBoxStyle, "SETTINGS.GROUP.STYLE", toolTip); + + LANG.C(btnCheckForUpdates, "SETTINGS.CHECK_UPDATE", toolTip); + LANG.C(btnOpenData, "SETTINGS.OPEN_DATA_BTN", toolTip); + + LANG.C(setDataLocationToolStripMenuItem, "SETTINGS.CUSTOM_DATA_FOLDER"); + LANG.C(ctxItemPickFolder, "SETTINGS.CUSTOM_DATA_PICK_FOLDER"); + LANG.C(ctxItemResetToDefault, "SETTINGS.CUSTOM_DATA_RESET_DEFAULT"); + + string? versionString = Program.GetVersion()?.ToString(); + if (!string.IsNullOrEmpty(versionString)) + toolTip.SetToolTip(btnCheckForUpdates, LANG.S("SETTINGS.VERSION", versionString) ?? $"Current Version {versionString}"); + } + // Subscribe to events on load private void SettingsWindow_Load(object sender, EventArgs e) { BindControlsRecursive(Controls); + LocalizeContent(); + // Custom audio handling PostAudioLocationSet(); checkPlayAudio.CheckedChanged += CheckPlayAudio_CheckedChanged; @@ -52,9 +82,6 @@ private void SettingsWindow_Load(object sender, EventArgs e) { // Refresh list when style is changed checkColorObjectives.CheckedChanged += CheckColorObjectives_CheckedChanged; - // Tooltips - toolTip.SetToolTip(btnCheckForUpdates, "Current Version: " + Program.GetVersion()); - // Round info checkShowWinLose.CheckedChanged += TimeFormat_CheckedChanged; checkSaveTerrors.CheckedChanged += checkSaveTerrors_CheckedChanged; @@ -65,6 +92,8 @@ private void SettingsWindow_Load(object sender, EventArgs e) { // OSC checkOSCEnabled.CheckedChanged += checkOSCEnabled_CheckedChanged; + + FillLanguageBox(); } private void SettingsWindow_FormClosed(object sender, FormClosedEventArgs e) { @@ -90,7 +119,7 @@ private void checkSaveTerrors_CheckedChanged(object? sender, EventArgs e) { private void CheckDiscordBackup_CheckedChanged(object? sender, EventArgs e) { if (checkDiscordBackup.Checked) { string url = Settings.Get.DiscordWebhookURL ?? string.Empty; - EditResult edit = EditWindow.Show(Settings.Get.DiscordWebhookURL ?? string.Empty, "Discord Webhook URL", this); + EditResult edit = EditWindow.Show(Settings.Get.DiscordWebhookURL ?? string.Empty, LANG.S("SETTINGS.DISCORDWEBHOOK.TITLE") ?? "Discord Webhook URL", this); if (edit.Accept && !edit.Text.Equals(url, StringComparison.Ordinal)) { url = edit.Text.Trim(); @@ -101,7 +130,7 @@ private void CheckDiscordBackup_CheckedChanged(object? sender, EventArgs e) { Settings.Get.DiscordWebhookURL = url; Settings.Export(); } else { - MessageBox.Show($"The URL your provided does not match a discord webhook url.\n\nMake sure you created your webhook and copied the url correctly.", "Invalid Webhook URL", MessageBoxButtons.OK, MessageBoxIcon.Error); + MessageBox.Show(LANG.S("SETTINGS.DISCORDWEBHOOKINVALID") ?? "The URL your provided does not match a discord webhook url.\n\nMake sure you created your webhook and copied the url correctly.", LANG.S("SETTINGS.DISCORDWEBHOOKINVALID.TITLE") ?? "Invalid Webhook URL", MessageBoxButtons.OK, MessageBoxIcon.Error); } } else { Settings.Get.DiscordWebhookURL = null; @@ -164,8 +193,8 @@ private void checkPlayAudio_MouseUp(object sender, MouseEventArgs e) { using (OpenFileDialog fileDialog = new OpenFileDialog()) { fileDialog.InitialDirectory = "./"; - fileDialog.Title = "Select Custom Sound"; - fileDialog.Filter = "Waveform Audio (*.wav)|*.wav"; + fileDialog.Title = LANG.S("SETTINGS.PLAYAUDIO.TITLE") ?? "Select Custom Sound"; + fileDialog.Filter = "Waveform (*.wav)|*.wav"; if (fileDialog.ShowDialog() == DialogResult.OK && !string.IsNullOrEmpty(fileDialog.FileName)) { Settings.Get.AudioLocation = fileDialog.FileName; @@ -181,7 +210,7 @@ private void checkOSCEnabled_CheckedChanged(object? sender, EventArgs e) { private void checkOSCEnabled_MouseUp(object sender, MouseEventArgs e) { if (e.Button == MouseButtons.Right) - MainWindow.OpenExternalLink("https://github.com/ChrisFeline/ToNSaveManager/?tab=readme-ov-file#osc-documentation"); + MainWindow.OpenExternalLink("https://github.com/ChrisFeline/ToNSaveManager/?tab=readme-ov-file#osc-documentation"); } private void ctxItemPickFolder_Click(object sender, EventArgs e) { @@ -207,6 +236,30 @@ private void ClickTimer_Tick(object? sender, EventArgs e) { TogglePlayAudio(); } } + + private void languageSelectBox_SelectedIndexChanged(object sender, EventArgs e) { + if (!FilledLanguages || languageSelectBox.SelectedIndex < 0) return; + LANG.LangKey langKey = (LANG.LangKey)languageSelectBox.SelectedItem; + + if (LANG.SelectedKey != langKey.Key) { + Debug.WriteLine("Changing language to: " + langKey); + LANG.Select(langKey.Key); + LANG.ReloadAll(); + Settings.Get.SelectedLanguage = langKey.Key; + Settings.Export(); + } + } + + private bool FilledLanguages; + private void FillLanguageBox() { + FilledLanguages = false; + foreach (var lang in LANG.AvailableLang) { + int index = languageSelectBox.Items.Count; + languageSelectBox.Items.Add(lang); + if (lang.Key == LANG.SelectedKey) languageSelectBox.SelectedIndex = index; + } + FilledLanguages = true; + } #endregion #region Utils @@ -222,7 +275,7 @@ private void BindControlsRecursive(Control.ControlCollection controls) { if (index > -1) { string tooltip = tag.Substring(index + 1).Replace("\\n", Environment.NewLine, StringComparison.Ordinal); tag = tag.Substring(0, index); - toolTip.SetToolTip(c, tooltip); + // toolTip.SetToolTip(c, tooltip); } } @@ -231,8 +284,10 @@ private void BindControlsRecursive(Control.ControlCollection controls) { BindControlsRecursive(g.Controls); break; case CheckBox b: - if (!string.IsNullOrEmpty(tag)) + if (!string.IsNullOrEmpty(tag)) { + LocalizedControlCache.Add("SETTINGS." + tag.ToUpperInvariant(), c); b.BindSettings(tag); + } break; default: break; } @@ -241,36 +296,9 @@ private void BindControlsRecursive(Control.ControlCollection controls) { private void PostAudioLocationSet() { bool hasLocation = string.IsNullOrEmpty(Settings.Get.AudioLocation); - checkPlayAudio.Text = "Play Audio (" + (hasLocation ? "default.wav" : Path.GetFileName(Settings.Get.AudioLocation)) + ")"; - } - - /* - private void WriteInstanceLogs() - { - if (!Settings.Get.RecordInstanceLogs) return; - - var logContext = MainWindow.LogWatcher.GetEarliestContext(); - if (logContext == null) return; - - string logs = logContext.GetRoomLogs(); - string destination = "debug"; - if (!Directory.Exists(destination)) - Directory.CreateDirectory(destination); - - string filePath = Path.Combine(destination, "output_logs_instance.log"); - File.WriteAllText(filePath, logs); - - logs = logContext.GetRoomExceptions(); - if (logs.Length > 0) - { - filePath = Path.Combine(destination, "output_log_exceptions.log"); - File.WriteAllText(filePath, logs); - } - - filePath = Path.GetFullPath(destination); - MainWindow.OpenExternalLink(filePath); + string? name = (hasLocation ? "default.wav" : (Path.GetFileName(Settings.Get.AudioLocation) ?? "custom.wav")); + checkPlayAudio.Text = LANG.S("SETTINGS.PLAYAUDIO", name) ?? $"Play Audio ({name})"; } - */ #endregion } }