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)