diff --git a/src/pretix/control/templates/pretixcontrol/base.html b/src/pretix/control/templates/pretixcontrol/base.html
index edd858c338..576f6e410e 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 75a8478726..09350e862c 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 630e559616..87c50da668 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 0000000000..9d3b7ea27d
--- /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 c93616c8af..8156b34336 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 fd52ab84fe..3ca92fc5bd 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) {