From eda3a2b80921632896dc7507da1dae00451c0680 Mon Sep 17 00:00:00 2001 From: Eric Myllyoja Date: Wed, 26 Jan 2022 00:41:14 -0500 Subject: [PATCH] Added translation via FireTongue. Fixed symbols in AtlasText. --- .gitignore | 4 ++ Project.xml | 3 +- source/Alphabet.hx | 1 + source/Main.hx | 13 ++-- source/MainMenuState.hx | 7 +- source/i18n/FireTongueHandler.hx | 114 +++++++++++++++++++++++++++++++ source/ui/AtlasText.hx | 30 +++++--- source/ui/OptionsState.hx | 17 ++--- 8 files changed, 159 insertions(+), 30 deletions(-) create mode 100644 source/i18n/FireTongueHandler.hx diff --git a/.gitignore b/.gitignore index b15789ba4..9f24f450e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,7 @@ export/ .vscode/ APIStuff.hx .DS_STORE + +example_mods/enaSkin/ + +example_mods/tricky/ diff --git a/Project.xml b/Project.xml index 8208dc649..03ebb7c61 100644 --- a/Project.xml +++ b/Project.xml @@ -71,7 +71,7 @@ - + @@ -121,6 +121,7 @@ + diff --git a/source/Alphabet.hx b/source/Alphabet.hx index 150b4c78c..5caaccbf4 100644 --- a/source/Alphabet.hx +++ b/source/Alphabet.hx @@ -11,6 +11,7 @@ using StringTools; /** * Loosley based on FlxTypeText lolol */ +@:deprecated("Use ui.AtlasText instead") class Alphabet extends FlxSpriteGroup { public var delay:Float = 0.05; diff --git a/source/Main.hx b/source/Main.hx index fa3a0d43b..2d0120387 100644 --- a/source/Main.hx +++ b/source/Main.hx @@ -1,19 +1,12 @@ package; -import modding.PolymodHandler; import flixel.FlxGame; import flixel.FlxState; -import flixel.util.FlxColor; -import openfl.Assets; import openfl.Lib; import openfl.display.FPS; import openfl.display.Sprite; -import openfl.events.AsyncErrorEvent; import openfl.events.Event; -import openfl.events.MouseEvent; -import openfl.events.NetStatusEvent; import openfl.media.Video; -import openfl.net.NetConnection; import openfl.net.NetStream; class Main extends Sprite @@ -25,8 +18,8 @@ class Main extends Sprite #if web var framerate:Int = 60; // How many frames per second the game should run at. #else + // TODO: This should probably be in the options menu? var framerate:Int = 300; // How many frames per second the game should run at. - #end var skipSplash:Bool = true; // Whether to skip the flixel splash screen that appears in release mode. var startFullscreen:Bool = false; // Whether to start the game in fullscreen on desktop targets @@ -49,7 +42,9 @@ class Main extends Sprite // A design similar to that of Minecraft resource packs would be intuitive. // 3. The interface should save (to the save file) and output an ordered array of mod IDs. // 4. Replace the call to PolymodHandler.loadAllMods() with a call to PolymodHandler.loadModsById(ids:Array). - PolymodHandler.loadAllMods(); + modding.PolymodHandler.loadAllMods(); + + i18n.FireTongueHandler.init(); if (stage != null) { diff --git a/source/MainMenuState.hx b/source/MainMenuState.hx index a2b788f7c..5a7dc505a 100644 --- a/source/MainMenuState.hx +++ b/source/MainMenuState.hx @@ -138,13 +138,14 @@ class MainMenuState extends MusicBeatState FlxG.camera.follow(camFollow, null, 0.06); // FlxG.camera.setScrollBounds(bg.x, bg.x + bg.width, bg.y, bg.y + bg.height * 1.2); - var versionShit:FlxText = new FlxText(5, FlxG.height - 18, 0, "v" + Application.current.meta.get('version'), 12); + var versionStr = 'v${Application.current.meta.get('version')}'; + versionStr += ' (secret week 8 build do not leak)'; + + var versionShit:FlxText = new FlxText(5, FlxG.height - 18, 0, versionStr, 12); versionShit.scrollFactor.set(); versionShit.setFormat("VCR OSD Mono", 16, FlxColor.WHITE, LEFT, FlxTextBorderStyle.OUTLINE, FlxColor.BLACK); add(versionShit); - versionShit.text += '(Newgrounds exclusive preview)'; - // NG.core.calls.event.logEvent('swag').send(); super.create(); diff --git a/source/i18n/FireTongueHandler.hx b/source/i18n/FireTongueHandler.hx new file mode 100644 index 000000000..53a16059d --- /dev/null +++ b/source/i18n/FireTongueHandler.hx @@ -0,0 +1,114 @@ +package i18n; + +import firetongue.FireTongue; + +class FireTongueHandler +{ + static final DEFAULT_LOCALE = 'en-US'; + // static final DEFAULT_LOCALE = 'pt-BR'; + static final LOCALE_DIR = 'assets/locales/'; + + static var tongue:FireTongue; + + /** + * Initialize the FireTongue instance. + * This will automatically start with the default locale for you. + */ + public static function init():Void + { + tongue = new FireTongue(OPENFL, // Haxe framework being used. + // This should really have been a parameterized object... + null, // Function to check if a file exists. Specify null to use the one from the framework. + null, // Function to retrieve the text of a file. Specify null to use the one from the framework. + null, // Function to get a list of files in a directory. Specify null to use the one from the framework. + firetongue.FireTongue.Case.Upper); + + // TODO: Make this use the language from the user's preferences. + setLanguage(DEFAULT_LOCALE); + + trace('[FIRETONGUE] Initialized. Available locales: ${tongue.locales.join(', ')}'); + } + + /** + * Switch the language used by FireTongue. + * @param locale The name of the locale to use, such as `en-US`. + */ + public static function setLanguage(locale:String):Void + { + tongue.initialize({ + locale: locale, // The locale to load. + + finishedCallback: onFinishLoad, // Function run when the locale is loaded. + directory: LOCALE_DIR, // Folder (relative to assets/) to load data from. + replaceMissing: false, // If true, missing flags fallback to the default locale. + checkMissing: true, // If true, check for and store the list of missing flags for this locale. + }); + } + + /** + * Function called when FireTongue finishes loading a language. + */ + static function onFinishLoad() + { + if (tongue == null) + return; + + trace('[FIRETONGUE] Finished loading locale: ${tongue.locale}'); + if (tongue.missingFlags != null) + { + if (tongue.missingFlags.get(tongue.locale) != null && tongue.missingFlags.get(tongue.locale).length != 0) + { + trace('[FIRETONGUE] Missing flags: ${tongue.missingFlags.get(tongue.locale).join(', ')}'); + } + else + { + trace('[FIRETONGUE] No missing flags for this locale. (Note: Another locale has missing flags.)'); + } + } + else + { + trace('[FIRETONGUE] No missing flags.'); + } + + trace('[FIRETONGUE] HELLO_WORLD = ${t("HELLO_WORLD")}'); + } + + /** + * Retrieve a localized string based on the given key. + * + * Example: + * import i18n.FiretongueHandler.t; + * trace(t('HELLO')); // Prints "Hello!" + * + * @param key The key to use to retrieve the localized string. + * @param context The data file to load the key from. + * @return The localized string. + */ + public static function t(key:String, context:String = 'data'):String + { + // The localization strings can be stored all in one file, + // or split into several contexts. + return tongue.get(key, context); + } + + /** + * Retrieve a localized string while replacing specific values. + * In this way, you can use the same invocation call to properly localize + * a variety of different languages with distinct grammar. + * + * Example: + * import i18n.FiretongueHandler.f; + * trace(f('COLLECT_X_APPLES', 'data', [''], ['10']); // Prints "Collect 10 apples!" + * + * @param key The key to use to retrieve the localized string. + * @param context The data file to load the key from. + * @param flags The flags to replace in the string. + * @param values The values to replace those flags with. + * @return The localized string. + */ + public static function f(key:String, context:String = 'data', flags:Array = null, values:Array = null):String + { + var str = t(key, context); + return firetongue.Replace.flags(str, flags, values); + } +} diff --git a/source/ui/AtlasText.hx b/source/ui/AtlasText.hx index e9d06e711..9ac6ae059 100644 --- a/source/ui/AtlasText.hx +++ b/source/ui/AtlasText.hx @@ -190,25 +190,37 @@ class AtlasChar extends FlxSprite animation.play("anim"); updateHitbox(); } - + return this.char = value; } - + function getAnimPrefix(char:String) { return switch (char) { - case '-': '-dash-'; - case '.': '-period-'; - case ",": '-comma-'; + case '&': return '-andpersand-'; + case "😠": '-angry faic-'; // TODO: Do multi-flag characters work? case "'": '-apostraphie-'; - case "?": '-question mark-'; - case "!": '-exclamation point-'; case "\\": '-back slash-'; - case "/": '-forward slash-'; + case ",": '-comma-'; + case '-': '-dash-'; + case '↓': '-down arrow-'; // U+2193 + case "”": '-end quote-'; // U+0022 + case "!": '-exclamation point-'; // U+0021 + case "/": '-forward slash-'; // U+002F + case '>': '-greater than-'; // U+003E + case '♥': '-heart-'; // U+2665 + case '♡': '-heart-'; + case '←': '-left arrow-'; // U+2190 + case '<': '-less than-'; // U+003C case "*": '-multiply x-'; + case '.': '-period-'; // U+002E + case "?": '-question mark-'; + case '→': '-right arrow-'; // U+2192 case "“": '-start quote-'; - case "”": '-end quote-'; + case '↑': '-up arrow-'; // U+2191 + + // Default to getting the character itself. default: char; } } diff --git a/source/ui/OptionsState.hx b/source/ui/OptionsState.hx index 68f54c2fa..816834933 100644 --- a/source/ui/OptionsState.hx +++ b/source/ui/OptionsState.hx @@ -5,6 +5,7 @@ import flixel.FlxSubState; import flixel.addons.transition.FlxTransitionableState; import flixel.group.FlxGroup; import flixel.util.FlxSignal; +import i18n.FireTongueHandler.t; // typedef OptionsState = OptionsMenu_old; // class OptionsState_new extends MusicBeatState @@ -176,27 +177,27 @@ class OptionsMenu extends Page super(); add(items = new TextMenuList()); - createItem('preferences', function() switchPage(Preferences)); - createItem("controls", function() switchPage(Controls)); - // createItem('colors', function() switchPage(Colors)); + createItem(t("PREFERENCES"), function() switchPage(Preferences)); + createItem(t("CONTROLS"), function() switchPage(Controls)); + // createItem(t("COLORS"), function() switchPage(Colors)); #if polymod - createItem('mods', function() switchPage(Mods)); + createItem(t("MODS"), function() switchPage(Mods)); #end #if CAN_OPEN_LINKS if (showDonate) { var hasPopupBlocker = #if web true #else false #end; - createItem('donate', selectDonate, hasPopupBlocker); + createItem(t("DONATE"), selectDonate, hasPopupBlocker); } #end #if newgrounds if (NGio.isLoggedIn) - createItem("logout", selectLogout); + createItem(t("LOGOUT"), selectLogout); else - createItem("login", selectLogin); + createItem(t("LOGIN"), selectLogin); #end - createItem("exit", exit); + createItem(t("EXIT"), exit); } function createItem(name:String, callback:Void->Void, fireInstantly = false)