diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php
index ba93bfe65..d2c75f956 100644
--- a/app/Http/Controllers/HomeController.php
+++ b/app/Http/Controllers/HomeController.php
@@ -91,35 +91,6 @@ class HomeController extends Controller
         return view('common.home', $commonData);
     }
 
-    /**
-     * Get a js representation of the current translations
-     * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
-     * @throws \Exception
-     */
-    public function getTranslations()
-    {
-        $locale = app()->getLocale();
-        $cacheKey = 'GLOBAL_TRANSLATIONS_' . $locale;
-
-        if (cache()->has($cacheKey) && config('app.env') !== 'development') {
-            $resp = cache($cacheKey);
-        } else {
-            $translations = [
-                // Get only translations which might be used in JS
-                'common' => trans('common'),
-                'components' => trans('components'),
-                'entities' => trans('entities'),
-                'errors' => trans('errors')
-            ];
-            $resp = 'window.translations = ' . json_encode($translations);
-            cache()->put($cacheKey, $resp, 120);
-        }
-
-        return response($resp, 200, [
-            'Content-Type' => 'application/javascript'
-        ]);
-    }
-
     /**
      * Get custom head HTML, Used in ajax calls to show in editor.
      * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php
index fa10d4874..e45a0be92 100644
--- a/app/Providers/AppServiceProvider.php
+++ b/app/Providers/AppServiceProvider.php
@@ -39,6 +39,14 @@ class AppServiceProvider extends ServiceProvider
             return "<?php echo icon($expression); ?>";
         });
 
+        Blade::directive('exposeTranslations', function($expression) {
+            return "<?php \$__env->startPush('translations'); ?>" .
+                "<?php foreach({$expression} as \$key): ?>" .
+                '<meta name="translation" key="<?php echo e($key); ?>" value="<?php echo e(trans($key)); ?>">' . "\n" .
+                "<?php endforeach; ?>" .
+                '<?php $__env->stopPush(); ?>';
+        });
+
         // Allow longer string lengths after upgrade to utf8mb4
         Schema::defaultStringLength(191);
 
diff --git a/resources/assets/js/index.js b/resources/assets/js/index.js
index c23615a88..e0c7b34e5 100644
--- a/resources/assets/js/index.js
+++ b/resources/assets/js/index.js
@@ -16,7 +16,7 @@ window.$events = eventManager;
 // Translation setup
 // Creates a global function with name 'trans' to be used in the same way as Laravel's translation system
 import Translations from "./services/translations"
-const translator = new Translations(window.translations);
+const translator = new Translations();
 window.trans = translator.get.bind(translator);
 window.trans_choice = translator.getPlural.bind(translator);
 
diff --git a/resources/assets/js/services/translations.js b/resources/assets/js/services/translations.js
index 06b44a580..645286c08 100644
--- a/resources/assets/js/services/translations.js
+++ b/resources/assets/js/services/translations.js
@@ -10,7 +10,20 @@ class Translator {
      * @param translations
      */
     constructor(translations) {
-        this.store = translations;
+        this.store = new Map();
+        this.parseTranslations();
+    }
+
+    /**
+     * Parse translations out of the page and place into the store.
+     */
+    parseTranslations() {
+        const translationMetaTags = document.querySelectorAll('meta[name="translation"]');
+        for (let tag of translationMetaTags) {
+            const key = tag.getAttribute('key');
+            const value = tag.getAttribute('value');
+            this.store.set(key, value);
+        }
     }
 
     /**
@@ -20,7 +33,7 @@ class Translator {
      * @returns {*}
      */
     get(key, replacements) {
-        let text = this.getTransText(key);
+        const text = this.getTransText(key);
         return this.performReplacements(text, replacements);
     }
 
