mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-01-19 08:32:45 +08:00
Updated JS translations to be inserted from back-end
Removes old awkward JS translations endpoint. New system still a little akward in code but not now in process. Also extracted out page editors into their own files. Closes #1258
This commit is contained in:
parent
6fa093d9d0
commit
15c39c1976
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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]);
|
||||
});
|
||||
|
|
|
@ -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')">
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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="">
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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">
|
||||
|
||||
|
|
|
@ -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>
|
||||
| 
|
||||
@endif
|
||||
<button class="text-button" type="button" data-action="insertImage">@icon('image'){{ trans('entities.pages_md_insert_image') }}</button>
|
||||
|
|
||||
<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>
|
||||
|
|
42
resources/views/pages/markdown-editor.blade.php
Normal file
42
resources/views/pages/markdown-editor.blade.php
Normal file
|
@ -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>
|
||||
| 
|
||||
@endif
|
||||
<button class="text-button" type="button" data-action="insertImage">@icon('image'){{ trans('entities.pages_md_insert_image') }}</button>
|
||||
|
|
||||
<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
|
13
resources/views/pages/wysiwyg-editor.blade.php
Normal file
13
resources/views/pages/wysiwyg-editor.blade.php
Normal file
|
@ -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
|
|
@ -1,6 +1,5 @@
|
|||
<?php
|
||||
|
||||
Route::get('/translations', 'HomeController@getTranslations');
|
||||
Route::get('/robots.txt', 'HomeController@getRobots');
|
||||
|
||||
// Authenticated routes...
|
||||
|
|
Loading…
Reference in New Issue
Block a user