diff --git a/src/pretix/control/templates/pretixcontrol/base.html b/src/pretix/control/templates/pretixcontrol/base.html
index edd858c33..576f6e410 100644
--- a/src/pretix/control/templates/pretixcontrol/base.html
+++ b/src/pretix/control/templates/pretixcontrol/base.html
@@ -35,6 +35,7 @@
+
diff --git a/src/pretix/presale/templates/pretixpresale/fragment_js.html b/src/pretix/presale/templates/pretixpresale/fragment_js.html
index 75a847872..09350e862 100644
--- a/src/pretix/presale/templates/pretixpresale/fragment_js.html
+++ b/src/pretix/presale/templates/pretixpresale/fragment_js.html
@@ -9,6 +9,7 @@
+
diff --git a/src/pretix/static/pretixbase/js/asynctask.js b/src/pretix/static/pretixbase/js/asynctask.js
index 630e55961..87c50da66 100644
--- a/src/pretix/static/pretixbase/js/asynctask.js
+++ b/src/pretix/static/pretixbase/js/asynctask.js
@@ -1,4 +1,4 @@
-/*global $, waitingDialog, gettext */
+/*global $, gettext */
var async_task_id = null;
var async_task_timeout = null;
var async_task_check_url = null;
diff --git a/src/pretix/static/pretixbase/js/gettextstub.js b/src/pretix/static/pretixbase/js/gettextstub.js
new file mode 100644
index 000000000..9d3b7ea27
--- /dev/null
+++ b/src/pretix/static/pretixbase/js/gettextstub.js
@@ -0,0 +1,23 @@
+// The actual gettext implementation is loaded asynchronously with the translation
+function gettext(msgid) {
+ if (typeof django !== 'undefined' && typeof django.gettext !== 'undefined') {
+ return django.gettext(msgid);
+ }
+ return msgid;
+}
+
+function ngettext(singular, plural, count) {
+ if (typeof django !== 'undefined' && typeof django.ngettext !== 'undefined') {
+ return django.ngettext(singular, plural, count);
+ }
+ return plural;
+}
+
+function interpolate(fmt, object, named) {
+ if (named) {
+ return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
+ } else {
+ return fmt.replace(/%s/g, function(match){return String(obj.shift())});
+ }
+}
+
diff --git a/src/pretix/static/pretixcontrol/js/ui/main.js b/src/pretix/static/pretixcontrol/js/ui/main.js
index c93616c8a..8156b3433 100644
--- a/src/pretix/static/pretixcontrol/js/ui/main.js
+++ b/src/pretix/static/pretixcontrol/js/ui/main.js
@@ -1,18 +1,4 @@
-/*global $,gettext*/
-
-function gettext(msgid) {
- if (typeof django !== 'undefined' && typeof django.gettext !== 'undefined') {
- return django.gettext(msgid);
- }
- return msgid;
-}
-
-function ngettext(singular, plural, count) {
- if (typeof django !== 'undefined' && typeof django.ngettext !== 'undefined') {
- return django.ngettext(singular, plural, count);
- }
- return plural;
-}
+/*global $, gettext, ngettext, interpolate */
function formatPrice(price, currency, locale) {
if (!window.Intl || !Intl.NumberFormat) return price;
diff --git a/src/pretix/static/pretixpresale/js/ui/main.js b/src/pretix/static/pretixpresale/js/ui/main.js
index fd52ab84f..3ca92fc5b 100644
--- a/src/pretix/static/pretixpresale/js/ui/main.js
+++ b/src/pretix/static/pretixpresale/js/ui/main.js
@@ -1,26 +1,4 @@
-/*global $ */
-
-function gettext(msgid) {
- if (typeof django !== 'undefined' && typeof django.gettext !== 'undefined') {
- return django.gettext(msgid);
- }
- return msgid;
-}
-
-function ngettext(singular, plural, count) {
- if (typeof django !== 'undefined' && typeof django.ngettext !== 'undefined') {
- return django.ngettext(singular, plural, count);
- }
- return plural;
-}
-
-function interpolate(fmt, object, named) {
- if (named) {
- return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
- } else {
- return fmt.replace(/%s/g, function(match){return String(obj.shift())});
- }
-}
+/*global $, gettext, ngettext, interpolate */
var form_handlers = function (el) {
el.find('input, select, textarea').on('invalid', function (e) {