-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #39 from ChrisFeline/dev
Add Localization
- Loading branch information
Showing
21 changed files
with
858 additions
and
171 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<string, Dictionary<string, string>> LanguageData = new Dictionary<string, Dictionary<string, string>>(); | ||
|
||
static Dictionary<string, string> SelectedLang = new Dictionary<string, string>(); | ||
internal static string SelectedKey { get; private set; } = PREF_DEFAULT_KEY; | ||
|
||
static Dictionary<string, string> SelectedDefault = new Dictionary<string, string>(); | ||
static string SelectedDefaultKey = string.Empty; | ||
|
||
internal static List<LangKey> AvailableLang { get; private set; } = new List<LangKey>(); | ||
static readonly Regex ReplacePattern = new Regex(@"\$\$(?<key>.*?)\$\$", 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<Dictionary<string, string>>(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."); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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": "<color=#ff9999><b>ToN</b></color><color=grey>:</color> <color=#adff2f>Save Data Stored</color>", | ||
"SETTINGS.XSOVERLAY.TOGGLE": "<color=#ff9999><b>ToN</b></color><color=grey>:</color> <color=#adff2f>Notifications Enabled</color>", | ||
|
||
"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." | ||
} |
Oops, something went wrong.