@@ -33,26 +46,26 @@ class Translator {
      * @returns {*}
      */
     getPlural(key, count, replacements) {
-        let text = this.getTransText(key);
-        let splitText = text.split('|');
+        const text = this.getTransText(key);
+        const splitText = text.split('|');
+        const exactCountRegex = /^{([0-9]+)}/;
+        const rangeRegex = /^\[([0-9]+),([0-9*]+)]/;
         let result = null;
-        let exactCountRegex = /^{([0-9]+)}/;
-        let rangeRegex = /^\[([0-9]+),([0-9*]+)]/;
 
-        for (let i = 0, len = splitText.length; i < len; i++) {
-            let t = splitText[i];
+        for (const i = 0, len = splitText.length; i < len; i++) {
+            const t = splitText[i];
 
             // Parse exact matches
-            let exactMatches = t.match(exactCountRegex);
+            const exactMatches = t.match(exactCountRegex);
             if (exactMatches !== null && Number(exactMatches[1]) === count) {
                 result = t.replace(exactCountRegex, '').trim();
                 break;
             }
 
             // Parse range matches
-            let rangeMatches = t.match(rangeRegex);
+            const rangeMatches = t.match(rangeRegex);
             if (rangeMatches !== null) {
-                let rangeStart = Number(rangeMatches[1]);
+                const rangeStart = Number(rangeMatches[1]);
                 if (rangeStart <= count && (rangeMatches[2] === '*' || Number(rangeMatches[2]) >= count)) {
                     result = t.replace(rangeRegex, '').trim();
                     break;
@@ -74,14 +87,10 @@ class Translator {
      * @returns {String|Object}
      */
     getTransText(key) {
-        let splitKey = key.split('.');
-        let value = splitKey.reduce((a, b) => {
-            return a !== undefined ? a[b] : a;
-        }, this.store);
+        const value = this.store.get(key);
 
         if (value === undefined) {
-            console.log(`Translation with key "${key}" does not exist`);
-            value = key;
+            console.warn(`Translation with key "${key}" does not exist`);
         }
 
         return value;
@@ -95,10 +104,10 @@ class Translator {
      */
     performReplacements(string, replacements) {
         if (!replacements) return string;
-        let replaceMatches = string.match(/:([\S]+)/g);
+        const replaceMatches = string.match(/:([\S]+)/g);
         if (replaceMatches === null) return string;
         replaceMatches.forEach(match => {
-            let key = match.substring(1);
+            const key = match.substring(1);
             if (typeof replacements[key] === 'undefined') return;
             string = string.replace(match, replacements[key]);
         });
diff --git a/resources/views/base.blade.php b/resources/views/base.blade.php
index 367a2cd8b..f5459a717 100644
--- a/resources/views/base.blade.php
+++ b/resources/views/base.blade.php
@@ -13,14 +13,17 @@
     <link rel="stylesheet" href="{{ versioned_asset('dist/styles.css') }}">
     <link rel="stylesheet" media="print" href="{{ versioned_asset('dist/print-styles.css') }}">
 
-    <!-- Scripts -->
-    <script src="{{ baseUrl('/translations') }}"></script>
-
     @yield('head')
+
+    <!-- Custom Styles & Head Content -->
     @include('partials.custom-styles')
     @include('partials.custom-head')
 
     @stack('head')
+
+    <!-- Translations for JS -->
+    @stack('translations')
+
 </head>
 <body class="@yield('body-class')">
 
diff --git a/resources/views/comments/comments.blade.php b/resources/views/comments/comments.blade.php
index cfc89340d..99b21b9b2 100644
--- a/resources/views/comments/comments.blade.php
+++ b/resources/views/comments/comments.blade.php
@@ -1,4 +1,12 @@
 <div page-comments page-id="{{ $page->id }}" class="comments-list">
+
+    @exposeTranslations([
+        'entities.comment_updated_success',
+        'entities.comment_deleted_success',
+        'entities.comment_created_success',
+        'entities.comment_count',
+    ])
+
     <div comment-count-bar class="grid half left-focus v-center no-row-gap">
         <h5 comments-title>{{ trans_choice('entities.comment_count', count($page->comments), ['count' => count($page->comments)]) }}</h5>
         @if (count($page->comments) === 0)
diff --git a/resources/views/components/image-manager.blade.php b/resources/views/components/image-manager.blade.php
index 7c9084ad1..6781bca5f 100644
--- a/resources/views/components/image-manager.blade.php
+++ b/resources/views/components/image-manager.blade.php
@@ -1,4 +1,13 @@
 <div id="image-manager" image-type="{{ $imageType }}" uploaded-to="{{ $uploaded_to ?? 0 }}">
+
+    @exposeTranslations([
+        'components.image_delete_success',
+        'components.image_upload_success',
+        'errors.server_upload_limit',
+        'components.image_upload_remove',
+        'components.file_upload_timeout',
+    ])
+
     <div overlay v-cloak @click="hide">
         <div class="popup-body" @click.stop="">
 
diff --git a/resources/views/pages/edit.blade.php b/resources/views/pages/edit.blade.php
index c12bd6b4d..15f5d5d96 100644
--- a/resources/views/pages/edit.blade.php
+++ b/resources/views/pages/edit.blade.php
@@ -10,6 +10,8 @@
 
     <div class="flex-fill flex">
         <form action="{{ $page->getUrl() }}" autocomplete="off" data-page-id="{{ $page->id }}" method="POST" class="flex flex-fill">
+            {{ csrf_field() }}
+
             @if(!isset($isDraft))
                 <input type="hidden" name="_method" value="PUT">
             @endif
diff --git a/resources/views/pages/form-toolbox.blade.php b/resources/views/pages/form-toolbox.blade.php
index e515c0b2d..d69be20c1 100644
--- a/resources/views/pages/form-toolbox.blade.php
+++ b/resources/views/pages/form-toolbox.blade.php
@@ -18,6 +18,17 @@
 
     @if(userCan('attachment-create-all'))
         <div toolbox-tab-content="files" id="attachment-manager" page-id="{{ $page->id ?? 0 }}">
+
+            @exposeTranslations([
+                'entities.attachments_file_uploaded',
+                'entities.attachments_file_updated',
+                'entities.attachments_link_attached',
+                'entities.attachments_updated_success',
+                'errors.server_upload_limit',
+                'components.image_upload_remove',
+                'components.file_upload_timeout',
+            ])
+
             <h4>{{ trans('entities.attachments') }}</h4>
             <div class="px-l files">
 
diff --git a/resources/views/pages/form.blade.php b/resources/views/pages/form.blade.php
index e9af4a4dd..380718dd7 100644
--- a/resources/views/pages/form.blade.php
+++ b/resources/views/pages/form.blade.php
@@ -1,4 +1,3 @@
-
 <div class="page-editor flex-fill flex" id="page-editor"
      drafts-enabled="{{ $draftsEnabled ? 'true' : 'false' }}"
      drawio-enabled="{{ config('services.drawio') ? 'true' : 'false' }}"
@@ -8,7 +7,14 @@
      page-new-draft="{{ $model->draft ?? 0 }}"
      page-update-draft="{{ $model->isDraft ?? 0 }}">
 
-    {{ csrf_field() }}
+    @exposeTranslations([
+        'entities.pages_editing_draft',
+        'entities.pages_editing_page',
+        'errors.page_draft_autosave_fail',
+        'entities.pages_editing_page',
+        'entities.pages_draft_discarded',
+        'entities.pages_edit_set_changelog',
+    ])
 
     {{--Header Bar--}}
     <div class="primary-background-light toolbar page-edit-toolbar">
@@ -65,57 +71,12 @@
 
         {{--WYSIWYG Editor--}}
         @if(setting('app-editor') === 'wysiwyg')
-            <div wysiwyg-editor class="flex-fill flex">
-                <textarea id="html-editor"  name="html" rows="5" v-pre
-                    @if($errors->has('html')) class="text-neg" @endif>@if(isset($model) || old('html')){{htmlspecialchars( old('html') ? old('html') : $model->html)}}@endif</textarea>
-            </div>
-
-            @if($errors->has('html'))
-                <div class="text-neg text-small">{{ $errors->first('html') }}</div>
-            @endif
+            @include('pages.wysiwyg-editor', ['model' => $model])
         @endif
 
         {{--Markdown Editor--}}
         @if(setting('app-editor') === 'markdown')
-            <div v-pre id="markdown-editor" markdown-editor class="flex-fill flex code-fill">
-
-                <div class="markdown-editor-wrap active">
-                    <div class="editor-toolbar">
-                        <span class="float left editor-toolbar-label">{{ trans('entities.pages_md_editor') }}</span>
-                        <div class="float right buttons">
-                            @if(config('services.drawio'))
-                                <button class="text-button" type="button" data-action="insertDrawing">@icon('drawing'){{ trans('entities.pages_md_insert_drawing') }}</button>
-                                &nbsp;|&nbsp
-                            @endif
-                            <button class="text-button" type="button" data-action="insertImage">@icon('image'){{ trans('entities.pages_md_insert_image') }}</button>
-                            &nbsp;|&nbsp;
-                            <button class="text-button" type="button" data-action="insertLink">@icon('link'){{ trans('entities.pages_md_insert_link') }}</button>
-                        </div>
-                    </div>
-
-                    <div markdown-input class="flex flex-fill">
-                        <textarea  id="markdown-editor-input"  name="markdown" rows="5"
-                            @if($errors->has('markdown')) class="text-neg" @endif>@if(isset($model) || old('markdown')){{htmlspecialchars( old('markdown') ? old('markdown') : ($model->markdown === '' ? $model->html : $model->markdown))}}@endif</textarea>
-                    </div>
-
-                </div>
-
-                <div class="markdown-editor-wrap">
-                    <div class="editor-toolbar">
-                        <div class="editor-toolbar-label">{{ trans('entities.pages_md_preview') }}</div>
-                    </div>
-                    <div class="markdown-display page-content">
-                    </div>
-                </div>
-                <input type="hidden" name="html"/>
-
-            </div>
-
-
-
-            @if($errors->has('markdown'))
-                <div class="text-neg text-small">{{ $errors->first('markdown') }}</div>
-            @endif
+            @include('pages.markdown-editor', ['model' => $model])
         @endif
 
     </div>
diff --git a/resources/views/pages/markdown-editor.blade.php b/resources/views/pages/markdown-editor.blade.php
new file mode 100644
index 000000000..87bde33ac
--- /dev/null
+++ b/resources/views/pages/markdown-editor.blade.php
@@ -0,0 +1,42 @@
+<div v-pre id="markdown-editor" markdown-editor class="flex-fill flex code-fill">
+    @exposeTranslations([
+        'errors.image_upload_error',
+    ])
+
+    <div class="markdown-editor-wrap active">
+        <div class="editor-toolbar">
+            <span class="float left editor-toolbar-label">{{ trans('entities.pages_md_editor') }}</span>
+            <div class="float right buttons">
+                @if(config('services.drawio'))
+                    <button class="text-button" type="button" data-action="insertDrawing">@icon('drawing'){{ trans('entities.pages_md_insert_drawing') }}</button>
+                    &nbsp;|&nbsp
+                @endif
+                <button class="text-button" type="button" data-action="insertImage">@icon('image'){{ trans('entities.pages_md_insert_image') }}</button>
+                &nbsp;|&nbsp;
+                <button class="text-button" type="button" data-action="insertLink">@icon('link'){{ trans('entities.pages_md_insert_link') }}</button>
+            </div>
+        </div>
+
+        <div markdown-input class="flex flex-fill">
+                        <textarea  id="markdown-editor-input"  name="markdown" rows="5"
+                                   @if($errors->has('markdown')) class="text-neg" @endif>@if(isset($model) || old('markdown')){{htmlspecialchars( old('markdown') ? old('markdown') : ($model->markdown === '' ? $model->html : $model->markdown))}}@endif</textarea>
+        </div>
+
+    </div>
+
+    <div class="markdown-editor-wrap">
+        <div class="editor-toolbar">
+            <div class="editor-toolbar-label">{{ trans('entities.pages_md_preview') }}</div>
+        </div>
+        <div class="markdown-display page-content">
+        </div>
+    </div>
+    <input type="hidden" name="html"/>
+
+</div>
+
+
+
+@if($errors->has('markdown'))
+    <div class="text-neg text-small">{{ $errors->first('markdown') }}</div>
+@endif
\ No newline at end of file
diff --git a/resources/views/pages/wysiwyg-editor.blade.php b/resources/views/pages/wysiwyg-editor.blade.php
new file mode 100644
index 000000000..f9a0f03cf
--- /dev/null
+++ b/resources/views/pages/wysiwyg-editor.blade.php
@@ -0,0 +1,13 @@
+<div wysiwyg-editor class="flex-fill flex">
+
+    @exposeTranslations([
+        'errors.image_upload_error',
+    ])
+
+    <textarea id="html-editor"  name="html" rows="5" v-pre
+          @if($errors->has('html')) class="text-neg" @endif>@if(isset($model) || old('html')){{htmlspecialchars( old('html') ? old('html') : $model->html)}}@endif</textarea>
+</div>
+
+@if($errors->has('html'))
+    <div class="text-neg text-small">{{ $errors->first('html') }}</div>
+@endif
\ No newline at end of file
diff --git a/routes/web.php b/routes/web.php
index 25d7ab692..94dd576fe 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -1,6 +1,5 @@
 <?php
 
-Route::get('/translations', 'HomeController@getTranslations');
 Route::get('/robots.txt', 'HomeController@getRobots');
 
 // Authenticated routes...