From 5395ca2f005ce111c15ec9d2fddc11c6e8814b9f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 16 Dec 2023 12:22:40 +0000 Subject: [PATCH 001/124] WYSWIYG: Allowed video/embed alignment controls Required a lot of working around TinyMCE since it added a preview/wrapper element in the editor which complicates things. Added view new "fixes.js" file so large hacks to default TinyMCe functionality are kept in one place. --- resources/js/wysiwyg/config.js | 9 ++++-- resources/js/wysiwyg/fixes.js | 55 ++++++++++++++++++++++++++++++++++ resources/sass/_content.scss | 8 ++--- resources/sass/_tinymce.scss | 24 +++++++++++++++ 4 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 resources/js/wysiwyg/fixes.js diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index 984081bd6..6973db8c8 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -13,6 +13,7 @@ import {getPlugin as getImagemanagerPlugin} from './plugins-imagemanager'; import {getPlugin as getAboutPlugin} from './plugins-about'; import {getPlugin as getDetailsPlugin} from './plugins-details'; import {getPlugin as getTasklistPlugin} from './plugins-tasklist'; +import {handleEmbedAlignmentChanges} from './fixes'; const styleFormats = [ {title: 'Large Header', format: 'h2', preview: 'color: blue;'}, @@ -35,9 +36,9 @@ const styleFormats = [ ]; const formats = { - alignleft: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-left'}, - aligncenter: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-center'}, - alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img', classes: 'align-right'}, + alignleft: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img,iframe,video,span', classes: 'align-left'}, + aligncenter: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img,iframe,video,span', classes: 'align-center'}, + alignright: {selector: 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li,table,img,iframe,video,span', classes: 'align-right'}, calloutsuccess: {block: 'p', exact: true, attributes: {class: 'callout success'}}, calloutinfo: {block: 'p', exact: true, attributes: {class: 'callout info'}}, calloutwarning: {block: 'p', exact: true, attributes: {class: 'callout warning'}}, @@ -177,6 +178,8 @@ function getSetupCallback(options) { setupFilters(editor); }); + handleEmbedAlignmentChanges(editor); + // Custom handler hook window.$events.emitPublic(options.containerElement, 'editor-tinymce::setup', {editor}); diff --git a/resources/js/wysiwyg/fixes.js b/resources/js/wysiwyg/fixes.js new file mode 100644 index 000000000..46984b45e --- /dev/null +++ b/resources/js/wysiwyg/fixes.js @@ -0,0 +1,55 @@ +/** + * Handle alignment for embed (iframe/video) content. + * TinyMCE built-in handling doesn't work well for these when classes are used for + * alignment, since the editor wraps these elements in a non-editable preview span + * which looses tracking and setting of alignment options. + * Here we manually manage these properties and formatting events, by effectively + * syncing the alignment classes to the parent preview span. + * @param {Editor} editor + */ +export function handleEmbedAlignmentChanges(editor) { + function updateClassesForPreview(previewElem) { + const mediaTarget = previewElem.querySelector('iframe, video'); + if (!mediaTarget) { + return; + } + + const alignmentClasses = [...mediaTarget.classList.values()].filter(c => c.startsWith('align-')); + const previewAlignClasses = [...previewElem.classList.values()].filter(c => c.startsWith('align-')); + previewElem.classList.remove(...previewAlignClasses); + previewElem.classList.add(...alignmentClasses); + } + + editor.on('SetContent', () => { + const previewElems = editor.dom.select('span.mce-preview-object'); + for (const previewElem of previewElems) { + updateClassesForPreview(previewElem); + } + }); + + editor.on('FormatApply', event => { + const isAlignment = event.format.startsWith('align'); + if (!event.node || !event.node.matches('.mce-preview-object')) { + return; + } + + const realTarget = event.node.querySelector('iframe, video'); + if (isAlignment && realTarget) { + const className = (editor.formatter.get(event.format)[0]?.classes || [])[0]; + const toAdd = !realTarget.classList.contains(className); + + const wrapperClasses = (event.node.getAttribute('data-mce-p-class') || '').split(' '); + const wrapperClassesFiltered = wrapperClasses.filter(c => !c.startsWith('align-')); + if (toAdd) { + wrapperClassesFiltered.push(className); + } + + const classesToApply = wrapperClassesFiltered.join(' '); + event.node.setAttribute('data-mce-p-class', classesToApply); + + realTarget.setAttribute('class', classesToApply); + editor.formatter.apply(event.format, {}, realTarget); + updateClassesForPreview(event.node); + } + }); +} diff --git a/resources/sass/_content.scss b/resources/sass/_content.scss index 10a2cd983..bde52bb77 100644 --- a/resources/sass/_content.scss +++ b/resources/sass/_content.scss @@ -11,24 +11,24 @@ .align-left { text-align: left; } - img.align-left, table.align-left { + img.align-left, table.align-left, iframe.align-left, video.align-left { float: left !important; margin: $-xs $-m $-m 0; } .align-right { text-align: right !important; } - img.align-right, table.align-right { + img.align-right, table.align-right, iframe.align-right, video.align-right { float: right !important; margin: $-xs 0 $-xs $-s; } .align-center { text-align: center; } - img.align-center { + img.align-center, video.align-center, iframe.align-center { display: block; } - img.align-center, table.align-center { + img.align-center, table.align-center, iframe.align-center, video.align-center { margin-left: auto; margin-right: auto; } diff --git a/resources/sass/_tinymce.scss b/resources/sass/_tinymce.scss index 7450f6016..8e036fc46 100644 --- a/resources/sass/_tinymce.scss +++ b/resources/sass/_tinymce.scss @@ -72,6 +72,30 @@ body.page-content.mce-content-body { overflow: hidden; } +// Allow alignment to be reflected in media embed wrappers +.page-content.mce-content-body .mce-preview-object.align-right { + float: right !important; + margin: $-xs 0 $-xs $-s; +} + +.page-content.mce-content-body .mce-preview-object.align-left { + float: left !important; + margin: $-xs $-m $-m 0; +} + +.page-content.mce-content-body .mce-preview-object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +.page-content.mce-content-body .mce-preview-object iframe, +.page-content.mce-content-body .mce-preview-object video { + display: block; + margin: 0 !important; + float: none !important; +} + /** * Dark Mode Overrides */ From 0c4dd7874ca3a2cb1b779170958529d5adc028c2 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 16 Dec 2023 14:03:12 +0000 Subject: [PATCH 002/124] Design: Updated buttons to be a bit friendlier Old all-caps button design made them a bit angry, and kinda odd and outdated. This updates them to use their original source text casing (which may help for translation variations) while being a bit rounder with a better defined shadow for outline buttons. --- resources/sass/_buttons.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/sass/_buttons.scss b/resources/sass/_buttons.scss index 7fa7a65b1..e629e7726 100644 --- a/resources/sass/_buttons.scss +++ b/resources/sass/_buttons.scss @@ -14,7 +14,7 @@ button { display: inline-block; font-weight: 400; outline: 0; - border-radius: 2px; + border-radius: 4px; cursor: pointer; transition: background-color ease-in-out 120ms, filter ease-in-out 120ms, @@ -22,7 +22,6 @@ button { box-shadow: none; background-color: var(--color-primary); color: #FFF; - text-transform: uppercase; border: 1px solid var(--color-primary); vertical-align: top; &:hover, &:focus, &:active { @@ -52,10 +51,11 @@ button { border: 1px solid; @include lightDark(border-color, #CCC, #666); &:hover, &:focus, &:active { + @include lightDark(color, #444, #BBB); border: 1px solid #CCC; - box-shadow: none; + box-shadow: 0 1px 4px 0 rgba(0, 0, 0, 0.1); background-color: #F2F2F2; - @include lightDark(background-color, #f2f2f2, #555); + @include lightDark(background-color, #f8f8f8, #444); filter: none; } &:active { From 569542f0bb519bf9a52a6565780a7e610ff12130 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 16 Dec 2023 14:48:35 +0000 Subject: [PATCH 003/124] Input WYSIWYG: Added compontent and rough logic to book form Just as a draft for prototyping and playing around to get things started. --- resources/js/components/index.js | 1 + resources/js/components/wysiwyg-editor.js | 12 ++--- resources/js/components/wysiwyg-input.js | 26 ++++++++++ resources/js/wysiwyg/config.js | 50 ++++++++++++++++++- resources/views/books/parts/form.blade.php | 15 +++++- .../editor-translations.blade.php | 0 .../pages/parts/wysiwyg-editor.blade.php | 2 +- 7 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 resources/js/components/wysiwyg-input.js rename resources/views/{pages/parts => form}/editor-translations.blade.php (100%) diff --git a/resources/js/components/index.js b/resources/js/components/index.js index a56f18a5a..3a66079d7 100644 --- a/resources/js/components/index.js +++ b/resources/js/components/index.js @@ -58,3 +58,4 @@ export {TriLayout} from './tri-layout'; export {UserSelect} from './user-select'; export {WebhookEvents} from './webhook-events'; export {WysiwygEditor} from './wysiwyg-editor'; +export {WysiwygInput} from './wysiwyg-input'; diff --git a/resources/js/components/wysiwyg-editor.js b/resources/js/components/wysiwyg-editor.js index 21db207e6..82f60827d 100644 --- a/resources/js/components/wysiwyg-editor.js +++ b/resources/js/components/wysiwyg-editor.js @@ -1,4 +1,4 @@ -import {build as buildEditorConfig} from '../wysiwyg/config'; +import {buildForEditor as buildEditorConfig} from '../wysiwyg/config'; import {Component} from './component'; export class WysiwygEditor extends Component { @@ -6,17 +6,13 @@ export class WysiwygEditor extends Component { setup() { this.elem = this.$el; - this.pageId = this.$opts.pageId; - this.textDirection = this.$opts.textDirection; - this.isDarkMode = document.documentElement.classList.contains('dark-mode'); - this.tinyMceConfig = buildEditorConfig({ language: this.$opts.language, containerElement: this.elem, - darkMode: this.isDarkMode, - textDirection: this.textDirection, + darkMode: document.documentElement.classList.contains('dark-mode'), + textDirection: this.$opts.textDirection, drawioUrl: this.getDrawIoUrl(), - pageId: Number(this.pageId), + pageId: Number(this.$opts.pageId), translations: { imageUploadErrorText: this.$opts.imageUploadErrorText, serverUploadLimitText: this.$opts.serverUploadLimitText, diff --git a/resources/js/components/wysiwyg-input.js b/resources/js/components/wysiwyg-input.js new file mode 100644 index 000000000..88c06a334 --- /dev/null +++ b/resources/js/components/wysiwyg-input.js @@ -0,0 +1,26 @@ +import {Component} from './component'; +import {buildForInput} from '../wysiwyg/config'; + +export class WysiwygInput extends Component { + + setup() { + this.elem = this.$el; + + const config = buildForInput({ + language: this.$opts.language, + containerElement: this.elem, + darkMode: document.documentElement.classList.contains('dark-mode'), + textDirection: this.textDirection, + translations: { + imageUploadErrorText: this.$opts.imageUploadErrorText, + serverUploadLimitText: this.$opts.serverUploadLimitText, + }, + translationMap: window.editor_translations, + }); + + window.tinymce.init(config).then(editors => { + this.editor = editors[0]; + }); + } + +} diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index 6973db8c8..d7c6bba72 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -217,7 +217,7 @@ body { * @param {WysiwygConfigOptions} options * @return {Object} */ -export function build(options) { +export function buildForEditor(options) { // Set language window.tinymce.addI18n(options.language, options.translationMap); @@ -290,6 +290,54 @@ export function build(options) { }; } +/** + * @param {WysiwygConfigOptions} options + * @return {RawEditorOptions} + */ +export function buildForInput(options) { + // Set language + window.tinymce.addI18n(options.language, options.translationMap); + + // BookStack Version + const version = document.querySelector('script[src*="/dist/app.js"]').getAttribute('src').split('?version=')[1]; + + // Return config object + return { + width: '100%', + height: '300px', + target: options.containerElement, + cache_suffix: `?version=${version}`, + content_css: [ + window.baseUrl('/dist/styles.css'), + ], + branding: false, + skin: options.darkMode ? 'tinymce-5-dark' : 'tinymce-5', + body_class: 'page-content', + browser_spellcheck: true, + relative_urls: false, + language: options.language, + directionality: options.textDirection, + remove_script_host: false, + document_base_url: window.baseUrl('/'), + end_container_on_empty_block: true, + remove_trailing_brs: false, + statusbar: false, + menubar: false, + plugins: 'link autolink', + contextmenu: false, + toolbar: 'bold italic underline link', + content_style: getContentStyle(options), + color_map: colorMap, + init_instance_callback(editor) { + const head = editor.getDoc().querySelector('head'); + head.innerHTML += fetchCustomHeadContent(); + }, + setup(editor) { + // + }, + }; +} + /** * @typedef {Object} WysiwygConfigOptions * @property {Element} containerElement diff --git a/resources/views/books/parts/form.blade.php b/resources/views/books/parts/form.blade.php index e22be619d..3a2e30da6 100644 --- a/resources/views/books/parts/form.blade.php +++ b/resources/views/books/parts/form.blade.php @@ -1,3 +1,6 @@ +@push('head') + +@endpush {{ csrf_field() }}
@@ -8,6 +11,15 @@
@include('form.textarea', ['name' => 'description']) + + + @if($errors->has('description_html')) +
{{ $errors->first('description_html') }}
+ @endif
@@ -62,4 +74,5 @@
-@include('entities.selector-popup', ['entityTypes' => 'page', 'selectorEndpoint' => '/search/entity-selector-templates']) \ No newline at end of file +@include('entities.selector-popup', ['entityTypes' => 'page', 'selectorEndpoint' => '/search/entity-selector-templates']) +@include('form.editor-translations') \ No newline at end of file diff --git a/resources/views/pages/parts/editor-translations.blade.php b/resources/views/form/editor-translations.blade.php similarity index 100% rename from resources/views/pages/parts/editor-translations.blade.php rename to resources/views/form/editor-translations.blade.php diff --git a/resources/views/pages/parts/wysiwyg-editor.blade.php b/resources/views/pages/parts/wysiwyg-editor.blade.php index ca6b6da8a..84a267b68 100644 --- a/resources/views/pages/parts/wysiwyg-editor.blade.php +++ b/resources/views/pages/parts/wysiwyg-editor.blade.php @@ -18,4 +18,4 @@
{{ $errors->first('html') }}
@endif -@include('pages.parts.editor-translations') \ No newline at end of file +@include('form.editor-translations') \ No newline at end of file From c622b785a969075bbc59129471d42d2df7377ea8 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 17 Dec 2023 15:02:15 +0000 Subject: [PATCH 004/124] Input WYSIWYG: Added description_html field, added store logic Rolled out HTML editor field and store logic across all target entity types. Cleaned up WYSIWYG input logic and design. Cleaned up some injected classes while there. --- app/Entities/Controllers/BookController.php | 4 +-- .../Controllers/BookshelfController.php | 30 +++++++--------- .../Controllers/ChapterController.php | 27 ++++++++------ app/Entities/Models/Book.php | 3 +- app/Entities/Models/Bookshelf.php | 1 + app/Entities/Models/Chapter.php | 1 + app/Entities/Models/HasHtmlDescription.php | 21 +++++++++++ app/Entities/Repos/BaseRepo.php | 33 ++++++++++++----- ...40913_add_description_html_to_entities.php | 36 +++++++++++++++++++ resources/js/wysiwyg/config.js | 10 +++--- resources/sass/_forms.scss | 8 +++++ resources/sass/_tinymce.scss | 7 ++++ resources/views/books/parts/form.blade.php | 13 ++----- resources/views/books/show.blade.php | 2 +- resources/views/chapters/parts/form.blade.php | 13 ++++--- .../form/description-html-input.blade.php | 8 +++++ resources/views/shelves/parts/form.blade.php | 14 +++++--- 17 files changed, 167 insertions(+), 64 deletions(-) create mode 100644 app/Entities/Models/HasHtmlDescription.php create mode 100644 database/migrations/2023_12_17_140913_add_description_html_to_entities.php create mode 100644 resources/views/form/description-html-input.blade.php diff --git a/app/Entities/Controllers/BookController.php b/app/Entities/Controllers/BookController.php index faa578893..481c621e6 100644 --- a/app/Entities/Controllers/BookController.php +++ b/app/Entities/Controllers/BookController.php @@ -93,7 +93,7 @@ class BookController extends Controller $this->checkPermission('book-create-all'); $validated = $this->validate($request, [ 'name' => ['required', 'string', 'max:255'], - 'description' => ['string', 'max:1000'], + 'description_html' => ['string', 'max:2000'], 'image' => array_merge(['nullable'], $this->getImageValidationRules()), 'tags' => ['array'], 'default_template_id' => ['nullable', 'integer'], @@ -168,7 +168,7 @@ class BookController extends Controller $validated = $this->validate($request, [ 'name' => ['required', 'string', 'max:255'], - 'description' => ['string', 'max:1000'], + 'description_html' => ['string', 'max:2000'], 'image' => array_merge(['nullable'], $this->getImageValidationRules()), 'tags' => ['array'], 'default_template_id' => ['nullable', 'integer'], diff --git a/app/Entities/Controllers/BookshelfController.php b/app/Entities/Controllers/BookshelfController.php index fcfd37538..acc972348 100644 --- a/app/Entities/Controllers/BookshelfController.php +++ b/app/Entities/Controllers/BookshelfController.php @@ -18,15 +18,11 @@ use Illuminate\Validation\ValidationException; class BookshelfController extends Controller { - protected BookshelfRepo $shelfRepo; - protected ShelfContext $shelfContext; - protected ReferenceFetcher $referenceFetcher; - - public function __construct(BookshelfRepo $shelfRepo, ShelfContext $shelfContext, ReferenceFetcher $referenceFetcher) - { - $this->shelfRepo = $shelfRepo; - $this->shelfContext = $shelfContext; - $this->referenceFetcher = $referenceFetcher; + public function __construct( + protected BookshelfRepo $shelfRepo, + protected ShelfContext $shelfContext, + protected ReferenceFetcher $referenceFetcher + ) { } /** @@ -81,10 +77,10 @@ class BookshelfController extends Controller { $this->checkPermission('bookshelf-create-all'); $validated = $this->validate($request, [ - 'name' => ['required', 'string', 'max:255'], - 'description' => ['string', 'max:1000'], - 'image' => array_merge(['nullable'], $this->getImageValidationRules()), - 'tags' => ['array'], + 'name' => ['required', 'string', 'max:255'], + 'description_html' => ['string', 'max:2000'], + 'image' => array_merge(['nullable'], $this->getImageValidationRules()), + 'tags' => ['array'], ]); $bookIds = explode(',', $request->get('books', '')); @@ -164,10 +160,10 @@ class BookshelfController extends Controller $shelf = $this->shelfRepo->getBySlug($slug); $this->checkOwnablePermission('bookshelf-update', $shelf); $validated = $this->validate($request, [ - 'name' => ['required', 'string', 'max:255'], - 'description' => ['string', 'max:1000'], - 'image' => array_merge(['nullable'], $this->getImageValidationRules()), - 'tags' => ['array'], + 'name' => ['required', 'string', 'max:255'], + 'description_html' => ['string', 'max:2000'], + 'image' => array_merge(['nullable'], $this->getImageValidationRules()), + 'tags' => ['array'], ]); if ($request->has('image_reset')) { diff --git a/app/Entities/Controllers/ChapterController.php b/app/Entities/Controllers/ChapterController.php index 40a537303..73f314ab6 100644 --- a/app/Entities/Controllers/ChapterController.php +++ b/app/Entities/Controllers/ChapterController.php @@ -22,13 +22,10 @@ use Throwable; class ChapterController extends Controller { - protected ChapterRepo $chapterRepo; - protected ReferenceFetcher $referenceFetcher; - - public function __construct(ChapterRepo $chapterRepo, ReferenceFetcher $referenceFetcher) - { - $this->chapterRepo = $chapterRepo; - $this->referenceFetcher = $referenceFetcher; + public function __construct( + protected ChapterRepo $chapterRepo, + protected ReferenceFetcher $referenceFetcher + ) { } /** @@ -51,14 +48,16 @@ class ChapterController extends Controller */ public function store(Request $request, string $bookSlug) { - $this->validate($request, [ - 'name' => ['required', 'string', 'max:255'], + $validated = $this->validate($request, [ + 'name' => ['required', 'string', 'max:255'], + 'description_html' => ['string', 'max:2000'], + 'tags' => ['array'], ]); $book = Book::visible()->where('slug', '=', $bookSlug)->firstOrFail(); $this->checkOwnablePermission('chapter-create', $book); - $chapter = $this->chapterRepo->create($request->all(), $book); + $chapter = $this->chapterRepo->create($validated, $book); return redirect($chapter->getUrl()); } @@ -111,10 +110,16 @@ class ChapterController extends Controller */ public function update(Request $request, string $bookSlug, string $chapterSlug) { + $validated = $this->validate($request, [ + 'name' => ['required', 'string', 'max:255'], + 'description_html' => ['string', 'max:2000'], + 'tags' => ['array'], + ]); + $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-update', $chapter); - $this->chapterRepo->update($chapter, $request->all()); + $this->chapterRepo->update($chapter, $validated); return redirect($chapter->getUrl()); } diff --git a/app/Entities/Models/Book.php b/app/Entities/Models/Book.php index ee9a7f447..7bbe2d8a4 100644 --- a/app/Entities/Models/Book.php +++ b/app/Entities/Models/Book.php @@ -26,10 +26,11 @@ use Illuminate\Support\Collection; class Book extends Entity implements HasCoverImage { use HasFactory; + use HasHtmlDescription; public $searchFactor = 1.2; - protected $fillable = ['name', 'description']; + protected $fillable = ['name']; protected $hidden = ['pivot', 'image_id', 'deleted_at']; /** diff --git a/app/Entities/Models/Bookshelf.php b/app/Entities/Models/Bookshelf.php index 4b44025a4..cf22195f7 100644 --- a/app/Entities/Models/Bookshelf.php +++ b/app/Entities/Models/Bookshelf.php @@ -11,6 +11,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; class Bookshelf extends Entity implements HasCoverImage { use HasFactory; + use HasHtmlDescription; protected $table = 'bookshelves'; diff --git a/app/Entities/Models/Chapter.php b/app/Entities/Models/Chapter.php index 98889ce3f..17fccfd6c 100644 --- a/app/Entities/Models/Chapter.php +++ b/app/Entities/Models/Chapter.php @@ -15,6 +15,7 @@ use Illuminate\Support\Collection; class Chapter extends BookChild { use HasFactory; + use HasHtmlDescription; public $searchFactor = 1.2; diff --git a/app/Entities/Models/HasHtmlDescription.php b/app/Entities/Models/HasHtmlDescription.php new file mode 100644 index 000000000..cc431f7fc --- /dev/null +++ b/app/Entities/Models/HasHtmlDescription.php @@ -0,0 +1,21 @@ +description_html ?: '

' . e($this->description) . '

'; + return HtmlContentFilter::removeScriptsFromHtmlString($html); + } +} diff --git a/app/Entities/Repos/BaseRepo.php b/app/Entities/Repos/BaseRepo.php index 2894a04e3..f6b9ff578 100644 --- a/app/Entities/Repos/BaseRepo.php +++ b/app/Entities/Repos/BaseRepo.php @@ -5,6 +5,7 @@ namespace BookStack\Entities\Repos; use BookStack\Activity\TagRepo; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\HasCoverImage; +use BookStack\Entities\Models\HasHtmlDescription; use BookStack\Exceptions\ImageUploadException; use BookStack\References\ReferenceUpdater; use BookStack\Uploads\ImageRepo; @@ -12,15 +13,11 @@ use Illuminate\Http\UploadedFile; class BaseRepo { - protected TagRepo $tagRepo; - protected ImageRepo $imageRepo; - protected ReferenceUpdater $referenceUpdater; - - public function __construct(TagRepo $tagRepo, ImageRepo $imageRepo, ReferenceUpdater $referenceUpdater) - { - $this->tagRepo = $tagRepo; - $this->imageRepo = $imageRepo; - $this->referenceUpdater = $referenceUpdater; + public function __construct( + protected TagRepo $tagRepo, + protected ImageRepo $imageRepo, + protected ReferenceUpdater $referenceUpdater + ) { } /** @@ -29,6 +26,7 @@ class BaseRepo public function create(Entity $entity, array $input) { $entity->fill($input); + $this->updateDescription($entity, $input); $entity->forceFill([ 'created_by' => user()->id, 'updated_by' => user()->id, @@ -54,6 +52,7 @@ class BaseRepo $oldUrl = $entity->getUrl(); $entity->fill($input); + $this->updateDescription($entity, $input); $entity->updated_by = user()->id; if ($entity->isDirty('name') || empty($entity->slug)) { @@ -99,4 +98,20 @@ class BaseRepo $entity->save(); } } + + protected function updateDescription(Entity $entity, array $input): void + { + if (!in_array(HasHtmlDescription::class, class_uses($entity))) { + return; + } + + /** @var HasHtmlDescription $entity */ + if (isset($input['description_html'])) { + $entity->description_html = $input['description_html']; + $entity->description = html_entity_decode(strip_tags($input['description_html'])); + } else if (isset($input['description'])) { + $entity->description = $input['description']; + $entity->description_html = $entity->descriptionHtml(); + } + } } diff --git a/database/migrations/2023_12_17_140913_add_description_html_to_entities.php b/database/migrations/2023_12_17_140913_add_description_html_to_entities.php new file mode 100644 index 000000000..68c52e81b --- /dev/null +++ b/database/migrations/2023_12_17_140913_add_description_html_to_entities.php @@ -0,0 +1,36 @@ + $table->text('description_html'); + + Schema::table('books', $addColumn); + Schema::table('chapters', $addColumn); + Schema::table('bookshelves', $addColumn); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + $removeColumn = fn(Blueprint $table) => $table->removeColumn('description_html'); + + Schema::table('books', $removeColumn); + Schema::table('chapters', $removeColumn); + Schema::table('bookshelves', $removeColumn); + } +}; diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index d7c6bba72..f0a2dbe1c 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -304,7 +304,7 @@ export function buildForInput(options) { // Return config object return { width: '100%', - height: '300px', + height: '185px', target: options.containerElement, cache_suffix: `?version=${version}`, content_css: [ @@ -312,7 +312,7 @@ export function buildForInput(options) { ], branding: false, skin: options.darkMode ? 'tinymce-5-dark' : 'tinymce-5', - body_class: 'page-content', + body_class: 'wysiwyg-input', browser_spellcheck: true, relative_urls: false, language: options.language, @@ -323,11 +323,13 @@ export function buildForInput(options) { remove_trailing_brs: false, statusbar: false, menubar: false, - plugins: 'link autolink', + plugins: 'link autolink lists', contextmenu: false, - toolbar: 'bold italic underline link', + toolbar: 'bold italic underline link bullist numlist', content_style: getContentStyle(options), color_map: colorMap, + file_picker_types: 'file', + file_picker_callback: filePickerCallback, init_instance_callback(editor) { const head = editor.getDoc().querySelector('head'); head.innerHTML += fetchCustomHeadContent(); diff --git a/resources/sass/_forms.scss b/resources/sass/_forms.scss index cd5d929f4..b63f9cdd5 100644 --- a/resources/sass/_forms.scss +++ b/resources/sass/_forms.scss @@ -406,6 +406,14 @@ input[type=color] { height: auto; } +.description-input > .tox-tinymce { + border: 1px solid #DDD !important; + border-radius: 3px; + .tox-toolbar__primary { + justify-content: end; + } +} + .search-box { max-width: 100%; position: relative; diff --git a/resources/sass/_tinymce.scss b/resources/sass/_tinymce.scss index 8e036fc46..c4336da7c 100644 --- a/resources/sass/_tinymce.scss +++ b/resources/sass/_tinymce.scss @@ -23,6 +23,13 @@ display: block; } +.wysiwyg-input.mce-content-body { + padding-block-start: 1rem; + padding-block-end: 1rem; + outline: 0; + display: block; +} + // Default styles for our custom root nodes .page-content.mce-content-body doc-root { display: block; diff --git a/resources/views/books/parts/form.blade.php b/resources/views/books/parts/form.blade.php index 3a2e30da6..d380c5871 100644 --- a/resources/views/books/parts/form.blade.php +++ b/resources/views/books/parts/form.blade.php @@ -9,17 +9,8 @@
- - @include('form.textarea', ['name' => 'description']) - - - @if($errors->has('description_html')) -
{{ $errors->first('description_html') }}
- @endif + + @include('form.description-html-input')
diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php index 8f7c3f6cf..5884e41fd 100644 --- a/resources/views/books/show.blade.php +++ b/resources/views/books/show.blade.php @@ -26,7 +26,7 @@

{{$book->name}}

-

{!! nl2br(e($book->description)) !!}

+

{!! $book->descriptionHtml() !!}

@if(count($bookChildren) > 0)
@foreach($bookChildren as $childElement) diff --git a/resources/views/chapters/parts/form.blade.php b/resources/views/chapters/parts/form.blade.php index 8abcebe13..7c565f43c 100644 --- a/resources/views/chapters/parts/form.blade.php +++ b/resources/views/chapters/parts/form.blade.php @@ -1,14 +1,16 @@ +@push('head') + +@endpush -{!! csrf_field() !!} - +{{ csrf_field() }}
@include('form.text', ['name' => 'name', 'autofocus' => true])
- - @include('form.textarea', ['name' => 'description']) + + @include('form.description-html-input')
@@ -24,3 +26,6 @@ {{ trans('common.cancel') }}
+ +@include('entities.selector-popup', ['entityTypes' => 'page', 'selectorEndpoint' => '/search/entity-selector-templates']) +@include('form.editor-translations') \ No newline at end of file diff --git a/resources/views/form/description-html-input.blade.php b/resources/views/form/description-html-input.blade.php new file mode 100644 index 000000000..3cf726ba4 --- /dev/null +++ b/resources/views/form/description-html-input.blade.php @@ -0,0 +1,8 @@ + +@if($errors->has('description_html')) +
{{ $errors->first('description_html') }}
+@endif \ No newline at end of file diff --git a/resources/views/shelves/parts/form.blade.php b/resources/views/shelves/parts/form.blade.php index ad67cb85c..a724c99ce 100644 --- a/resources/views/shelves/parts/form.blade.php +++ b/resources/views/shelves/parts/form.blade.php @@ -1,13 +1,16 @@ -{{ csrf_field() }} +@push('head') + +@endpush +{{ csrf_field() }}
@include('form.text', ['name' => 'name', 'autofocus' => true])
- - @include('form.textarea', ['name' => 'description']) + + @include('form.description-html-input')
@@ -84,4 +87,7 @@
{{ trans('common.cancel') }} -
\ No newline at end of file +
+ +@include('entities.selector-popup', ['entityTypes' => 'page', 'selectorEndpoint' => '/search/entity-selector-templates']) +@include('form.editor-translations') \ No newline at end of file From 307fae39c450f687aba93e589e947e5389357601 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 18 Dec 2023 16:23:40 +0000 Subject: [PATCH 005/124] Input WYSIWYG: Added reference store & fetch handling For book, shelves and chapters. Made much of the existing handling generic to entity types. Added new MixedEntityListLoader to help load lists somewhat efficiently. Only manually tested so far. --- .../Commands/RegenerateReferencesCommand.php | 2 +- app/Entities/Controllers/BookController.php | 2 +- .../Controllers/BookshelfController.php | 2 +- .../Controllers/ChapterController.php | 2 +- app/Entities/Controllers/PageController.php | 2 +- app/Entities/Models/Book.php | 2 +- app/Entities/Models/Bookshelf.php | 2 +- app/Entities/Models/Chapter.php | 2 +- app/Entities/Models/Entity.php | 9 +- app/Entities/Models/Page.php | 3 +- app/Entities/Repos/BaseRepo.php | 6 +- app/Entities/Repos/PageRepo.php | 4 +- app/Entities/Tools/MixedEntityListLoader.php | 103 ++++++++++++++++++ app/References/ReferenceController.php | 16 ++- app/References/ReferenceFetcher.php | 60 ++++------ app/References/ReferenceStore.php | 85 ++++++++++----- app/References/ReferenceUpdater.php | 4 +- app/Settings/MaintenanceController.php | 2 +- lang/en/entities.php | 4 +- resources/views/entities/meta.blade.php | 2 +- resources/views/shelves/show.blade.php | 2 +- 21 files changed, 219 insertions(+), 97 deletions(-) create mode 100644 app/Entities/Tools/MixedEntityListLoader.php diff --git a/app/Console/Commands/RegenerateReferencesCommand.php b/app/Console/Commands/RegenerateReferencesCommand.php index ea8ff8e00..563da100a 100644 --- a/app/Console/Commands/RegenerateReferencesCommand.php +++ b/app/Console/Commands/RegenerateReferencesCommand.php @@ -34,7 +34,7 @@ class RegenerateReferencesCommand extends Command DB::setDefaultConnection($this->option('database')); } - $references->updateForAllPages(); + $references->updateForAll(); DB::setDefaultConnection($connection); diff --git a/app/Entities/Controllers/BookController.php b/app/Entities/Controllers/BookController.php index 481c621e6..412feca2f 100644 --- a/app/Entities/Controllers/BookController.php +++ b/app/Entities/Controllers/BookController.php @@ -138,7 +138,7 @@ class BookController extends Controller 'bookParentShelves' => $bookParentShelves, 'watchOptions' => new UserEntityWatchOptions(user(), $book), 'activity' => $activities->entityActivity($book, 20, 1), - 'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($book), + 'referenceCount' => $this->referenceFetcher->getReferenceCountToEntity($book), ]); } diff --git a/app/Entities/Controllers/BookshelfController.php b/app/Entities/Controllers/BookshelfController.php index acc972348..2f5461cdb 100644 --- a/app/Entities/Controllers/BookshelfController.php +++ b/app/Entities/Controllers/BookshelfController.php @@ -125,7 +125,7 @@ class BookshelfController extends Controller 'view' => $view, 'activity' => $activities->entityActivity($shelf, 20, 1), 'listOptions' => $listOptions, - 'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($shelf), + 'referenceCount' => $this->referenceFetcher->getReferenceCountToEntity($shelf), ]); } diff --git a/app/Entities/Controllers/ChapterController.php b/app/Entities/Controllers/ChapterController.php index 73f314ab6..28ad35fa4 100644 --- a/app/Entities/Controllers/ChapterController.php +++ b/app/Entities/Controllers/ChapterController.php @@ -86,7 +86,7 @@ class ChapterController extends Controller 'pages' => $pages, 'next' => $nextPreviousLocator->getNext(), 'previous' => $nextPreviousLocator->getPrevious(), - 'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($chapter), + 'referenceCount' => $this->referenceFetcher->getReferenceCountToEntity($chapter), ]); } diff --git a/app/Entities/Controllers/PageController.php b/app/Entities/Controllers/PageController.php index 0a3e76daa..adafcdc7b 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -155,7 +155,7 @@ class PageController extends Controller 'watchOptions' => new UserEntityWatchOptions(user(), $page), 'next' => $nextPreviousLocator->getNext(), 'previous' => $nextPreviousLocator->getPrevious(), - 'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($page), + 'referenceCount' => $this->referenceFetcher->getReferenceCountToEntity($page), ]); } diff --git a/app/Entities/Models/Book.php b/app/Entities/Models/Book.php index 7bbe2d8a4..52674d839 100644 --- a/app/Entities/Models/Book.php +++ b/app/Entities/Models/Book.php @@ -28,7 +28,7 @@ class Book extends Entity implements HasCoverImage use HasFactory; use HasHtmlDescription; - public $searchFactor = 1.2; + public float $searchFactor = 1.2; protected $fillable = ['name']; protected $hidden = ['pivot', 'image_id', 'deleted_at']; diff --git a/app/Entities/Models/Bookshelf.php b/app/Entities/Models/Bookshelf.php index cf22195f7..464c127b8 100644 --- a/app/Entities/Models/Bookshelf.php +++ b/app/Entities/Models/Bookshelf.php @@ -15,7 +15,7 @@ class Bookshelf extends Entity implements HasCoverImage protected $table = 'bookshelves'; - public $searchFactor = 1.2; + public float $searchFactor = 1.2; protected $fillable = ['name', 'description', 'image_id']; diff --git a/app/Entities/Models/Chapter.php b/app/Entities/Models/Chapter.php index 17fccfd6c..6c5f059ac 100644 --- a/app/Entities/Models/Chapter.php +++ b/app/Entities/Models/Chapter.php @@ -17,7 +17,7 @@ class Chapter extends BookChild use HasFactory; use HasHtmlDescription; - public $searchFactor = 1.2; + public float $searchFactor = 1.2; protected $fillable = ['name', 'description', 'priority']; protected $hidden = ['pivot', 'deleted_at']; diff --git a/app/Entities/Models/Entity.php b/app/Entities/Models/Entity.php index 332510672..f07d372c3 100644 --- a/app/Entities/Models/Entity.php +++ b/app/Entities/Models/Entity.php @@ -57,12 +57,17 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable /** * @var string - Name of property where the main text content is found */ - public $textField = 'description'; + public string $textField = 'description'; + + /** + * @var string - Name of the property where the main HTML content is found + */ + public string $htmlField = 'description_html'; /** * @var float - Multiplier for search indexing. */ - public $searchFactor = 1.0; + public float $searchFactor = 1.0; /** * Get the entities that are visible to the current user. diff --git a/app/Entities/Models/Page.php b/app/Entities/Models/Page.php index 7e2c12c20..17d6f9a01 100644 --- a/app/Entities/Models/Page.php +++ b/app/Entities/Models/Page.php @@ -37,7 +37,8 @@ class Page extends BookChild protected $fillable = ['name', 'priority']; - public $textField = 'text'; + public string $textField = 'text'; + public string $htmlField = 'html'; protected $hidden = ['html', 'markdown', 'text', 'pivot', 'deleted_at']; diff --git a/app/Entities/Repos/BaseRepo.php b/app/Entities/Repos/BaseRepo.php index f6b9ff578..dbdaa9213 100644 --- a/app/Entities/Repos/BaseRepo.php +++ b/app/Entities/Repos/BaseRepo.php @@ -7,6 +7,7 @@ use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\HasCoverImage; use BookStack\Entities\Models\HasHtmlDescription; use BookStack\Exceptions\ImageUploadException; +use BookStack\References\ReferenceStore; use BookStack\References\ReferenceUpdater; use BookStack\Uploads\ImageRepo; use Illuminate\Http\UploadedFile; @@ -16,7 +17,8 @@ class BaseRepo public function __construct( protected TagRepo $tagRepo, protected ImageRepo $imageRepo, - protected ReferenceUpdater $referenceUpdater + protected ReferenceUpdater $referenceUpdater, + protected ReferenceStore $referenceStore, ) { } @@ -42,6 +44,7 @@ class BaseRepo $entity->refresh(); $entity->rebuildPermissions(); $entity->indexForSearch(); + $this->referenceStore->updateForEntity($entity); } /** @@ -68,6 +71,7 @@ class BaseRepo $entity->rebuildPermissions(); $entity->indexForSearch(); + $this->referenceStore->updateForEntity($entity); if ($oldUrl !== $entity->getUrl()) { $this->referenceUpdater->updateEntityPageReferences($entity, $oldUrl); diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index 9a183469b..d491b7f2c 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -162,7 +162,6 @@ class PageRepo $this->baseRepo->update($draft, $input); $this->revisionRepo->storeNewForPage($draft, trans('entities.pages_initial_revision')); - $this->referenceStore->updateForPage($draft); $draft->refresh(); Activity::add(ActivityType::PAGE_CREATE, $draft); @@ -182,7 +181,6 @@ class PageRepo $this->updateTemplateStatusAndContentFromInput($page, $input); $this->baseRepo->update($page, $input); - $this->referenceStore->updateForPage($page); // Update with new details $page->revision_count++; @@ -301,7 +299,7 @@ class PageRepo $page->refreshSlug(); $page->save(); $page->indexForSearch(); - $this->referenceStore->updateForPage($page); + $this->referenceStore->updateForEntity($page); $summary = trans('entities.pages_revision_restored_from', ['id' => strval($revisionId), 'summary' => $revision->summary]); $this->revisionRepo->storeNewForPage($page, $summary); diff --git a/app/Entities/Tools/MixedEntityListLoader.php b/app/Entities/Tools/MixedEntityListLoader.php new file mode 100644 index 000000000..50079e3bf --- /dev/null +++ b/app/Entities/Tools/MixedEntityListLoader.php @@ -0,0 +1,103 @@ + ['id', 'name', 'slug', 'book_id', 'chapter_id', 'text', 'draft'], + 'chapter' => ['id', 'name', 'slug', 'book_id', 'description'], + 'book' => ['id', 'name', 'slug', 'description'], + 'bookshelf' => ['id', 'name', 'slug', 'description'], + ]; + + public function __construct( + protected EntityProvider $entityProvider + ) { + } + + /** + * Efficiently load in entities for listing onto the given list + * where entities are set as a relation via the given name. + * This will look for a model id and type via 'name_id' and 'name_type'. + * @param Model[] $relations + */ + public function loadIntoRelations(array $relations, string $relationName): void + { + $idsByType = []; + foreach ($relations as $relation) { + $type = $relation->getAttribute($relationName . '_type'); + $id = $relation->getAttribute($relationName . '_id'); + + if (!isset($idsByType[$type])) { + $idsByType[$type] = []; + } + + $idsByType[$type][] = $id; + } + + $modelMap = $this->idsByTypeToModelMap($idsByType); + + foreach ($relations as $relation) { + $type = $relation->getAttribute($relationName . '_type'); + $id = $relation->getAttribute($relationName . '_id'); + $related = $modelMap[$type][strval($id)] ?? null; + if ($related) { + $relation->setRelation($relationName, $related); + } + } + } + + /** + * @param array $idsByType + * @return array> + */ + protected function idsByTypeToModelMap(array $idsByType): array + { + $modelMap = []; + + foreach ($idsByType as $type => $ids) { + if (!isset($this->listAttributes[$type])) { + continue; + } + + $instance = $this->entityProvider->get($type); + $models = $instance->newQuery() + ->select($this->listAttributes[$type]) + ->scopes('visible') + ->whereIn('id', $ids) + ->with($this->getRelationsToEagerLoad($type)) + ->get(); + + if (count($models) > 0) { + $modelMap[$type] = []; + } + + foreach ($models as $model) { + $modelMap[$type][strval($model->id)] = $model; + } + } + + return $modelMap; + } + + protected function getRelationsToEagerLoad(string $type): array + { + $toLoad = []; + $loadVisible = fn (Relation $query) => $query->scopes('visible'); + + if ($type === 'chapter' || $type === 'page') { + $toLoad['book'] = $loadVisible; + } + + if ($type === 'page') { + $toLoad['chapter'] = $loadVisible; + } + + return $toLoad; + } +} diff --git a/app/References/ReferenceController.php b/app/References/ReferenceController.php index d6978dd5b..991f47225 100644 --- a/app/References/ReferenceController.php +++ b/app/References/ReferenceController.php @@ -10,11 +10,9 @@ use BookStack\Http\Controller; class ReferenceController extends Controller { - protected ReferenceFetcher $referenceFetcher; - - public function __construct(ReferenceFetcher $referenceFetcher) - { - $this->referenceFetcher = $referenceFetcher; + public function __construct( + protected ReferenceFetcher $referenceFetcher + ) { } /** @@ -23,7 +21,7 @@ class ReferenceController extends Controller public function page(string $bookSlug, string $pageSlug) { $page = Page::getBySlugs($bookSlug, $pageSlug); - $references = $this->referenceFetcher->getPageReferencesToEntity($page); + $references = $this->referenceFetcher->getReferencesToEntity($page); return view('pages.references', [ 'page' => $page, @@ -37,7 +35,7 @@ class ReferenceController extends Controller public function chapter(string $bookSlug, string $chapterSlug) { $chapter = Chapter::getBySlugs($bookSlug, $chapterSlug); - $references = $this->referenceFetcher->getPageReferencesToEntity($chapter); + $references = $this->referenceFetcher->getReferencesToEntity($chapter); return view('chapters.references', [ 'chapter' => $chapter, @@ -51,7 +49,7 @@ class ReferenceController extends Controller public function book(string $slug) { $book = Book::getBySlug($slug); - $references = $this->referenceFetcher->getPageReferencesToEntity($book); + $references = $this->referenceFetcher->getReferencesToEntity($book); return view('books.references', [ 'book' => $book, @@ -65,7 +63,7 @@ class ReferenceController extends Controller public function shelf(string $slug) { $shelf = Bookshelf::getBySlug($slug); - $references = $this->referenceFetcher->getPageReferencesToEntity($shelf); + $references = $this->referenceFetcher->getReferencesToEntity($shelf); return view('shelves.references', [ 'shelf' => $shelf, diff --git a/app/References/ReferenceFetcher.php b/app/References/ReferenceFetcher.php index c4a7d31b6..0d9883a3e 100644 --- a/app/References/ReferenceFetcher.php +++ b/app/References/ReferenceFetcher.php @@ -3,65 +3,51 @@ namespace BookStack\References; use BookStack\Entities\Models\Entity; -use BookStack\Entities\Models\Page; +use BookStack\Entities\Tools\MixedEntityListLoader; use BookStack\Permissions\PermissionApplicator; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; -use Illuminate\Database\Eloquent\Relations\Relation; class ReferenceFetcher { - protected PermissionApplicator $permissions; - - public function __construct(PermissionApplicator $permissions) - { - $this->permissions = $permissions; + public function __construct( + protected PermissionApplicator $permissions, + protected MixedEntityListLoader $mixedEntityListLoader, + ) { } /** - * Query and return the page references pointing to the given entity. + * Query and return the references pointing to the given entity. * Loads the commonly required relations while taking permissions into account. */ - public function getPageReferencesToEntity(Entity $entity): Collection + public function getReferencesToEntity(Entity $entity): Collection { - $baseQuery = $this->queryPageReferencesToEntity($entity) - ->with([ - 'from' => fn (Relation $query) => $query->select(Page::$listAttributes), - 'from.book' => fn (Relation $query) => $query->scopes('visible'), - 'from.chapter' => fn (Relation $query) => $query->scopes('visible'), - ]); - - $references = $this->permissions->restrictEntityRelationQuery( - $baseQuery, - 'references', - 'from_id', - 'from_type' - )->get(); + $references = $this->queryReferencesToEntity($entity)->get(); + $this->mixedEntityListLoader->loadIntoRelations($references->all(), 'from'); return $references; } /** - * Returns the count of page references pointing to the given entity. + * Returns the count of references pointing to the given entity. * Takes permissions into account. */ - public function getPageReferenceCountToEntity(Entity $entity): int + public function getReferenceCountToEntity(Entity $entity): int { - $count = $this->permissions->restrictEntityRelationQuery( - $this->queryPageReferencesToEntity($entity), + return $this->queryReferencesToEntity($entity)->count(); + } + + protected function queryReferencesToEntity(Entity $entity): Builder + { + $baseQuery = Reference::query() + ->where('to_type', '=', $entity->getMorphClass()) + ->where('to_id', '=', $entity->id); + + return $this->permissions->restrictEntityRelationQuery( + $baseQuery, 'references', 'from_id', 'from_type' - )->count(); - - return $count; - } - - protected function queryPageReferencesToEntity(Entity $entity): Builder - { - return Reference::query() - ->where('to_type', '=', $entity->getMorphClass()) - ->where('to_id', '=', $entity->id) - ->where('from_type', '=', (new Page())->getMorphClass()); + ); } } diff --git a/app/References/ReferenceStore.php b/app/References/ReferenceStore.php index 4c6db35c5..78595084b 100644 --- a/app/References/ReferenceStore.php +++ b/app/References/ReferenceStore.php @@ -2,60 +2,62 @@ namespace BookStack\References; -use BookStack\Entities\Models\Page; +use BookStack\Entities\EntityProvider; +use BookStack\Entities\Models\Entity; use Illuminate\Database\Eloquent\Collection; class ReferenceStore { - /** - * Update the outgoing references for the given page. - */ - public function updateForPage(Page $page): void - { - $this->updateForPages([$page]); + public function __construct( + protected EntityProvider $entityProvider + ) { } /** - * Update the outgoing references for all pages in the system. + * Update the outgoing references for the given entity. */ - public function updateForAllPages(): void + public function updateForEntity(Entity $entity): void { - Reference::query() - ->where('from_type', '=', (new Page())->getMorphClass()) - ->delete(); - - Page::query()->select(['id', 'html'])->chunk(100, function (Collection $pages) { - $this->updateForPages($pages->all()); - }); + $this->updateForEntities([$entity]); } /** - * Update the outgoing references for the pages in the given array. + * Update the outgoing references for all entities in the system. + */ + public function updateForAll(): void + { + Reference::query()->delete(); + + foreach ($this->entityProvider->all() as $entity) { + $entity->newQuery()->select(['id', $entity->htmlField])->chunk(100, function (Collection $entities) { + $this->updateForEntities($entities->all()); + }); + } + } + + /** + * Update the outgoing references for the entities in the given array. * - * @param Page[] $pages + * @param Entity[] $entities */ - protected function updateForPages(array $pages): void + protected function updateForEntities(array $entities): void { - if (count($pages) === 0) { + if (count($entities) === 0) { return; } $parser = CrossLinkParser::createWithEntityResolvers(); $references = []; - $pageIds = array_map(fn (Page $page) => $page->id, $pages); - Reference::query() - ->where('from_type', '=', $pages[0]->getMorphClass()) - ->whereIn('from_id', $pageIds) - ->delete(); + $this->dropReferencesFromEntities($entities); - foreach ($pages as $page) { - $models = $parser->extractLinkedModels($page->html); + foreach ($entities as $entity) { + $models = $parser->extractLinkedModels($entity->getAttribute($entity->htmlField)); foreach ($models as $model) { $references[] = [ - 'from_id' => $page->id, - 'from_type' => $page->getMorphClass(), + 'from_id' => $entity->id, + 'from_type' => $entity->getMorphClass(), 'to_id' => $model->id, 'to_type' => $model->getMorphClass(), ]; @@ -66,4 +68,29 @@ class ReferenceStore Reference::query()->insert($referenceDataChunk); } } + + /** + * Delete all the existing references originating from the given entities. + * @param Entity[] $entities + */ + protected function dropReferencesFromEntities(array $entities): void + { + $IdsByType = []; + + foreach ($entities as $entity) { + $type = $entity->getMorphClass(); + if (!isset($IdsByType[$type])) { + $IdsByType[$type] = []; + } + + $IdsByType[$type][] = $entity->id; + } + + foreach ($IdsByType as $type => $entityIds) { + Reference::query() + ->where('from_type', '=', $type) + ->whereIn('from_id', $entityIds) + ->delete(); + } + } } diff --git a/app/References/ReferenceUpdater.php b/app/References/ReferenceUpdater.php index 248937339..db1bce4fb 100644 --- a/app/References/ReferenceUpdater.php +++ b/app/References/ReferenceUpdater.php @@ -35,7 +35,7 @@ class ReferenceUpdater protected function getReferencesToUpdate(Entity $entity): array { /** @var Reference[] $references */ - $references = $this->referenceFetcher->getPageReferencesToEntity($entity)->values()->all(); + $references = $this->referenceFetcher->getReferencesToEntity($entity)->values()->all(); if ($entity instanceof Book) { $pages = $entity->pages()->get(['id']); @@ -43,7 +43,7 @@ class ReferenceUpdater $children = $pages->concat($chapters); foreach ($children as $bookChild) { /** @var Reference[] $childRefs */ - $childRefs = $this->referenceFetcher->getPageReferencesToEntity($bookChild)->values()->all(); + $childRefs = $this->referenceFetcher->getReferencesToEntity($bookChild)->values()->all(); array_push($references, ...$childRefs); } } diff --git a/app/Settings/MaintenanceController.php b/app/Settings/MaintenanceController.php index 60e5fee28..62eeecf39 100644 --- a/app/Settings/MaintenanceController.php +++ b/app/Settings/MaintenanceController.php @@ -87,7 +87,7 @@ class MaintenanceController extends Controller $this->logActivity(ActivityType::MAINTENANCE_ACTION_RUN, 'regenerate-references'); try { - $referenceStore->updateForAllPages(); + $referenceStore->updateForAll(); $this->showSuccessNotification(trans('settings.maint_regen_references_success')); } catch (\Exception $exception) { $this->showErrorNotification($exception->getMessage()); diff --git a/lang/en/entities.php b/lang/en/entities.php index 354eee42e..f1f915544 100644 --- a/lang/en/entities.php +++ b/lang/en/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Updated :timeLength', 'meta_updated_name' => 'Updated :timeLength by :user', 'meta_owned_name' => 'Owned by :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Entity Select', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Images', @@ -409,7 +409,7 @@ return [ // References 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/resources/views/entities/meta.blade.php b/resources/views/entities/meta.blade.php index 2298be8bb..9d3c4b956 100644 --- a/resources/views/entities/meta.blade.php +++ b/resources/views/entities/meta.blade.php @@ -64,7 +64,7 @@ @icon('reference')
- {!! trans_choice('entities.meta_reference_page_count', $referenceCount, ['count' => $referenceCount]) !!} + {{ trans_choice('entities.meta_reference_count', $referenceCount, ['count' => $referenceCount]) }}
@endif diff --git a/resources/views/shelves/show.blade.php b/resources/views/shelves/show.blade.php index 58fe1cd86..e475a8080 100644 --- a/resources/views/shelves/show.blade.php +++ b/resources/views/shelves/show.blade.php @@ -28,7 +28,7 @@
-

{!! nl2br(e($shelf->description)) !!}

+

{!! $shelf->descriptionHtml() !!}

@if(count($sortedVisibleShelfBooks) > 0) @if($view === 'list')
From bc354e8b12a19ed96eab669f0926f517671c7c3e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 18 Dec 2023 18:12:36 +0000 Subject: [PATCH 006/124] Input WYSIWYG: Updated reference link updating for descriptions --- app/Entities/Models/BookChild.php | 2 +- app/Entities/Repos/BaseRepo.php | 2 +- app/Entities/Repos/PageRepo.php | 2 +- app/References/ReferenceUpdater.php | 35 +++++++++++++++++++++++------ 4 files changed, 31 insertions(+), 10 deletions(-) diff --git a/app/Entities/Models/BookChild.php b/app/Entities/Models/BookChild.php index ed08f16e6..18735e56b 100644 --- a/app/Entities/Models/BookChild.php +++ b/app/Entities/Models/BookChild.php @@ -65,7 +65,7 @@ abstract class BookChild extends Entity $this->refresh(); if ($oldUrl !== $this->getUrl()) { - app()->make(ReferenceUpdater::class)->updateEntityPageReferences($this, $oldUrl); + app()->make(ReferenceUpdater::class)->updateEntityReferences($this, $oldUrl); } // Update all child pages if a chapter diff --git a/app/Entities/Repos/BaseRepo.php b/app/Entities/Repos/BaseRepo.php index dbdaa9213..3d3d16732 100644 --- a/app/Entities/Repos/BaseRepo.php +++ b/app/Entities/Repos/BaseRepo.php @@ -74,7 +74,7 @@ class BaseRepo $this->referenceStore->updateForEntity($entity); if ($oldUrl !== $entity->getUrl()) { - $this->referenceUpdater->updateEntityPageReferences($entity, $oldUrl); + $this->referenceUpdater->updateEntityReferences($entity, $oldUrl); } } diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index d491b7f2c..7b14ea7d2 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -305,7 +305,7 @@ class PageRepo $this->revisionRepo->storeNewForPage($page, $summary); if ($oldUrl !== $page->getUrl()) { - $this->referenceUpdater->updateEntityPageReferences($page, $oldUrl); + $this->referenceUpdater->updateEntityReferences($page, $oldUrl); } Activity::add(ActivityType::PAGE_RESTORE, $page); diff --git a/app/References/ReferenceUpdater.php b/app/References/ReferenceUpdater.php index db1bce4fb..db355f211 100644 --- a/app/References/ReferenceUpdater.php +++ b/app/References/ReferenceUpdater.php @@ -4,6 +4,7 @@ namespace BookStack\References; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Entity; +use BookStack\Entities\Models\HasHtmlDescription; use BookStack\Entities\Models\Page; use BookStack\Entities\Repos\RevisionRepo; use BookStack\Util\HtmlDocument; @@ -12,20 +13,19 @@ class ReferenceUpdater { public function __construct( protected ReferenceFetcher $referenceFetcher, - protected RevisionRepo $revisionRepo + protected RevisionRepo $revisionRepo, ) { } - public function updateEntityPageReferences(Entity $entity, string $oldLink) + public function updateEntityReferences(Entity $entity, string $oldLink): void { $references = $this->getReferencesToUpdate($entity); $newLink = $entity->getUrl(); - /** @var Reference $reference */ foreach ($references as $reference) { - /** @var Page $page */ - $page = $reference->from; - $this->updateReferencesWithinPage($page, $oldLink, $newLink); + /** @var Entity $entity */ + $entity = $reference->from; + $this->updateReferencesWithinEntity($entity, $oldLink, $newLink); } } @@ -57,7 +57,28 @@ class ReferenceUpdater return array_values($deduped); } - protected function updateReferencesWithinPage(Page $page, string $oldLink, string $newLink) + protected function updateReferencesWithinEntity(Entity $entity, string $oldLink, string $newLink): void + { + if ($entity instanceof Page) { + $this->updateReferencesWithinPage($entity, $oldLink, $newLink); + return; + } + + if (in_array(HasHtmlDescription::class, class_uses($entity))) { + $this->updateReferencesWithinDescription($entity, $oldLink, $newLink); + } + } + + protected function updateReferencesWithinDescription(Entity $entity, string $oldLink, string $newLink): void + { + /** @var HasHtmlDescription&Entity $entity */ + $entity = (clone $entity)->refresh(); + $html = $this->updateLinksInHtml($entity->description_html ?: '', $oldLink, $newLink); + $entity->description_html = $html; + $entity->save(); + } + + protected function updateReferencesWithinPage(Page $page, string $oldLink, string $newLink): void { $page = (clone $page)->refresh(); $html = $this->updateLinksInHtml($page->html, $oldLink, $newLink); From c07aa056c2e42974826285cd8e614ae49db24b4b Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 18 Dec 2023 18:31:16 +0000 Subject: [PATCH 007/124] Input WYSIWYG: Updated UpdateUrlCommand, Added chapter HTML display --- app/Console/Commands/UpdateUrlCommand.php | 3 +++ resources/views/chapters/show.blade.php | 2 +- tests/Commands/UpdateUrlCommandTest.php | 23 +++++++++++++++++++++++ 3 files changed, 27 insertions(+), 1 deletion(-) diff --git a/app/Console/Commands/UpdateUrlCommand.php b/app/Console/Commands/UpdateUrlCommand.php index 27f84cc89..0c95b0a3c 100644 --- a/app/Console/Commands/UpdateUrlCommand.php +++ b/app/Console/Commands/UpdateUrlCommand.php @@ -46,6 +46,9 @@ class UpdateUrlCommand extends Command $columnsToUpdateByTable = [ 'attachments' => ['path'], 'pages' => ['html', 'text', 'markdown'], + 'chapters' => ['description_html'], + 'books' => ['description_html'], + 'bookshelves' => ['description_html'], 'images' => ['url'], 'settings' => ['value'], 'comments' => ['html', 'text'], diff --git a/resources/views/chapters/show.blade.php b/resources/views/chapters/show.blade.php index 0e5224d54..6fe1ce431 100644 --- a/resources/views/chapters/show.blade.php +++ b/resources/views/chapters/show.blade.php @@ -24,7 +24,7 @@

{{ $chapter->name }}

-

{!! nl2br(e($chapter->description)) !!}

+

{!! $chapter->descriptionHtml() !!}

@if(count($pages) > 0)
@foreach($pages as $page) diff --git a/tests/Commands/UpdateUrlCommandTest.php b/tests/Commands/UpdateUrlCommandTest.php index 280c81feb..62c39c274 100644 --- a/tests/Commands/UpdateUrlCommandTest.php +++ b/tests/Commands/UpdateUrlCommandTest.php @@ -2,6 +2,7 @@ namespace Tests\Commands; +use BookStack\Entities\Models\Entity; use Illuminate\Support\Facades\Artisan; use Symfony\Component\Console\Exception\RuntimeException; use Tests\TestCase; @@ -24,6 +25,28 @@ class UpdateUrlCommandTest extends TestCase ]); } + public function test_command_updates_description_html() + { + /** @var Entity[] $models */ + $models = [$this->entities->book(), $this->entities->chapter(), $this->entities->shelf()]; + + foreach ($models as $model) { + $model->description_html = ''; + $model->save(); + } + + $this->artisan('bookstack:update-url https://example.com https://cats.example.com') + ->expectsQuestion("This will search for \"https://example.com\" in your database and replace it with \"https://cats.example.com\".\nAre you sure you want to proceed?", 'y') + ->expectsQuestion('This operation could cause issues if used incorrectly. Have you made a backup of your existing database?', 'y'); + + foreach ($models as $model) { + $this->assertDatabaseHas($model->getTable(), [ + 'id' => $model->id, + 'description_html' => '', + ]); + } + } + public function test_command_requires_valid_url() { $badUrlMessage = 'The given urls are expected to be full urls starting with http:// or https://'; From 2fbed3919bce377c7091c35503e8bea8cd1f9754 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 19 Dec 2023 12:09:57 +0000 Subject: [PATCH 008/124] Input WYSIWYG: Added dynamic options for entity selector popups So that multiple elements on the page can share the same popup, with different search options. --- .../js/components/entity-selector-popup.js | 9 ++++- resources/js/components/entity-selector.js | 40 ++++++++++++++++--- resources/js/components/page-picker.js | 6 +++ resources/js/markdown/actions.js | 6 ++- resources/js/wysiwyg/config.js | 6 ++- resources/js/wysiwyg/shortcuts.js | 6 ++- resources/views/books/parts/form.blade.php | 3 +- resources/views/chapters/parts/form.blade.php | 2 +- .../views/entities/selector-popup.blade.php | 2 +- resources/views/form/page-picker.blade.php | 3 +- .../views/settings/customization.blade.php | 11 +++-- resources/views/shelves/parts/form.blade.php | 2 +- 12 files changed, 79 insertions(+), 17 deletions(-) diff --git a/resources/js/components/entity-selector-popup.js b/resources/js/components/entity-selector-popup.js index 9ff67d53e..6fb461968 100644 --- a/resources/js/components/entity-selector-popup.js +++ b/resources/js/components/entity-selector-popup.js @@ -15,8 +15,15 @@ export class EntitySelectorPopup extends Component { window.$events.listen('entity-select-confirm', this.handleConfirmedSelection.bind(this)); } - show(callback, searchText = '') { + /** + * Show the selector popup. + * @param {Function} callback + * @param {String} searchText + * @param {EntitySelectorSearchOptions} searchOptions + */ + show(callback, searchText = '', searchOptions = {}) { this.callback = callback; + this.getSelector().configureSearchOptions(searchOptions); this.getPopup().show(); if (searchText) { diff --git a/resources/js/components/entity-selector.js b/resources/js/components/entity-selector.js index b12eeb402..5ad991437 100644 --- a/resources/js/components/entity-selector.js +++ b/resources/js/components/entity-selector.js @@ -1,6 +1,13 @@ import {onChildEvent} from '../services/dom'; import {Component} from './component'; +/** + * @typedef EntitySelectorSearchOptions + * @property entityTypes string + * @property entityPermission string + * @property searchEndpoint string + */ + /** * Entity Selector */ @@ -8,21 +15,35 @@ export class EntitySelector extends Component { setup() { this.elem = this.$el; - this.entityTypes = this.$opts.entityTypes || 'page,book,chapter'; - this.entityPermission = this.$opts.entityPermission || 'view'; - this.searchEndpoint = this.$opts.searchEndpoint || '/search/entity-selector'; this.input = this.$refs.input; this.searchInput = this.$refs.search; this.loading = this.$refs.loading; this.resultsContainer = this.$refs.results; + this.searchOptions = { + entityTypes: this.$opts.entityTypes || 'page,book,chapter', + entityPermission: this.$opts.entityPermission || 'view', + searchEndpoint: this.$opts.searchEndpoint || '', + }; + this.search = ''; this.lastClick = 0; this.setupListeners(); this.showLoading(); - this.initialLoad(); + + if (this.searchOptions.searchEndpoint) { + this.initialLoad(); + } + } + + /** + * @param {EntitySelectorSearchOptions} options + */ + configureSearchOptions(options) { + Object.assign(this.searchOptions, options); + this.reset(); } setupListeners() { @@ -103,6 +124,10 @@ export class EntitySelector extends Component { } initialLoad() { + if (!this.searchOptions.searchEndpoint) { + throw new Error('Search endpoint not set for entity-selector load'); + } + window.$http.get(this.searchUrl()).then(resp => { this.resultsContainer.innerHTML = resp.data; this.hideLoading(); @@ -110,10 +135,15 @@ export class EntitySelector extends Component { } searchUrl() { - return `${this.searchEndpoint}?types=${encodeURIComponent(this.entityTypes)}&permission=${encodeURIComponent(this.entityPermission)}`; + const query = `types=${encodeURIComponent(this.searchOptions.entityTypes)}&permission=${encodeURIComponent(this.searchOptions.entityPermission)}`; + return `${this.searchOptions.searchEndpoint}?${query}`; } searchEntities(searchTerm) { + if (!this.searchOptions.searchEndpoint) { + throw new Error('Search endpoint not set for entity-selector load'); + } + this.input.value = ''; const url = `${this.searchUrl()}&term=${encodeURIComponent(searchTerm)}`; window.$http.get(url).then(resp => { diff --git a/resources/js/components/page-picker.js b/resources/js/components/page-picker.js index 9bb0bee04..39af67229 100644 --- a/resources/js/components/page-picker.js +++ b/resources/js/components/page-picker.js @@ -14,6 +14,8 @@ export class PagePicker extends Component { this.defaultDisplay = this.$refs.defaultDisplay; this.buttonSep = this.$refs.buttonSeperator; + this.selectorEndpoint = this.$opts.selectorEndpoint; + this.value = this.input.value; this.setupListeners(); } @@ -33,6 +35,10 @@ export class PagePicker extends Component { const selectorPopup = window.$components.first('entity-selector-popup'); selectorPopup.show(entity => { this.setValue(entity.id, entity.name); + }, '', { + searchEndpoint: this.selectorEndpoint, + entityTypes: 'page', + entityPermission: 'view', }); } diff --git a/resources/js/markdown/actions.js b/resources/js/markdown/actions.js index 4909a59d0..511f1ebda 100644 --- a/resources/js/markdown/actions.js +++ b/resources/js/markdown/actions.js @@ -73,7 +73,11 @@ export class Actions { const selectedText = selectionText || entity.name; const newText = `[${selectedText}](${entity.link})`; this.#replaceSelection(newText, newText.length, selectionRange); - }, selectionText); + }, selectionText, { + searchEndpoint: '/search/entity-selector', + entityTypes: 'page,book,chapter,bookshelf', + entityPermission: 'view', + }); } // Show draw.io if enabled and handle save. diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index f0a2dbe1c..2bd4b630b 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -85,7 +85,11 @@ function filePickerCallback(callback, value, meta) { text: entity.name, title: entity.name, }); - }, selectionText); + }, selectionText, { + searchEndpoint: '/search/entity-selector', + entityTypes: 'page,book,chapter,bookshelf', + entityPermission: 'view', + }); } if (meta.filetype === 'image') { diff --git a/resources/js/wysiwyg/shortcuts.js b/resources/js/wysiwyg/shortcuts.js index 147e3c2d5..da9e02270 100644 --- a/resources/js/wysiwyg/shortcuts.js +++ b/resources/js/wysiwyg/shortcuts.js @@ -58,6 +58,10 @@ export function register(editor) { editor.selection.collapse(false); editor.focus(); - }, selectionText); + }, selectionText, { + searchEndpoint: '/search/entity-selector', + entityTypes: 'page,book,chapter,bookshelf', + entityPermission: 'view', + }); }); } diff --git a/resources/views/books/parts/form.blade.php b/resources/views/books/parts/form.blade.php index d380c5871..a3d4be5e9 100644 --- a/resources/views/books/parts/form.blade.php +++ b/resources/views/books/parts/form.blade.php @@ -53,6 +53,7 @@ 'name' => 'default_template_id', 'placeholder' => trans('entities.books_default_template_select'), 'value' => $book->default_template_id ?? null, + 'selectorEndpoint' => '/search/entity-selector-templates', ])
@@ -65,5 +66,5 @@
-@include('entities.selector-popup', ['entityTypes' => 'page', 'selectorEndpoint' => '/search/entity-selector-templates']) +@include('entities.selector-popup') @include('form.editor-translations') \ No newline at end of file diff --git a/resources/views/chapters/parts/form.blade.php b/resources/views/chapters/parts/form.blade.php index 7c565f43c..c6052c93a 100644 --- a/resources/views/chapters/parts/form.blade.php +++ b/resources/views/chapters/parts/form.blade.php @@ -27,5 +27,5 @@
-@include('entities.selector-popup', ['entityTypes' => 'page', 'selectorEndpoint' => '/search/entity-selector-templates']) +@include('entities.selector-popup') @include('form.editor-translations') \ No newline at end of file diff --git a/resources/views/entities/selector-popup.blade.php b/resources/views/entities/selector-popup.blade.php index d4c941e9a..ac91725d6 100644 --- a/resources/views/entities/selector-popup.blade.php +++ b/resources/views/entities/selector-popup.blade.php @@ -5,7 +5,7 @@
- @include('entities.selector', ['name' => 'entity-selector']) + @include('entities.selector', ['name' => 'entity-selector', 'selectorEndpoint' => '']) diff --git a/resources/views/form/page-picker.blade.php b/resources/views/form/page-picker.blade.php index d9810d575..ad0a9d516 100644 --- a/resources/views/form/page-picker.blade.php +++ b/resources/views/form/page-picker.blade.php @@ -1,6 +1,7 @@ {{--Depends on entity selector popup--}} -
+
diff --git a/resources/views/settings/customization.blade.php b/resources/views/settings/customization.blade.php index 7112ebcff..4845e2055 100644 --- a/resources/views/settings/customization.blade.php +++ b/resources/views/settings/customization.blade.php @@ -3,7 +3,7 @@ @section('card')

{{ trans('settings.app_customization') }}

- {!! csrf_field() !!} + {{ csrf_field() }}
@@ -133,7 +133,12 @@
@@ -168,5 +173,5 @@ @endsection @section('after-content') - @include('entities.selector-popup', ['entityTypes' => 'page']) + @include('entities.selector-popup') @endsection diff --git a/resources/views/shelves/parts/form.blade.php b/resources/views/shelves/parts/form.blade.php index a724c99ce..a75dd6ac1 100644 --- a/resources/views/shelves/parts/form.blade.php +++ b/resources/views/shelves/parts/form.blade.php @@ -89,5 +89,5 @@
-@include('entities.selector-popup', ['entityTypes' => 'page', 'selectorEndpoint' => '/search/entity-selector-templates']) +@include('entities.selector-popup') @include('form.editor-translations') \ No newline at end of file From 077b9709d44031605dabc470a567f4349f623b99 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 19 Dec 2023 12:55:51 +0000 Subject: [PATCH 009/124] Input WYSIWYG: Added testing for description references --- tests/References/ReferencesTest.php | 70 ++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/tests/References/ReferencesTest.php b/tests/References/ReferencesTest.php index a19e1b901..bc691b0cd 100644 --- a/tests/References/ReferencesTest.php +++ b/tests/References/ReferencesTest.php @@ -30,7 +30,30 @@ class ReferencesTest extends TestCase ]); } - public function test_references_deleted_on_entity_delete() + public function test_references_created_on_book_chapter_bookshelf_update() + { + $entities = [$this->entities->book(), $this->entities->chapter(), $this->entities->shelf()]; + $shelf = $this->entities->shelf(); + + foreach ($entities as $entity) { + $entity->refresh(); + $this->assertDatabaseMissing('references', ['from_id' => $entity->id, 'from_type' => $entity->getMorphClass()]); + + $this->asEditor()->put($entity->getUrl(), [ + 'name' => 'Reference test', + 'description_html' => 'Testing', + ]); + + $this->assertDatabaseHas('references', [ + 'from_id' => $entity->id, + 'from_type' => $entity->getMorphClass(), + 'to_id' => $shelf->id, + 'to_type' => $shelf->getMorphClass(), + ]); + } + } + + public function test_references_deleted_on_page_delete() { $pageA = $this->entities->page(); $pageB = $this->entities->page(); @@ -48,6 +71,25 @@ class ReferencesTest extends TestCase $this->assertDatabaseMissing('references', ['to_id' => $pageA->id, 'to_type' => $pageA->getMorphClass()]); } + public function test_references_from_deleted_on_book_chapter_shelf_delete() + { + $entities = [$this->entities->chapter(), $this->entities->book(), $this->entities->shelf()]; + $shelf = $this->entities->shelf(); + + foreach ($entities as $entity) { + $this->createReference($entity, $shelf); + $this->assertDatabaseHas('references', ['from_id' => $entity->id, 'from_type' => $entity->getMorphClass()]); + + $this->asEditor()->delete($entity->getUrl()); + app(TrashCan::class)->empty(); + + $this->assertDatabaseMissing('references', [ + 'from_id' => $entity->id, + 'from_type' => $entity->getMorphClass() + ]); + } + } + public function test_references_to_count_visible_on_entity_show_view() { $entities = $this->entities->all(); @@ -203,6 +245,32 @@ class ReferencesTest extends TestCase $this->assertEquals($expected, $page->markdown); } + public function test_description_links_from_book_chapter_shelf_updated_on_url_change() + { + $entities = [$this->entities->chapter(), $this->entities->book(), $this->entities->shelf()]; + $shelf = $this->entities->shelf(); + $this->asEditor(); + + foreach ($entities as $entity) { + $this->put($entity->getUrl(), [ + 'name' => 'Reference test', + 'description_html' => 'Testing', + ]); + } + + $oldUrl = $shelf->getUrl(); + $this->put($shelf->getUrl(), ['name' => 'My updated shelf link']); + $shelf->refresh(); + $this->assertNotEquals($oldUrl, $shelf->getUrl()); + + foreach ($entities as $entity) { + $oldHtml = $entity->description_html; + $entity->refresh(); + $this->assertNotEquals($oldHtml, $entity->description_html); + $this->assertStringContainsString($shelf->getUrl(), $entity->description_html); + } + } + protected function createReference(Model $from, Model $to) { (new Reference())->forceFill([ From 7fd6d5b2ccca0c4ea9740366da0dc8720dadc8ad Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 19 Dec 2023 15:10:29 +0000 Subject: [PATCH 010/124] Input WYSIWYG: Updated tests, Added simple html limiting --- app/Entities/Repos/BaseRepo.php | 3 +- app/Util/HtmlDescriptionFilter.php | 75 +++++++++++++++++++ .../factories/Entities/Models/BookFactory.php | 4 +- .../Entities/Models/BookshelfFactory.php | 4 +- .../Entities/Models/ChapterFactory.php | 4 +- resources/js/wysiwyg/config.js | 2 +- tests/Entity/BookShelfTest.php | 32 ++++---- tests/Entity/BookTest.php | 42 +++++++---- tests/Entity/ChapterTest.php | 4 +- 9 files changed, 134 insertions(+), 36 deletions(-) create mode 100644 app/Util/HtmlDescriptionFilter.php diff --git a/app/Entities/Repos/BaseRepo.php b/app/Entities/Repos/BaseRepo.php index 3d3d16732..b52f3974e 100644 --- a/app/Entities/Repos/BaseRepo.php +++ b/app/Entities/Repos/BaseRepo.php @@ -10,6 +10,7 @@ use BookStack\Exceptions\ImageUploadException; use BookStack\References\ReferenceStore; use BookStack\References\ReferenceUpdater; use BookStack\Uploads\ImageRepo; +use BookStack\Util\HtmlDescriptionFilter; use Illuminate\Http\UploadedFile; class BaseRepo @@ -111,7 +112,7 @@ class BaseRepo /** @var HasHtmlDescription $entity */ if (isset($input['description_html'])) { - $entity->description_html = $input['description_html']; + $entity->description_html = HtmlDescriptionFilter::filterFromString($input['description_html']); $entity->description = html_entity_decode(strip_tags($input['description_html'])); } else if (isset($input['description'])) { $entity->description = $input['description']; diff --git a/app/Util/HtmlDescriptionFilter.php b/app/Util/HtmlDescriptionFilter.php new file mode 100644 index 000000000..4494a73ce --- /dev/null +++ b/app/Util/HtmlDescriptionFilter.php @@ -0,0 +1,75 @@ + + */ + protected static array $allowedAttrsByElements = [ + 'p' => [], + 'a' => ['href', 'title'], + 'ol' => [], + 'ul' => [], + 'li' => [], + 'strong' => [], + 'em' => [], + 'br' => [], + ]; + + public static function filterFromString(string $html): string + { + $doc = new HtmlDocument($html); + + $topLevel = [...$doc->getBodyChildren()]; + foreach ($topLevel as $child) { + /** @var DOMNode $child */ + if ($child instanceof DOMElement) { + static::filterElement($child); + } else { + $child->parentNode->removeChild($child); + } + } + + return $doc->getBodyInnerHtml(); + } + + protected static function filterElement(DOMElement $element): void + { + $elType = strtolower($element->tagName); + $allowedAttrs = static::$allowedAttrsByElements[$elType] ?? null; + if (is_null($allowedAttrs)) { + $element->remove(); + return; + } + + /** @var DOMNamedNodeMap $attrs */ + $attrs = $element->attributes; + for ($i = $attrs->length - 1; $i >= 0; $i--) { + /** @var DOMAttr $attr */ + $attr = $attrs->item($i); + $name = strtolower($attr->name); + if (!in_array($name, $allowedAttrs)) { + $element->removeAttribute($attr->name); + } + } + + foreach ($element->childNodes as $child) { + if ($child instanceof DOMElement) { + static::filterElement($child); + } + } + } +} diff --git a/database/factories/Entities/Models/BookFactory.php b/database/factories/Entities/Models/BookFactory.php index 3bf157786..9cb8e971c 100644 --- a/database/factories/Entities/Models/BookFactory.php +++ b/database/factories/Entities/Models/BookFactory.php @@ -21,10 +21,12 @@ class BookFactory extends Factory */ public function definition() { + $description = $this->faker->paragraph(); return [ 'name' => $this->faker->sentence(), 'slug' => Str::random(10), - 'description' => $this->faker->paragraph(), + 'description' => $description, + 'description_html' => '

' . e($description) . '

' ]; } } diff --git a/database/factories/Entities/Models/BookshelfFactory.php b/database/factories/Entities/Models/BookshelfFactory.php index 66dd1c111..edbefc3e7 100644 --- a/database/factories/Entities/Models/BookshelfFactory.php +++ b/database/factories/Entities/Models/BookshelfFactory.php @@ -21,10 +21,12 @@ class BookshelfFactory extends Factory */ public function definition() { + $description = $this->faker->paragraph(); return [ 'name' => $this->faker->sentence, 'slug' => Str::random(10), - 'description' => $this->faker->paragraph, + 'description' => $description, + 'description_html' => '

' . e($description) . '

' ]; } } diff --git a/database/factories/Entities/Models/ChapterFactory.php b/database/factories/Entities/Models/ChapterFactory.php index 36379866e..1fc49933e 100644 --- a/database/factories/Entities/Models/ChapterFactory.php +++ b/database/factories/Entities/Models/ChapterFactory.php @@ -21,10 +21,12 @@ class ChapterFactory extends Factory */ public function definition() { + $description = $this->faker->paragraph(); return [ 'name' => $this->faker->sentence(), 'slug' => Str::random(10), - 'description' => $this->faker->paragraph(), + 'description' => $description, + 'description_html' => '

' . e($description) . '

' ]; } } diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index 2bd4b630b..f669849ad 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -329,7 +329,7 @@ export function buildForInput(options) { menubar: false, plugins: 'link autolink lists', contextmenu: false, - toolbar: 'bold italic underline link bullist numlist', + toolbar: 'bold italic link bullist numlist', content_style: getContentStyle(options), color_map: colorMap, file_picker_types: 'file', diff --git a/tests/Entity/BookShelfTest.php b/tests/Entity/BookShelfTest.php index c1842c175..7f6542d5c 100644 --- a/tests/Entity/BookShelfTest.php +++ b/tests/Entity/BookShelfTest.php @@ -77,8 +77,8 @@ class BookShelfTest extends TestCase { $booksToInclude = Book::take(2)->get(); $shelfInfo = [ - 'name' => 'My test book' . Str::random(4), - 'description' => 'Test book description ' . Str::random(10), + 'name' => 'My test shelf' . Str::random(4), + 'description_html' => '

Test book description ' . Str::random(10) . '

', ]; $resp = $this->asEditor()->post('/shelves', array_merge($shelfInfo, [ 'books' => $booksToInclude->implode('id', ','), @@ -96,7 +96,7 @@ class BookShelfTest extends TestCase $shelf = Bookshelf::where('name', '=', $shelfInfo['name'])->first(); $shelfPage = $this->get($shelf->getUrl()); $shelfPage->assertSee($shelfInfo['name']); - $shelfPage->assertSee($shelfInfo['description']); + $shelfPage->assertSee($shelfInfo['description_html'], false); $this->withHtml($shelfPage)->assertElementContains('.tag-item', 'Test Category'); $this->withHtml($shelfPage)->assertElementContains('.tag-item', 'Test Tag Value'); @@ -107,8 +107,8 @@ class BookShelfTest extends TestCase public function test_shelves_create_sets_cover_image() { $shelfInfo = [ - 'name' => 'My test book' . Str::random(4), - 'description' => 'Test book description ' . Str::random(10), + 'name' => 'My test shelf' . Str::random(4), + 'description_html' => '

Test book description ' . Str::random(10) . '

', ]; $imageFile = $this->files->uploadedImage('shelf-test.png'); @@ -174,7 +174,7 @@ class BookShelfTest extends TestCase // Set book ordering $this->asAdmin()->put($shelf->getUrl(), [ 'books' => $books->implode('id', ','), - 'tags' => [], 'description' => 'abc', 'name' => 'abc', + 'tags' => [], 'description_html' => 'abc', 'name' => 'abc', ]); $this->assertEquals(3, $shelf->books()->count()); $shelf->refresh(); @@ -207,7 +207,7 @@ class BookShelfTest extends TestCase // Set book ordering $this->asAdmin()->put($shelf->getUrl(), [ 'books' => $books->implode('id', ','), - 'tags' => [], 'description' => 'abc', 'name' => 'abc', + 'tags' => [], 'description_html' => 'abc', 'name' => 'abc', ]); $this->assertEquals(3, $shelf->books()->count()); $shelf->refresh(); @@ -229,8 +229,8 @@ class BookShelfTest extends TestCase $booksToInclude = Book::take(2)->get(); $shelfInfo = [ - 'name' => 'My test book' . Str::random(4), - 'description' => 'Test book description ' . Str::random(10), + 'name' => 'My test shelf' . Str::random(4), + 'description_html' => '

Test book description ' . Str::random(10) . '

', ]; $resp = $this->asEditor()->put($shelf->getUrl(), array_merge($shelfInfo, [ @@ -251,7 +251,7 @@ class BookShelfTest extends TestCase $shelfPage = $this->get($shelf->getUrl()); $shelfPage->assertSee($shelfInfo['name']); - $shelfPage->assertSee($shelfInfo['description']); + $shelfPage->assertSee($shelfInfo['description_html'], false); $this->withHtml($shelfPage)->assertElementContains('.tag-item', 'Test Category'); $this->withHtml($shelfPage)->assertElementContains('.tag-item', 'Test Tag Value'); @@ -270,8 +270,8 @@ class BookShelfTest extends TestCase $testName = 'Test Book in Shelf Name'; $createBookResp = $this->asEditor()->post($shelf->getUrl('/create-book'), [ - 'name' => $testName, - 'description' => 'Book in shelf description', + 'name' => $testName, + 'description_html' => 'Book in shelf description', ]); $createBookResp->assertRedirect(); @@ -372,8 +372,8 @@ class BookShelfTest extends TestCase { // Create shelf $shelfInfo = [ - 'name' => 'My test shelf' . Str::random(4), - 'description' => 'Test shelf description ' . Str::random(10), + 'name' => 'My test shelf' . Str::random(4), + 'description_html' => '

Test shelf description ' . Str::random(10) . '

', ]; $this->asEditor()->post('/shelves', $shelfInfo); @@ -381,8 +381,8 @@ class BookShelfTest extends TestCase // Create book and add to shelf $this->asEditor()->post($shelf->getUrl('/create-book'), [ - 'name' => 'Test book name', - 'description' => 'Book in shelf description', + 'name' => 'Test book name', + 'description_html' => '

Book in shelf description

', ]); $newBook = Book::query()->orderBy('id', 'desc')->first(); diff --git a/tests/Entity/BookTest.php b/tests/Entity/BookTest.php index 833cabaae..3e37e61f7 100644 --- a/tests/Entity/BookTest.php +++ b/tests/Entity/BookTest.php @@ -22,7 +22,7 @@ class BookTest extends TestCase $resp = $this->get('/create-book'); $this->withHtml($resp)->assertElementContains('form[action="' . url('/books') . '"][method="POST"]', 'Save Book'); - $resp = $this->post('/books', $book->only('name', 'description')); + $resp = $this->post('/books', $book->only('name', 'description_html')); $resp->assertRedirect('/books/my-first-book'); $resp = $this->get('/books/my-first-book'); @@ -36,8 +36,8 @@ class BookTest extends TestCase 'name' => 'My First Book', ]); - $this->asEditor()->post('/books', $book->only('name', 'description')); - $this->asEditor()->post('/books', $book->only('name', 'description')); + $this->asEditor()->post('/books', $book->only('name', 'description_html')); + $this->asEditor()->post('/books', $book->only('name', 'description_html')); $books = Book::query()->where('name', '=', $book->name) ->orderBy('id', 'desc') @@ -52,9 +52,9 @@ class BookTest extends TestCase { // Cheeky initial update to refresh slug $this->asEditor()->post('books', [ - 'name' => 'My book with tags', - 'description' => 'A book with tags', - 'tags' => [ + 'name' => 'My book with tags', + 'description_html' => '

A book with tags

', + 'tags' => [ [ 'name' => 'Category', 'value' => 'Donkey Content', @@ -79,23 +79,23 @@ class BookTest extends TestCase { $book = $this->entities->book(); // Cheeky initial update to refresh slug - $this->asEditor()->put($book->getUrl(), ['name' => $book->name . '5', 'description' => $book->description]); + $this->asEditor()->put($book->getUrl(), ['name' => $book->name . '5', 'description_html' => $book->description_html]); $book->refresh(); $newName = $book->name . ' Updated'; - $newDesc = $book->description . ' with more content'; + $newDesc = $book->description_html . '

with more content

'; $resp = $this->get($book->getUrl('/edit')); $resp->assertSee($book->name); - $resp->assertSee($book->description); + $resp->assertSee($book->description_html); $this->withHtml($resp)->assertElementContains('form[action="' . $book->getUrl() . '"]', 'Save Book'); - $resp = $this->put($book->getUrl(), ['name' => $newName, 'description' => $newDesc]); + $resp = $this->put($book->getUrl(), ['name' => $newName, 'description_html' => $newDesc]); $resp->assertRedirect($book->getUrl() . '-updated'); $resp = $this->get($book->getUrl() . '-updated'); $resp->assertSee($newName); - $resp->assertSee($newDesc); + $resp->assertSee($newDesc, false); } public function test_update_sets_tags() @@ -184,7 +184,7 @@ class BookTest extends TestCase public function test_recently_viewed_books_updates_as_expected() { - $books = Book::all()->take(2); + $books = Book::take(2)->get(); $resp = $this->asAdmin()->get('/books'); $this->withHtml($resp)->assertElementNotContains('#recents', $books[0]->name) @@ -200,7 +200,7 @@ class BookTest extends TestCase public function test_popular_books_updates_upon_visits() { - $books = Book::all()->take(2); + $books = Book::take(2)->get(); $resp = $this->asAdmin()->get('/books'); $this->withHtml($resp)->assertElementNotContains('#popular', $books[0]->name) @@ -262,6 +262,22 @@ class BookTest extends TestCase $this->assertEquals('parta-partb-partc', $book->slug); } + public function test_description_limited_to_specific_html() + { + $book = $this->entities->book(); + + $input = '

Test

Contenta

Hello

'; + $expected = '

Contenta

'; + + $this->asEditor()->put($book->getUrl(), [ + 'name' => $book->name, + 'description_html' => $input + ]); + + $book->refresh(); + $this->assertEquals($expected, $book->description_html); + } + public function test_show_view_has_copy_button() { $book = $this->entities->book(); diff --git a/tests/Entity/ChapterTest.php b/tests/Entity/ChapterTest.php index 7fa32c252..a057d91b5 100644 --- a/tests/Entity/ChapterTest.php +++ b/tests/Entity/ChapterTest.php @@ -23,12 +23,12 @@ class ChapterTest extends TestCase $resp = $this->get($book->getUrl('/create-chapter')); $this->withHtml($resp)->assertElementContains('form[action="' . $book->getUrl('/create-chapter') . '"][method="POST"]', 'Save Chapter'); - $resp = $this->post($book->getUrl('/create-chapter'), $chapter->only('name', 'description')); + $resp = $this->post($book->getUrl('/create-chapter'), $chapter->only('name', 'description_html')); $resp->assertRedirect($book->getUrl('/chapter/my-first-chapter')); $resp = $this->get($book->getUrl('/chapter/my-first-chapter')); $resp->assertSee($chapter->name); - $resp->assertSee($chapter->description); + $resp->assertSee($chapter->description_html, false); } public function test_delete() From a21ca446332a7020e54bea15171d4b6233ea3ab8 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 20 Dec 2023 17:21:09 +0000 Subject: [PATCH 011/124] Input WYSIWYG: Fixed existing tests, fixed empty description handling --- app/Util/HtmlDescriptionFilter.php | 4 ++++ database/seeders/DummyContentSeeder.php | 5 +++-- tests/Api/SearchApiTest.php | 2 +- tests/Entity/BookTest.php | 2 ++ tests/Entity/ConvertTest.php | 2 ++ tests/References/ReferencesTest.php | 6 +++--- tests/Settings/RegenerateReferencesTest.php | 4 ++-- 7 files changed, 17 insertions(+), 8 deletions(-) diff --git a/app/Util/HtmlDescriptionFilter.php b/app/Util/HtmlDescriptionFilter.php index 4494a73ce..7287586d1 100644 --- a/app/Util/HtmlDescriptionFilter.php +++ b/app/Util/HtmlDescriptionFilter.php @@ -31,6 +31,10 @@ class HtmlDescriptionFilter public static function filterFromString(string $html): string { + if (empty(trim($html))) { + return ''; + } + $doc = new HtmlDocument($html); $topLevel = [...$doc->getBodyChildren()]; diff --git a/database/seeders/DummyContentSeeder.php b/database/seeders/DummyContentSeeder.php index 47e8d1d7c..a4383be50 100644 --- a/database/seeders/DummyContentSeeder.php +++ b/database/seeders/DummyContentSeeder.php @@ -3,6 +3,7 @@ namespace Database\Seeders; use BookStack\Api\ApiToken; +use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Page; @@ -38,7 +39,7 @@ class DummyContentSeeder extends Seeder $byData = ['created_by' => $editorUser->id, 'updated_by' => $editorUser->id, 'owned_by' => $editorUser->id]; - \BookStack\Entities\Models\Book::factory()->count(5)->create($byData) + Book::factory()->count(5)->create($byData) ->each(function ($book) use ($byData) { $chapters = Chapter::factory()->count(3)->create($byData) ->each(function ($chapter) use ($book, $byData) { @@ -50,7 +51,7 @@ class DummyContentSeeder extends Seeder $book->pages()->saveMany($pages); }); - $largeBook = \BookStack\Entities\Models\Book::factory()->create(array_merge($byData, ['name' => 'Large book' . Str::random(10)])); + $largeBook = Book::factory()->create(array_merge($byData, ['name' => 'Large book' . Str::random(10)])); $pages = Page::factory()->count(200)->make($byData); $chapters = Chapter::factory()->count(50)->make($byData); $largeBook->pages()->saveMany($pages); diff --git a/tests/Api/SearchApiTest.php b/tests/Api/SearchApiTest.php index cdc954ec3..2a186e8d6 100644 --- a/tests/Api/SearchApiTest.php +++ b/tests/Api/SearchApiTest.php @@ -52,7 +52,7 @@ class SearchApiTest extends TestCase public function test_all_endpoint_returns_items_with_preview_html() { $book = $this->entities->book(); - $book->update(['name' => 'name with superuniquevalue within', 'description' => 'Description with superuniquevalue within']); + $book->forceFill(['name' => 'name with superuniquevalue within', 'description' => 'Description with superuniquevalue within'])->save(); $book->indexForSearch(); $resp = $this->actingAsApiAdmin()->getJson($this->baseEndpoint . '?query=superuniquevalue'); diff --git a/tests/Entity/BookTest.php b/tests/Entity/BookTest.php index 3e37e61f7..c4872785b 100644 --- a/tests/Entity/BookTest.php +++ b/tests/Entity/BookTest.php @@ -307,6 +307,8 @@ class BookTest extends TestCase $resp->assertRedirect($copy->getUrl()); $this->assertEquals($book->getDirectChildren()->count(), $copy->getDirectChildren()->count()); + + $this->get($copy->getUrl())->assertSee($book->description_html, false); } public function test_copy_does_not_copy_non_visible_content() diff --git a/tests/Entity/ConvertTest.php b/tests/Entity/ConvertTest.php index decda5224..d9b1ee466 100644 --- a/tests/Entity/ConvertTest.php +++ b/tests/Entity/ConvertTest.php @@ -42,6 +42,7 @@ class ConvertTest extends TestCase $this->assertEquals('Penguins', $newBook->tags->first()->value); $this->assertEquals($chapter->name, $newBook->name); $this->assertEquals($chapter->description, $newBook->description); + $this->assertEquals($chapter->description_html, $newBook->description_html); $this->assertActivityExists(ActivityType::BOOK_CREATE_FROM_CHAPTER, $newBook); } @@ -105,6 +106,7 @@ class ConvertTest extends TestCase $this->assertEquals('Ducks', $newShelf->tags->first()->value); $this->assertEquals($book->name, $newShelf->name); $this->assertEquals($book->description, $newShelf->description); + $this->assertEquals($book->description_html, $newShelf->description_html); $this->assertEquals($newShelf->books()->count(), $bookChapterCount + 1); $this->assertEquals($systemBookCount + $bookChapterCount, Book::query()->count()); $this->assertActivityExists(ActivityType::BOOKSHELF_CREATE_FROM_BOOK, $newShelf); diff --git a/tests/References/ReferencesTest.php b/tests/References/ReferencesTest.php index bc691b0cd..715f71435 100644 --- a/tests/References/ReferencesTest.php +++ b/tests/References/ReferencesTest.php @@ -102,13 +102,13 @@ class ReferencesTest extends TestCase foreach ($entities as $entity) { $resp = $this->get($entity->getUrl()); - $resp->assertSee('Referenced on 1 page'); - $resp->assertDontSee('Referenced on 1 pages'); + $resp->assertSee('Referenced by 1 item'); + $resp->assertDontSee('Referenced by 1 items'); } $this->createReference($otherPage, $entities['page']); $resp = $this->get($entities['page']->getUrl()); - $resp->assertSee('Referenced on 2 pages'); + $resp->assertSee('Referenced by 2 items'); } public function test_references_to_visible_on_references_page() diff --git a/tests/Settings/RegenerateReferencesTest.php b/tests/Settings/RegenerateReferencesTest.php index 25832b03e..0511f2624 100644 --- a/tests/Settings/RegenerateReferencesTest.php +++ b/tests/Settings/RegenerateReferencesTest.php @@ -21,7 +21,7 @@ class RegenerateReferencesTest extends TestCase public function test_action_runs_reference_regen() { $this->mock(ReferenceStore::class) - ->shouldReceive('updateForAllPages') + ->shouldReceive('updateForAll') ->once(); $resp = $this->asAdmin()->post('/settings/maintenance/regenerate-references'); @@ -45,7 +45,7 @@ class RegenerateReferencesTest extends TestCase public function test_action_failed_shown_as_error_notification() { $this->mock(ReferenceStore::class) - ->shouldReceive('updateForAllPages') + ->shouldReceive('updateForAll') ->andThrow(\Exception::class, 'A badger stopped the task'); $resp = $this->asAdmin()->post('/settings/maintenance/regenerate-references'); From ed5d67e6095639e1bd8a238bf77227ce2b97a5f8 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 20 Dec 2023 17:40:58 +0000 Subject: [PATCH 012/124] Input WYSIWYG: Aligned newline handling with old descriptions To ensure consistenent behaviour before/after changes. Added tests to cover. --- app/Entities/Models/HasHtmlDescription.php | 2 +- resources/views/books/show.blade.php | 2 +- resources/views/chapters/show.blade.php | 2 +- resources/views/shelves/show.blade.php | 2 +- tests/Entity/BookShelfTest.php | 11 +++++++++++ tests/Entity/BookTest.php | 11 +++++++++++ tests/Entity/ChapterTest.php | 11 +++++++++++ 7 files changed, 37 insertions(+), 4 deletions(-) diff --git a/app/Entities/Models/HasHtmlDescription.php b/app/Entities/Models/HasHtmlDescription.php index cc431f7fc..c9f08616d 100644 --- a/app/Entities/Models/HasHtmlDescription.php +++ b/app/Entities/Models/HasHtmlDescription.php @@ -15,7 +15,7 @@ trait HasHtmlDescription */ public function descriptionHtml(): string { - $html = $this->description_html ?: '

' . e($this->description) . '

'; + $html = $this->description_html ?: '

' . nl2br(e($this->description)) . '

'; return HtmlContentFilter::removeScriptsFromHtmlString($html); } } diff --git a/resources/views/books/show.blade.php b/resources/views/books/show.blade.php index 5884e41fd..dbb09fc9e 100644 --- a/resources/views/books/show.blade.php +++ b/resources/views/books/show.blade.php @@ -26,7 +26,7 @@

{{$book->name}}

-

{!! $book->descriptionHtml() !!}

+
{!! $book->descriptionHtml() !!}
@if(count($bookChildren) > 0)
@foreach($bookChildren as $childElement) diff --git a/resources/views/chapters/show.blade.php b/resources/views/chapters/show.blade.php index 6fe1ce431..45e43ad96 100644 --- a/resources/views/chapters/show.blade.php +++ b/resources/views/chapters/show.blade.php @@ -24,7 +24,7 @@

{{ $chapter->name }}

-

{!! $chapter->descriptionHtml() !!}

+
{!! $chapter->descriptionHtml() !!}
@if(count($pages) > 0)
@foreach($pages as $page) diff --git a/resources/views/shelves/show.blade.php b/resources/views/shelves/show.blade.php index e475a8080..11baccaf4 100644 --- a/resources/views/shelves/show.blade.php +++ b/resources/views/shelves/show.blade.php @@ -28,7 +28,7 @@
-

{!! $shelf->descriptionHtml() !!}

+
{!! $shelf->descriptionHtml() !!}
@if(count($sortedVisibleShelfBooks) > 0) @if($view === 'list')
diff --git a/tests/Entity/BookShelfTest.php b/tests/Entity/BookShelfTest.php index 7f6542d5c..fb9862931 100644 --- a/tests/Entity/BookShelfTest.php +++ b/tests/Entity/BookShelfTest.php @@ -403,4 +403,15 @@ class BookShelfTest extends TestCase $resp = $this->asEditor()->get($shelf->getUrl('/create-book')); $this->withHtml($resp)->assertElementContains('form a[href="' . $shelf->getUrl() . '"]', 'Cancel'); } + + public function test_show_view_displays_description_if_no_description_html_set() + { + $shelf = $this->entities->shelf(); + $shelf->description_html = ''; + $shelf->description = "My great\ndescription\n\nwith newlines"; + $shelf->save(); + + $resp = $this->asEditor()->get($shelf->getUrl()); + $resp->assertSee("

My great
\ndescription
\n
\nwith newlines

", false); + } } diff --git a/tests/Entity/BookTest.php b/tests/Entity/BookTest.php index c4872785b..374089246 100644 --- a/tests/Entity/BookTest.php +++ b/tests/Entity/BookTest.php @@ -278,6 +278,17 @@ class BookTest extends TestCase $this->assertEquals($expected, $book->description_html); } + public function test_show_view_displays_description_if_no_description_html_set() + { + $book = $this->entities->book(); + $book->description_html = ''; + $book->description = "My great\ndescription\n\nwith newlines"; + $book->save(); + + $resp = $this->asEditor()->get($book->getUrl()); + $resp->assertSee("

My great
\ndescription
\n
\nwith newlines

", false); + } + public function test_show_view_has_copy_button() { $book = $this->entities->book(); diff --git a/tests/Entity/ChapterTest.php b/tests/Entity/ChapterTest.php index a057d91b5..1577cee76 100644 --- a/tests/Entity/ChapterTest.php +++ b/tests/Entity/ChapterTest.php @@ -31,6 +31,17 @@ class ChapterTest extends TestCase $resp->assertSee($chapter->description_html, false); } + public function test_show_view_displays_description_if_no_description_html_set() + { + $chapter = $this->entities->chapter(); + $chapter->description_html = ''; + $chapter->description = "My great\ndescription\n\nwith newlines"; + $chapter->save(); + + $resp = $this->asEditor()->get($chapter->getUrl()); + $resp->assertSee("

My great
\ndescription
\n
\nwith newlines

", false); + } + public function test_delete() { $chapter = Chapter::query()->whereHas('pages')->first(); From 00ae04e0bd7f9d80aa80334b8a8bb55e7ca33cad Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 21 Dec 2023 13:23:52 +0000 Subject: [PATCH 013/124] Input WYSIWYG: Updated API to show/accept html descriptions Also aligned books, shelves and chapters to return description content and some relations (where not breaking API) in create/update responses also so that information can be seen direct from that input in a request. API docs and tests not yet updated to match. --- .../Controllers/BookApiController.php | 40 +++++++++----- .../Controllers/BookshelfApiController.php | 54 ++++++++++++------- .../Controllers/ChapterApiController.php | 52 ++++++++++++------ app/Entities/Models/Book.php | 2 +- app/Entities/Models/Bookshelf.php | 2 +- app/Entities/Models/Chapter.php | 2 +- dev/api/responses/books-read.json | 1 + dev/api/responses/chapters-read.json | 1 + dev/api/responses/shelves-read.json | 1 + 9 files changed, 103 insertions(+), 52 deletions(-) diff --git a/app/Entities/Controllers/BookApiController.php b/app/Entities/Controllers/BookApiController.php index 41ff11dde..aa21aea47 100644 --- a/app/Entities/Controllers/BookApiController.php +++ b/app/Entities/Controllers/BookApiController.php @@ -45,7 +45,7 @@ class BookApiController extends ApiController $book = $this->bookRepo->create($requestData); - return response()->json($book); + return response()->json($this->forJsonDisplay($book)); } /** @@ -56,9 +56,9 @@ class BookApiController extends ApiController */ public function read(string $id) { - $book = Book::visible() - ->with(['tags', 'cover', 'createdBy', 'updatedBy', 'ownedBy']) - ->findOrFail($id); + $book = Book::visible()->findOrFail($id); + $book = $this->forJsonDisplay($book); + $book->load(['createdBy', 'updatedBy', 'ownedBy']); $contents = (new BookContents($book))->getTree(true, false)->all(); $contentsApiData = (new ApiEntityListFormatter($contents)) @@ -89,7 +89,7 @@ class BookApiController extends ApiController $requestData = $this->validate($request, $this->rules()['update']); $book = $this->bookRepo->update($book, $requestData); - return response()->json($book); + return response()->json($this->forJsonDisplay($book)); } /** @@ -108,21 +108,35 @@ class BookApiController extends ApiController return response('', 204); } + protected function forJsonDisplay(Book $book): Book + { + $book = clone $book; + $book->unsetRelations()->refresh(); + + $book->load(['tags', 'cover']); + $book->makeVisible('description_html') + ->setAttribute('description_html', $book->descriptionHtml()); + + return $book; + } + protected function rules(): array { return [ 'create' => [ - 'name' => ['required', 'string', 'max:255'], - 'description' => ['string', 'max:1000'], - 'tags' => ['array'], - 'image' => array_merge(['nullable'], $this->getImageValidationRules()), + 'name' => ['required', 'string', 'max:255'], + 'description' => ['string', 'max:1900'], + 'description_html' => ['string', 'max:2000'], + 'tags' => ['array'], + 'image' => array_merge(['nullable'], $this->getImageValidationRules()), 'default_template_id' => ['nullable', 'integer'], ], 'update' => [ - 'name' => ['string', 'min:1', 'max:255'], - 'description' => ['string', 'max:1000'], - 'tags' => ['array'], - 'image' => array_merge(['nullable'], $this->getImageValidationRules()), + 'name' => ['string', 'min:1', 'max:255'], + 'description' => ['string', 'max:1900'], + 'description_html' => ['string', 'max:2000'], + 'tags' => ['array'], + 'image' => array_merge(['nullable'], $this->getImageValidationRules()), 'default_template_id' => ['nullable', 'integer'], ], ]; diff --git a/app/Entities/Controllers/BookshelfApiController.php b/app/Entities/Controllers/BookshelfApiController.php index 9bdb8256d..a12dc90ac 100644 --- a/app/Entities/Controllers/BookshelfApiController.php +++ b/app/Entities/Controllers/BookshelfApiController.php @@ -12,11 +12,9 @@ use Illuminate\Validation\ValidationException; class BookshelfApiController extends ApiController { - protected BookshelfRepo $bookshelfRepo; - - public function __construct(BookshelfRepo $bookshelfRepo) - { - $this->bookshelfRepo = $bookshelfRepo; + public function __construct( + protected BookshelfRepo $bookshelfRepo + ) { } /** @@ -48,7 +46,7 @@ class BookshelfApiController extends ApiController $bookIds = $request->get('books', []); $shelf = $this->bookshelfRepo->create($requestData, $bookIds); - return response()->json($shelf); + return response()->json($this->forJsonDisplay($shelf)); } /** @@ -56,12 +54,14 @@ class BookshelfApiController extends ApiController */ public function read(string $id) { - $shelf = Bookshelf::visible()->with([ - 'tags', 'cover', 'createdBy', 'updatedBy', 'ownedBy', + $shelf = Bookshelf::visible()->findOrFail($id); + $shelf = $this->forJsonDisplay($shelf); + $shelf->load([ + 'createdBy', 'updatedBy', 'ownedBy', 'books' => function (BelongsToMany $query) { $query->scopes('visible')->get(['id', 'name', 'slug']); }, - ])->findOrFail($id); + ]); return response()->json($shelf); } @@ -86,7 +86,7 @@ class BookshelfApiController extends ApiController $shelf = $this->bookshelfRepo->update($shelf, $requestData, $bookIds); - return response()->json($shelf); + return response()->json($this->forJsonDisplay($shelf)); } /** @@ -105,22 +105,36 @@ class BookshelfApiController extends ApiController return response('', 204); } + protected function forJsonDisplay(Bookshelf $shelf): Bookshelf + { + $shelf = clone $shelf; + $shelf->unsetRelations()->refresh(); + + $shelf->load(['tags', 'cover']); + $shelf->makeVisible('description_html') + ->setAttribute('description_html', $shelf->descriptionHtml()); + + return $shelf; + } + protected function rules(): array { return [ 'create' => [ - 'name' => ['required', 'string', 'max:255'], - 'description' => ['string', 'max:1000'], - 'books' => ['array'], - 'tags' => ['array'], - 'image' => array_merge(['nullable'], $this->getImageValidationRules()), + 'name' => ['required', 'string', 'max:255'], + 'description' => ['string', 'max:1900'], + 'description_html' => ['string', 'max:2000'], + 'books' => ['array'], + 'tags' => ['array'], + 'image' => array_merge(['nullable'], $this->getImageValidationRules()), ], 'update' => [ - 'name' => ['string', 'min:1', 'max:255'], - 'description' => ['string', 'max:1000'], - 'books' => ['array'], - 'tags' => ['array'], - 'image' => array_merge(['nullable'], $this->getImageValidationRules()), + 'name' => ['string', 'min:1', 'max:255'], + 'description' => ['string', 'max:1900'], + 'description_html' => ['string', 'max:2000'], + 'books' => ['array'], + 'tags' => ['array'], + 'image' => array_merge(['nullable'], $this->getImageValidationRules()), ], ]; } diff --git a/app/Entities/Controllers/ChapterApiController.php b/app/Entities/Controllers/ChapterApiController.php index 7f01e445a..c21323262 100644 --- a/app/Entities/Controllers/ChapterApiController.php +++ b/app/Entities/Controllers/ChapterApiController.php @@ -15,18 +15,20 @@ class ChapterApiController extends ApiController { protected $rules = [ 'create' => [ - 'book_id' => ['required', 'integer'], - 'name' => ['required', 'string', 'max:255'], - 'description' => ['string', 'max:1000'], - 'tags' => ['array'], - 'priority' => ['integer'], + 'book_id' => ['required', 'integer'], + 'name' => ['required', 'string', 'max:255'], + 'description' => ['string', 'max:1900'], + 'description_html' => ['string', 'max:2000'], + 'tags' => ['array'], + 'priority' => ['integer'], ], 'update' => [ - 'book_id' => ['integer'], - 'name' => ['string', 'min:1', 'max:255'], - 'description' => ['string', 'max:1000'], - 'tags' => ['array'], - 'priority' => ['integer'], + 'book_id' => ['integer'], + 'name' => ['string', 'min:1', 'max:255'], + 'description' => ['string', 'max:1900'], + 'description_html' => ['string', 'max:2000'], + 'tags' => ['array'], + 'priority' => ['integer'], ], ]; @@ -61,7 +63,7 @@ class ChapterApiController extends ApiController $chapter = $this->chapterRepo->create($requestData, $book); - return response()->json($chapter->load(['tags'])); + return response()->json($this->forJsonDisplay($chapter)); } /** @@ -69,9 +71,15 @@ class ChapterApiController extends ApiController */ public function read(string $id) { - $chapter = Chapter::visible()->with(['tags', 'createdBy', 'updatedBy', 'ownedBy', 'pages' => function (HasMany $query) { - $query->scopes('visible')->get(['id', 'name', 'slug']); - }])->findOrFail($id); + $chapter = Chapter::visible()->findOrFail($id); + $chapter = $this->forJsonDisplay($chapter); + + $chapter->load([ + 'createdBy', 'updatedBy', 'ownedBy', + 'pages' => function (HasMany $query) { + $query->scopes('visible')->get(['id', 'name', 'slug']); + } + ]); return response()->json($chapter); } @@ -93,7 +101,7 @@ class ChapterApiController extends ApiController try { $this->chapterRepo->move($chapter, "book:{$requestData['book_id']}"); } catch (Exception $exception) { - if ($exception instanceof PermissionsException) { + if ($exception instanceof PermissionsException) { $this->showPermissionError(); } @@ -103,7 +111,7 @@ class ChapterApiController extends ApiController $updatedChapter = $this->chapterRepo->update($chapter, $requestData); - return response()->json($updatedChapter->load(['tags'])); + return response()->json($this->forJsonDisplay($updatedChapter)); } /** @@ -119,4 +127,16 @@ class ChapterApiController extends ApiController return response('', 204); } + + protected function forJsonDisplay(Chapter $chapter): Chapter + { + $chapter = clone $chapter; + $chapter->unsetRelations()->refresh(); + + $chapter->load(['tags']); + $chapter->makeVisible('description_html') + ->setAttribute('description_html', $chapter->descriptionHtml()); + + return $chapter; + } } diff --git a/app/Entities/Models/Book.php b/app/Entities/Models/Book.php index 52674d839..14cb790c5 100644 --- a/app/Entities/Models/Book.php +++ b/app/Entities/Models/Book.php @@ -31,7 +31,7 @@ class Book extends Entity implements HasCoverImage public float $searchFactor = 1.2; protected $fillable = ['name']; - protected $hidden = ['pivot', 'image_id', 'deleted_at']; + protected $hidden = ['pivot', 'image_id', 'deleted_at', 'description_html']; /** * Get the url for this book. diff --git a/app/Entities/Models/Bookshelf.php b/app/Entities/Models/Bookshelf.php index 464c127b8..9ffa0ea9c 100644 --- a/app/Entities/Models/Bookshelf.php +++ b/app/Entities/Models/Bookshelf.php @@ -19,7 +19,7 @@ class Bookshelf extends Entity implements HasCoverImage protected $fillable = ['name', 'description', 'image_id']; - protected $hidden = ['image_id', 'deleted_at']; + protected $hidden = ['image_id', 'deleted_at', 'description_html']; /** * Get the books in this shelf. diff --git a/app/Entities/Models/Chapter.php b/app/Entities/Models/Chapter.php index 6c5f059ac..f30d77b5c 100644 --- a/app/Entities/Models/Chapter.php +++ b/app/Entities/Models/Chapter.php @@ -20,7 +20,7 @@ class Chapter extends BookChild public float $searchFactor = 1.2; protected $fillable = ['name', 'description', 'priority']; - protected $hidden = ['pivot', 'deleted_at']; + protected $hidden = ['pivot', 'deleted_at', 'description_html']; /** * Get the pages that this chapter contains. diff --git a/dev/api/responses/books-read.json b/dev/api/responses/books-read.json index 21e1829b8..afeebade6 100644 --- a/dev/api/responses/books-read.json +++ b/dev/api/responses/books-read.json @@ -3,6 +3,7 @@ "name": "My own book", "slug": "my-own-book", "description": "This is my own little book", + "description_html": "

This is my own little book

", "created_at": "2020-01-12T14:09:59.000000Z", "updated_at": "2020-01-12T14:11:51.000000Z", "created_by": { diff --git a/dev/api/responses/chapters-read.json b/dev/api/responses/chapters-read.json index 5f4de85f1..192ffce7c 100644 --- a/dev/api/responses/chapters-read.json +++ b/dev/api/responses/chapters-read.json @@ -4,6 +4,7 @@ "slug": "content-creation", "name": "Content Creation", "description": "How to create documentation on whatever subject you need to write about.", + "description_html": "

How to create documentation on whatever subject you need to write about.

", "priority": 3, "created_at": "2019-05-05T21:49:56.000000Z", "updated_at": "2019-09-28T11:24:23.000000Z", diff --git a/dev/api/responses/shelves-read.json b/dev/api/responses/shelves-read.json index 802045bd8..eca06a46b 100644 --- a/dev/api/responses/shelves-read.json +++ b/dev/api/responses/shelves-read.json @@ -3,6 +3,7 @@ "name": "My shelf", "slug": "my-shelf", "description": "This is my shelf with some books", + "description_html": "

This is my shelf with some books

", "created_by": { "id": 1, "name": "Admin", From 2a7a81e74952c760f58e07c887f9797b9137c61d Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 22 Dec 2023 13:17:23 +0000 Subject: [PATCH 014/124] Input WYSIWYG: Updated API testing, fixed description set issue Fixed issue where an existing description_html field would not be updated via 'description' input. --- app/Entities/Repos/BaseRepo.php | 1 + tests/Api/BooksApiTest.php | 56 +++++++++++++++++++++++++++++---- tests/Api/ChaptersApiTest.php | 52 ++++++++++++++++++++++++++++-- tests/Api/ShelvesApiTest.php | 51 ++++++++++++++++++++++++++++-- 4 files changed, 148 insertions(+), 12 deletions(-) diff --git a/app/Entities/Repos/BaseRepo.php b/app/Entities/Repos/BaseRepo.php index b52f3974e..27bf00161 100644 --- a/app/Entities/Repos/BaseRepo.php +++ b/app/Entities/Repos/BaseRepo.php @@ -116,6 +116,7 @@ class BaseRepo $entity->description = html_entity_decode(strip_tags($input['description_html'])); } else if (isset($input['description'])) { $entity->description = $input['description']; + $entity->description_html = ''; $entity->description_html = $entity->descriptionHtml(); } } diff --git a/tests/Api/BooksApiTest.php b/tests/Api/BooksApiTest.php index c648faaf2..b31bd7d37 100644 --- a/tests/Api/BooksApiTest.php +++ b/tests/Api/BooksApiTest.php @@ -33,8 +33,8 @@ class BooksApiTest extends TestCase $this->actingAsApiEditor(); $templatePage = $this->entities->templatePage(); $details = [ - 'name' => 'My API book', - 'description' => 'A book created via the API', + 'name' => 'My API book', + 'description' => 'A book created via the API', 'default_template_id' => $templatePage->id, ]; @@ -42,10 +42,35 @@ class BooksApiTest extends TestCase $resp->assertStatus(200); $newItem = Book::query()->orderByDesc('id')->where('name', '=', $details['name'])->first(); - $resp->assertJson(array_merge($details, ['id' => $newItem->id, 'slug' => $newItem->slug])); + $resp->assertJson(array_merge($details, [ + 'id' => $newItem->id, + 'slug' => $newItem->slug, + 'description_html' => '

A book created via the API

', + ])); $this->assertActivityExists('book_create', $newItem); } + public function test_create_endpoint_with_html() + { + $this->actingAsApiEditor(); + $details = [ + 'name' => 'My API book', + 'description_html' => '

A book created via the API

', + ]; + + $resp = $this->postJson($this->baseEndpoint, $details); + $resp->assertStatus(200); + + $newItem = Book::query()->orderByDesc('id')->where('name', '=', $details['name'])->first(); + $expectedDetails = array_merge($details, [ + 'id' => $newItem->id, + 'description' => 'A book created via the API', + ]); + + $resp->assertJson($expectedDetails); + $this->assertDatabaseHas('books', $expectedDetails); + } + public function test_book_name_needed_to_create() { $this->actingAsApiEditor(); @@ -61,7 +86,7 @@ class BooksApiTest extends TestCase 'validation' => [ 'name' => ['The name field is required.'], ], - 'code' => 422, + 'code' => 422, ], ]); } @@ -128,7 +153,7 @@ class BooksApiTest extends TestCase $templatePage = $this->entities->templatePage(); $details = [ 'name' => 'My updated API book', - 'description' => 'A book created via the API', + 'description' => 'A book updated via the API', 'default_template_id' => $templatePage->id, ]; @@ -136,10 +161,29 @@ class BooksApiTest extends TestCase $book->refresh(); $resp->assertStatus(200); - $resp->assertJson(array_merge($details, ['id' => $book->id, 'slug' => $book->slug])); + $resp->assertJson(array_merge($details, [ + 'id' => $book->id, + 'slug' => $book->slug, + 'description_html' => '

A book updated via the API

', + ])); $this->assertActivityExists('book_update', $book); } + public function test_update_endpoint_with_html() + { + $this->actingAsApiEditor(); + $book = $this->entities->book(); + $details = [ + 'name' => 'My updated API book', + 'description_html' => '

A book updated via the API

', + ]; + + $resp = $this->putJson($this->baseEndpoint . "/{$book->id}", $details); + $resp->assertStatus(200); + + $this->assertDatabaseHas('books', array_merge($details, ['id' => $book->id, 'description' => 'A book updated via the API'])); + } + public function test_update_increments_updated_date_if_only_tags_are_sent() { $this->actingAsApiEditor(); diff --git a/tests/Api/ChaptersApiTest.php b/tests/Api/ChaptersApiTest.php index 0629f3aed..81a918877 100644 --- a/tests/Api/ChaptersApiTest.php +++ b/tests/Api/ChaptersApiTest.php @@ -51,7 +51,11 @@ class ChaptersApiTest extends TestCase $resp = $this->postJson($this->baseEndpoint, $details); $resp->assertStatus(200); $newItem = Chapter::query()->orderByDesc('id')->where('name', '=', $details['name'])->first(); - $resp->assertJson(array_merge($details, ['id' => $newItem->id, 'slug' => $newItem->slug])); + $resp->assertJson(array_merge($details, [ + 'id' => $newItem->id, + 'slug' => $newItem->slug, + 'description_html' => '

A chapter created via the API

', + ])); $this->assertDatabaseHas('tags', [ 'entity_id' => $newItem->id, 'entity_type' => $newItem->getMorphClass(), @@ -62,6 +66,28 @@ class ChaptersApiTest extends TestCase $this->assertActivityExists('chapter_create', $newItem); } + public function test_create_endpoint_with_html() + { + $this->actingAsApiEditor(); + $book = $this->entities->book(); + $details = [ + 'name' => 'My API chapter', + 'description_html' => '

A chapter created via the API

', + 'book_id' => $book->id, + ]; + + $resp = $this->postJson($this->baseEndpoint, $details); + $resp->assertStatus(200); + $newItem = Chapter::query()->orderByDesc('id')->where('name', '=', $details['name'])->first(); + + $expectedDetails = array_merge($details, [ + 'id' => $newItem->id, + 'description' => 'A chapter created via the API', + ]); + $resp->assertJson($expectedDetails); + $this->assertDatabaseHas('chapters', $expectedDetails); + } + public function test_chapter_name_needed_to_create() { $this->actingAsApiEditor(); @@ -131,7 +157,7 @@ class ChaptersApiTest extends TestCase $chapter = $this->entities->chapter(); $details = [ 'name' => 'My updated API chapter', - 'description' => 'A chapter created via the API', + 'description' => 'A chapter updated via the API', 'tags' => [ [ 'name' => 'freshtag', @@ -146,11 +172,31 @@ class ChaptersApiTest extends TestCase $resp->assertStatus(200); $resp->assertJson(array_merge($details, [ - 'id' => $chapter->id, 'slug' => $chapter->slug, 'book_id' => $chapter->book_id, + 'id' => $chapter->id, + 'slug' => $chapter->slug, + 'book_id' => $chapter->book_id, + 'description_html' => '

A chapter updated via the API

', ])); $this->assertActivityExists('chapter_update', $chapter); } + public function test_update_endpoint_with_html() + { + $this->actingAsApiEditor(); + $chapter = $this->entities->chapter(); + $details = [ + 'name' => 'My updated API chapter', + 'description_html' => '

A chapter updated via the API

', + ]; + + $resp = $this->putJson($this->baseEndpoint . "/{$chapter->id}", $details); + $resp->assertStatus(200); + + $this->assertDatabaseHas('chapters', array_merge($details, [ + 'id' => $chapter->id, 'description' => 'A chapter updated via the API' + ])); + } + public function test_update_increments_updated_date_if_only_tags_are_sent() { $this->actingAsApiEditor(); diff --git a/tests/Api/ShelvesApiTest.php b/tests/Api/ShelvesApiTest.php index fbfc17cb4..f1b8ed985 100644 --- a/tests/Api/ShelvesApiTest.php +++ b/tests/Api/ShelvesApiTest.php @@ -42,7 +42,11 @@ class ShelvesApiTest extends TestCase $resp = $this->postJson($this->baseEndpoint, array_merge($details, ['books' => [$books[0]->id, $books[1]->id]])); $resp->assertStatus(200); $newItem = Bookshelf::query()->orderByDesc('id')->where('name', '=', $details['name'])->first(); - $resp->assertJson(array_merge($details, ['id' => $newItem->id, 'slug' => $newItem->slug])); + $resp->assertJson(array_merge($details, [ + 'id' => $newItem->id, + 'slug' => $newItem->slug, + 'description_html' => '

A shelf created via the API

', + ])); $this->assertActivityExists('bookshelf_create', $newItem); foreach ($books as $index => $book) { $this->assertDatabaseHas('bookshelves_books', [ @@ -53,6 +57,28 @@ class ShelvesApiTest extends TestCase } } + public function test_create_endpoint_with_html() + { + $this->actingAsApiEditor(); + + $details = [ + 'name' => 'My API shelf', + 'description_html' => '

A shelf created via the API

', + ]; + + $resp = $this->postJson($this->baseEndpoint, $details); + $resp->assertStatus(200); + $newItem = Bookshelf::query()->orderByDesc('id')->where('name', '=', $details['name'])->first(); + + $expectedDetails = array_merge($details, [ + 'id' => $newItem->id, + 'description' => 'A shelf created via the API', + ]); + + $resp->assertJson($expectedDetails); + $this->assertDatabaseHas('bookshelves', $expectedDetails); + } + public function test_shelf_name_needed_to_create() { $this->actingAsApiEditor(); @@ -102,17 +128,36 @@ class ShelvesApiTest extends TestCase $shelf = Bookshelf::visible()->first(); $details = [ 'name' => 'My updated API shelf', - 'description' => 'A shelf created via the API', + 'description' => 'A shelf updated via the API', ]; $resp = $this->putJson($this->baseEndpoint . "/{$shelf->id}", $details); $shelf->refresh(); $resp->assertStatus(200); - $resp->assertJson(array_merge($details, ['id' => $shelf->id, 'slug' => $shelf->slug])); + $resp->assertJson(array_merge($details, [ + 'id' => $shelf->id, + 'slug' => $shelf->slug, + 'description_html' => '

A shelf updated via the API

', + ])); $this->assertActivityExists('bookshelf_update', $shelf); } + public function test_update_endpoint_with_html() + { + $this->actingAsApiEditor(); + $shelf = Bookshelf::visible()->first(); + $details = [ + 'name' => 'My updated API shelf', + 'description_html' => '

A shelf updated via the API

', + ]; + + $resp = $this->putJson($this->baseEndpoint . "/{$shelf->id}", $details); + $resp->assertStatus(200); + + $this->assertDatabaseHas('bookshelves', array_merge($details, ['id' => $shelf->id, 'description' => 'A shelf updated via the API'])); + } + public function test_update_increments_updated_date_if_only_tags_are_sent() { $this->actingAsApiEditor(); From fb3cfaf7c7cdd1fb77f7553c02821fe29ccee2ea Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 22 Dec 2023 14:37:48 +0000 Subject: [PATCH 015/124] Input WYSIWYG: Updated API examples to align with changes --- dev/api/requests/books-create.json | 4 ++-- dev/api/requests/books-update.json | 4 ++-- dev/api/requests/chapters-create.json | 2 +- dev/api/requests/chapters-update.json | 2 +- dev/api/requests/shelves-create.json | 7 +++++-- dev/api/requests/shelves-update.json | 2 +- dev/api/responses/books-create.json | 28 +++++++++++++++++++------- dev/api/responses/books-update.json | 17 ++++++++++++---- dev/api/responses/chapters-create.json | 9 +++++---- dev/api/responses/chapters-update.json | 10 ++++----- dev/api/responses/shelves-create.json | 15 +++++++++++--- dev/api/responses/shelves-update.json | 17 ++++++++++++---- 12 files changed, 81 insertions(+), 36 deletions(-) diff --git a/dev/api/requests/books-create.json b/dev/api/requests/books-create.json index 2a38dba83..71dbdcc65 100644 --- a/dev/api/requests/books-create.json +++ b/dev/api/requests/books-create.json @@ -1,7 +1,7 @@ { "name": "My own book", - "description": "This is my own little book", - "default_template_id": 12, + "description_html": "

This is my own little book created via the API

", + "default_template_id": 2427, "tags": [ {"name": "Category", "value": "Top Content"}, {"name": "Rating", "value": "Highest"} diff --git a/dev/api/requests/books-update.json b/dev/api/requests/books-update.json index c026b7b49..30ce7e95a 100644 --- a/dev/api/requests/books-update.json +++ b/dev/api/requests/books-update.json @@ -1,7 +1,7 @@ { "name": "My updated book", - "description": "This is my book with updated details", - "default_template_id": 12, + "description_html": "

This is my book with updated details

", + "default_template_id": 2427, "tags": [ {"name": "Subject", "value": "Updates"} ] diff --git a/dev/api/requests/chapters-create.json b/dev/api/requests/chapters-create.json index a7a0e072c..e9d903387 100644 --- a/dev/api/requests/chapters-create.json +++ b/dev/api/requests/chapters-create.json @@ -1,7 +1,7 @@ { "book_id": 1, "name": "My fantastic new chapter", - "description": "This is a great new chapter that I've created via the API", + "description_html": "

This is a great new chapter that I've created via the API

", "priority": 15, "tags": [ {"name": "Category", "value": "Top Content"}, diff --git a/dev/api/requests/chapters-update.json b/dev/api/requests/chapters-update.json index 18c40301b..be675772b 100644 --- a/dev/api/requests/chapters-update.json +++ b/dev/api/requests/chapters-update.json @@ -1,7 +1,7 @@ { "book_id": 1, "name": "My fantastic updated chapter", - "description": "This is an updated chapter that I've altered via the API", + "description_html": "

This is an updated chapter that I've altered via the API

", "priority": 16, "tags": [ {"name": "Category", "value": "Kinda Good Content"}, diff --git a/dev/api/requests/shelves-create.json b/dev/api/requests/shelves-create.json index 39b88af7e..8f35340f6 100644 --- a/dev/api/requests/shelves-create.json +++ b/dev/api/requests/shelves-create.json @@ -1,5 +1,8 @@ { "name": "My shelf", - "description": "This is my shelf with some books", - "books": [5,1,3] + "description_html": "

This is my shelf with some books

", + "books": [5,1,3], + "tags": [ + {"name": "Category", "value": "Learning"} + ] } \ No newline at end of file diff --git a/dev/api/requests/shelves-update.json b/dev/api/requests/shelves-update.json index df5f5735d..081c8f4c1 100644 --- a/dev/api/requests/shelves-update.json +++ b/dev/api/requests/shelves-update.json @@ -1,5 +1,5 @@ { "name": "My updated shelf", - "description": "This is my update shelf with some books", + "description_html": "

This is my updated shelf with some books

", "books": [5,1,3] } \ No newline at end of file diff --git a/dev/api/responses/books-create.json b/dev/api/responses/books-create.json index 773879125..8895fb854 100644 --- a/dev/api/responses/books-create.json +++ b/dev/api/responses/books-create.json @@ -1,12 +1,26 @@ { - "id": 15, - "name": "My new book", - "slug": "my-new-book", - "description": "This is a book created via the API", + "id": 226, + "name": "My own book", + "slug": "my-own-book", + "description": "This is my own little book created via the API", + "created_at": "2023-12-22T14:22:28.000000Z", + "updated_at": "2023-12-22T14:22:28.000000Z", "created_by": 1, "updated_by": 1, "owned_by": 1, - "default_template_id": 12, - "updated_at": "2020-01-12T14:05:11.000000Z", - "created_at": "2020-01-12T14:05:11.000000Z" + "default_template_id": 2427, + "description_html": "

This is my<\/strong> own little book created via the API<\/p>", + "tags": [ + { + "name": "Category", + "value": "Top Content", + "order": 0 + }, + { + "name": "Rating", + "value": "Highest", + "order": 0 + } + ], + "cover": null } \ No newline at end of file diff --git a/dev/api/responses/books-update.json b/dev/api/responses/books-update.json index f69677c4a..dafa2feb0 100644 --- a/dev/api/responses/books-update.json +++ b/dev/api/responses/books-update.json @@ -1,12 +1,21 @@ { - "id": 16, + "id": 226, "name": "My updated book", "slug": "my-updated-book", "description": "This is my book with updated details", - "created_at": "2020-01-12T14:09:59.000000Z", - "updated_at": "2020-01-12T14:16:10.000000Z", + "created_at": "2023-12-22T14:22:28.000000Z", + "updated_at": "2023-12-22T14:24:07.000000Z", "created_by": 1, "updated_by": 1, "owned_by": 1, - "default_template_id": 12 + "default_template_id": 2427, + "description_html": "

This is my book with updated<\/em> details<\/p>", + "tags": [ + { + "name": "Subject", + "value": "Updates", + "order": 0 + } + ], + "cover": null } \ No newline at end of file diff --git a/dev/api/responses/chapters-create.json b/dev/api/responses/chapters-create.json index cf47b123d..183186b0b 100644 --- a/dev/api/responses/chapters-create.json +++ b/dev/api/responses/chapters-create.json @@ -1,15 +1,16 @@ { - "id": 74, + "id": 668, "book_id": 1, "slug": "my-fantastic-new-chapter", "name": "My fantastic new chapter", "description": "This is a great new chapter that I've created via the API", "priority": 15, + "created_at": "2023-12-22T14:26:28.000000Z", + "updated_at": "2023-12-22T14:26:28.000000Z", "created_by": 1, "updated_by": 1, "owned_by": 1, - "updated_at": "2020-05-22T22:59:55.000000Z", - "created_at": "2020-05-22T22:59:55.000000Z", + "description_html": "

This is a great new chapter<\/strong> that I've created via the API<\/p>", "tags": [ { "name": "Category", @@ -19,7 +20,7 @@ { "name": "Rating", "value": "Highest", - "order": 1 + "order": 0 } ] } \ No newline at end of file diff --git a/dev/api/responses/chapters-update.json b/dev/api/responses/chapters-update.json index a4940af2d..5ac3c64c1 100644 --- a/dev/api/responses/chapters-update.json +++ b/dev/api/responses/chapters-update.json @@ -1,16 +1,16 @@ { - "id": 75, + "id": 668, "book_id": 1, "slug": "my-fantastic-updated-chapter", "name": "My fantastic updated chapter", "description": "This is an updated chapter that I've altered via the API", "priority": 16, - "created_at": "2020-05-22T23:03:35.000000Z", - "updated_at": "2020-05-22T23:07:20.000000Z", + "created_at": "2023-12-22T14:26:28.000000Z", + "updated_at": "2023-12-22T14:27:59.000000Z", "created_by": 1, "updated_by": 1, "owned_by": 1, - "book_slug": "bookstack-demo-site", + "description_html": "

This is an updated chapter<\/strong> that I've altered via the API<\/p>", "tags": [ { "name": "Category", @@ -20,7 +20,7 @@ { "name": "Rating", "value": "Medium", - "order": 1 + "order": 0 } ] } \ No newline at end of file diff --git a/dev/api/responses/shelves-create.json b/dev/api/responses/shelves-create.json index 84caf8bdc..235557834 100644 --- a/dev/api/responses/shelves-create.json +++ b/dev/api/responses/shelves-create.json @@ -1,11 +1,20 @@ { - "id": 14, + "id": 20, "name": "My shelf", "slug": "my-shelf", "description": "This is my shelf with some books", "created_by": 1, "updated_by": 1, + "created_at": "2023-12-22T14:33:52.000000Z", + "updated_at": "2023-12-22T14:33:52.000000Z", "owned_by": 1, - "created_at": "2020-04-10T13:24:09.000000Z", - "updated_at": "2020-04-10T13:24:09.000000Z" + "description_html": "

This is my shelf<\/strong> with some books<\/p>", + "tags": [ + { + "name": "Category", + "value": "Learning", + "order": 0 + } + ], + "cover": null } \ No newline at end of file diff --git a/dev/api/responses/shelves-update.json b/dev/api/responses/shelves-update.json index e199d8d68..3b3f0538e 100644 --- a/dev/api/responses/shelves-update.json +++ b/dev/api/responses/shelves-update.json @@ -1,11 +1,20 @@ { - "id": 14, + "id": 20, "name": "My updated shelf", "slug": "my-updated-shelf", - "description": "This is my update shelf with some books", + "description": "This is my updated shelf with some books", "created_by": 1, "updated_by": 1, + "created_at": "2023-12-22T14:33:52.000000Z", + "updated_at": "2023-12-22T14:35:00.000000Z", "owned_by": 1, - "created_at": "2020-04-10T13:24:09.000000Z", - "updated_at": "2020-04-10T13:48:22.000000Z" + "description_html": "

This is my updated shelf<\/em> with some books<\/p>", + "tags": [ + { + "name": "Category", + "value": "Learning", + "order": 0 + } + ], + "cover": null } \ No newline at end of file From 7cd0629a75b2b70b5de1608d5171a6814088e2cc Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 22 Dec 2023 14:57:20 +0000 Subject: [PATCH 016/124] Input WYSIWYG: Updated exports to handle HTML descriptions --- resources/views/exports/book.blade.php | 2 +- resources/views/exports/chapter.blade.php | 2 +- .../exports/parts/chapter-item.blade.php | 2 +- tests/Entity/ExportTest.php | 28 +++++++++++++------ 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/resources/views/exports/book.blade.php b/resources/views/exports/book.blade.php index 42e03ea01..9de7b8eba 100644 --- a/resources/views/exports/book.blade.php +++ b/resources/views/exports/book.blade.php @@ -5,7 +5,7 @@ @section('content')

{{$book->name}}

-

{{ $book->description }}

+
{!! $book->descriptionHtml() !!}
@include('exports.parts.book-contents-menu', ['children' => $bookChildren]) diff --git a/resources/views/exports/chapter.blade.php b/resources/views/exports/chapter.blade.php index ae49fa918..515366d60 100644 --- a/resources/views/exports/chapter.blade.php +++ b/resources/views/exports/chapter.blade.php @@ -5,7 +5,7 @@ @section('content')

{{$chapter->name}}

-

{{ $chapter->description }}

+
{!! $chapter->descriptionHtml() !!}
@include('exports.parts.chapter-contents-menu', ['pages' => $pages]) diff --git a/resources/views/exports/parts/chapter-item.blade.php b/resources/views/exports/parts/chapter-item.blade.php index f58068b5e..fa0b1f228 100644 --- a/resources/views/exports/parts/chapter-item.blade.php +++ b/resources/views/exports/parts/chapter-item.blade.php @@ -1,7 +1,7 @@

{{ $chapter->name }}

-

{{ $chapter->description }}

+
{!! $chapter->descriptionHtml() !!}
@if(count($chapter->visible_pages) > 0) @foreach($chapter->visible_pages as $page) diff --git a/tests/Entity/ExportTest.php b/tests/Entity/ExportTest.php index 08bf17d0a..eedcb672c 100644 --- a/tests/Entity/ExportTest.php +++ b/tests/Entity/ExportTest.php @@ -107,18 +107,18 @@ class ExportTest extends TestCase $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $book->slug . '.html"'); } - public function test_book_html_export_shows_chapter_descriptions() + public function test_book_html_export_shows_html_descriptions() { - $chapterDesc = 'My custom test chapter description ' . Str::random(12); - $chapter = $this->entities->chapter(); - $chapter->description = $chapterDesc; + $book = $this->entities->bookHasChaptersAndPages(); + $chapter = $book->chapters()->first(); + $book->description_html = '

A description with HTML within!

'; + $chapter->description_html = '

A chapter description with HTML within!

'; + $book->save(); $chapter->save(); - $book = $chapter->book; - $this->asEditor(); - - $resp = $this->get($book->getUrl('/export/html')); - $resp->assertSee($chapterDesc); + $resp = $this->asEditor()->get($book->getUrl('/export/html')); + $resp->assertSee($book->description_html, false); + $resp->assertSee($chapter->description_html, false); } public function test_chapter_text_export() @@ -174,6 +174,16 @@ class ExportTest extends TestCase $resp->assertHeader('Content-Disposition', 'attachment; filename="' . $chapter->slug . '.html"'); } + public function test_chapter_html_export_shows_html_descriptions() + { + $chapter = $this->entities->chapter(); + $chapter->description_html = '

A description with HTML within!

'; + $chapter->save(); + + $resp = $this->asEditor()->get($chapter->getUrl('/export/html')); + $resp->assertSee($chapter->description_html, false); + } + public function test_page_html_export_contains_custom_head_if_set() { $page = $this->entities->page(); From 3668949705ad85c42e39dc18569e086d968f7022 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 22 Dec 2023 15:16:06 +0000 Subject: [PATCH 017/124] Input WYSIWYG: Fixed up some dark mode elements --- resources/js/wysiwyg/config.js | 6 ++---- resources/sass/_forms.scss | 1 + resources/views/books/parts/form.blade.php | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index f669849ad..963e2970d 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -331,15 +331,13 @@ export function buildForInput(options) { contextmenu: false, toolbar: 'bold italic link bullist numlist', content_style: getContentStyle(options), - color_map: colorMap, file_picker_types: 'file', file_picker_callback: filePickerCallback, init_instance_callback(editor) { const head = editor.getDoc().querySelector('head'); head.innerHTML += fetchCustomHeadContent(); - }, - setup(editor) { - // + + editor.contentDocument.documentElement.classList.toggle('dark-mode', options.darkMode); }, }; } diff --git a/resources/sass/_forms.scss b/resources/sass/_forms.scss index b63f9cdd5..8c277c2b5 100644 --- a/resources/sass/_forms.scss +++ b/resources/sass/_forms.scss @@ -408,6 +408,7 @@ input[type=color] { .description-input > .tox-tinymce { border: 1px solid #DDD !important; + @include lightDark(border-color, #DDD !important, #000 !important); border-radius: 3px; .tox-toolbar__primary { justify-content: end; diff --git a/resources/views/books/parts/form.blade.php b/resources/views/books/parts/form.blade.php index a3d4be5e9..fa8f16e52 100644 --- a/resources/views/books/parts/form.blade.php +++ b/resources/views/books/parts/form.blade.php @@ -39,7 +39,7 @@
-
From 88ee33ee49c0c920d8ad3fb1161fe27b62c64004 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 22 Dec 2023 15:48:46 +0000 Subject: [PATCH 018/124] Deps: Updated php depenencies via composer --- composer.json | 2 +- composer.lock | 172 +++++++++++++++++++++++++------------------------- 2 files changed, 86 insertions(+), 88 deletions(-) diff --git a/composer.json b/composer.json index 44c3a893f..85bb3cbae 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ "guzzlehttp/guzzle": "^7.4", "intervention/image": "^2.7", "laravel/framework": "^9.0", - "laravel/socialite": "5.10.*", + "laravel/socialite": "^5.10", "laravel/tinker": "^2.6", "league/commonmark": "^2.3", "league/flysystem-aws-s3-v3": "^3.0", diff --git a/composer.lock b/composer.lock index 91305d496..56d33e78e 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8db598e44dadc6dd4f22267780d6681d", + "content-hash": "7c64775f79832d552a2ef40e11f79c40", "packages": [ { "name": "aws/aws-crt-php", @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.293.7", + "version": "3.294.5", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "3bf86ba8b9bbea2b298f89e6f5edc58de276690b" + "reference": "2e34d45e970c77775e4c298e08732d64b647c41c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3bf86ba8b9bbea2b298f89e6f5edc58de276690b", - "reference": "3bf86ba8b9bbea2b298f89e6f5edc58de276690b", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2e34d45e970c77775e4c298e08732d64b647c41c", + "reference": "2e34d45e970c77775e4c298e08732d64b647c41c", "shasum": "" }, "require": { @@ -151,9 +151,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.293.7" + "source": "https://github.com/aws/aws-sdk-php/tree/3.294.5" }, - "time": "2023-12-08T19:11:21+00:00" + "time": "2023-12-21T19:10:21+00:00" }, { "name": "bacon/bacon-qr-code", @@ -421,16 +421,16 @@ }, { "name": "carbonphp/carbon-doctrine-types", - "version": "2.0.0", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", - "reference": "67a77972b9f398ae7068dabacc39c08aeee170d5" + "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/67a77972b9f398ae7068dabacc39c08aeee170d5", - "reference": "67a77972b9f398ae7068dabacc39c08aeee170d5", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", + "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", "shasum": "" }, "require": { @@ -470,7 +470,7 @@ ], "support": { "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", - "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/2.0.0" + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/2.1.0" }, "funding": [ { @@ -486,7 +486,7 @@ "type": "tidelift" } ], - "time": "2023-10-01T14:29:01+00:00" + "time": "2023-12-11T17:09:12+00:00" }, { "name": "dasprid/enum", @@ -1129,16 +1129,16 @@ }, { "name": "dompdf/dompdf", - "version": "v2.0.3", + "version": "v2.0.4", "source": { "type": "git", "url": "https://github.com/dompdf/dompdf.git", - "reference": "e8d2d5e37e8b0b30f0732a011295ab80680d7e85" + "reference": "093f2d9739cec57428e39ddadedfd4f3ae862c0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/dompdf/zipball/e8d2d5e37e8b0b30f0732a011295ab80680d7e85", - "reference": "e8d2d5e37e8b0b30f0732a011295ab80680d7e85", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/093f2d9739cec57428e39ddadedfd4f3ae862c0f", + "reference": "093f2d9739cec57428e39ddadedfd4f3ae862c0f", "shasum": "" }, "require": { @@ -1185,9 +1185,9 @@ "homepage": "https://github.com/dompdf/dompdf", "support": { "issues": "https://github.com/dompdf/dompdf/issues", - "source": "https://github.com/dompdf/dompdf/tree/v2.0.3" + "source": "https://github.com/dompdf/dompdf/tree/v2.0.4" }, - "time": "2023-02-07T12:51:48+00:00" + "time": "2023-12-12T20:19:39+00:00" }, { "name": "dragonmantank/cron-expression", @@ -2279,32 +2279,32 @@ }, { "name": "laravel/socialite", - "version": "v5.10.0", + "version": "v5.11.0", "source": { "type": "git", "url": "https://github.com/laravel/socialite.git", - "reference": "f376b6eda9084899e37ac08bafd64a95edf9c6c0" + "reference": "4f6a8af6f3f7c18da03d19842dd0514315501c10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/f376b6eda9084899e37ac08bafd64a95edf9c6c0", - "reference": "f376b6eda9084899e37ac08bafd64a95edf9c6c0", + "url": "https://api.github.com/repos/laravel/socialite/zipball/4f6a8af6f3f7c18da03d19842dd0514315501c10", + "reference": "4f6a8af6f3f7c18da03d19842dd0514315501c10", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/guzzle": "^6.0|^7.0", - "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "league/oauth1-client": "^1.10.1", "php": "^7.2|^8.0" }, "require-dev": { "mockery/mockery": "^1.0", - "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0", + "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0|^9.0", "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^8.0|^9.3" + "phpunit/phpunit": "^8.0|^9.3|^10.4" }, "type": "library", "extra": { @@ -2345,7 +2345,7 @@ "issues": "https://github.com/laravel/socialite/issues", "source": "https://github.com/laravel/socialite" }, - "time": "2023-10-30T22:09:58+00:00" + "time": "2023-12-02T18:22:36+00:00" }, { "name": "laravel/tinker", @@ -3348,16 +3348,16 @@ }, { "name": "nesbot/carbon", - "version": "2.72.0", + "version": "2.72.1", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "a6885fcbad2ec4360b0e200ee0da7d9b7c90786b" + "reference": "2b3b3db0a2d0556a177392ff1a3bf5608fa09f78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/a6885fcbad2ec4360b0e200ee0da7d9b7c90786b", - "reference": "a6885fcbad2ec4360b0e200ee0da7d9b7c90786b", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/2b3b3db0a2d0556a177392ff1a3bf5608fa09f78", + "reference": "2b3b3db0a2d0556a177392ff1a3bf5608fa09f78", "shasum": "" }, "require": { @@ -3451,7 +3451,7 @@ "type": "tidelift" } ], - "time": "2023-11-28T10:13:25+00:00" + "time": "2023-12-08T23:47:49+00:00" }, { "name": "nette/schema", @@ -3603,16 +3603,16 @@ }, { "name": "nikic/php-parser", - "version": "v4.17.1", + "version": "v4.18.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", - "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", "shasum": "" }, "require": { @@ -3653,9 +3653,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" }, - "time": "2023-08-13T19:53:39+00:00" + "time": "2023-12-10T21:03:43+00:00" }, { "name": "nunomaduro/termwind", @@ -3962,16 +3962,16 @@ }, { "name": "phenx/php-svg-lib", - "version": "0.5.0", + "version": "0.5.1", "source": { "type": "git", "url": "https://github.com/dompdf/php-svg-lib.git", - "reference": "76876c6cf3080bcb6f249d7d59705108166a6685" + "reference": "8a8a1ebcf6aea861ef30197999f096f7bd4b4456" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/76876c6cf3080bcb6f249d7d59705108166a6685", - "reference": "76876c6cf3080bcb6f249d7d59705108166a6685", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8a8a1ebcf6aea861ef30197999f096f7bd4b4456", + "reference": "8a8a1ebcf6aea861ef30197999f096f7bd4b4456", "shasum": "" }, "require": { @@ -4002,9 +4002,9 @@ "homepage": "https://github.com/PhenX/php-svg-lib", "support": { "issues": "https://github.com/dompdf/php-svg-lib/issues", - "source": "https://github.com/dompdf/php-svg-lib/tree/0.5.0" + "source": "https://github.com/dompdf/php-svg-lib/tree/0.5.1" }, - "time": "2022-09-06T12:16:56+00:00" + "time": "2023-12-11T20:56:08+00:00" }, { "name": "phpoption/phpoption", @@ -5384,22 +5384,22 @@ }, { "name": "socialiteproviders/okta", - "version": "4.3.0", + "version": "4.4.0", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Okta.git", - "reference": "e5fb62035bfa0ccdbc8facf4cf205428fc502edb" + "reference": "5e47cd7b4c19da94ecafbd91fa430e4151c09806" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Okta/zipball/e5fb62035bfa0ccdbc8facf4cf205428fc502edb", - "reference": "e5fb62035bfa0ccdbc8facf4cf205428fc502edb", + "url": "https://api.github.com/repos/SocialiteProviders/Okta/zipball/5e47cd7b4c19da94ecafbd91fa430e4151c09806", + "reference": "5e47cd7b4c19da94ecafbd91fa430e4151c09806", "shasum": "" }, "require": { "ext-json": "*", - "php": "^7.4 || ^8.0", - "socialiteproviders/manager": "~4.0" + "php": "^8.0", + "socialiteproviders/manager": "^4.4" }, "type": "library", "autoload": { @@ -5430,7 +5430,7 @@ "issues": "https://github.com/socialiteproviders/providers/issues", "source": "https://github.com/socialiteproviders/providers" }, - "time": "2022-09-06T03:39:26+00:00" + "time": "2023-12-12T01:59:17+00:00" }, { "name": "socialiteproviders/twitch", @@ -8471,16 +8471,16 @@ }, { "name": "mockery/mockery", - "version": "1.6.6", + "version": "1.6.7", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "b8e0bb7d8c604046539c1115994632c74dcb361e" + "reference": "0cc058854b3195ba21dc6b1f7b1f60f4ef3a9c06" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/b8e0bb7d8c604046539c1115994632c74dcb361e", - "reference": "b8e0bb7d8c604046539c1115994632c74dcb361e", + "url": "https://api.github.com/repos/mockery/mockery/zipball/0cc058854b3195ba21dc6b1f7b1f60f4ef3a9c06", + "reference": "0cc058854b3195ba21dc6b1f7b1f60f4ef3a9c06", "shasum": "" }, "require": { @@ -8493,9 +8493,7 @@ }, "require-dev": { "phpunit/phpunit": "^8.5 || ^9.6.10", - "psalm/plugin-phpunit": "^0.18.4", - "symplify/easy-coding-standard": "^11.5.0", - "vimeo/psalm": "^4.30" + "symplify/easy-coding-standard": "^12.0.8" }, "type": "library", "autoload": { @@ -8552,7 +8550,7 @@ "security": "https://github.com/mockery/mockery/security/advisories", "source": "https://github.com/mockery/mockery" }, - "time": "2023-08-09T00:03:52+00:00" + "time": "2023-12-10T02:24:34+00:00" }, { "name": "myclabs/deep-copy", @@ -8901,16 +8899,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.48", + "version": "1.10.50", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "087ed4b5f4a7a6e8f3bbdfbfe98ce5c181380bc6" + "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/087ed4b5f4a7a6e8f3bbdfbfe98ce5c181380bc6", - "reference": "087ed4b5f4a7a6e8f3bbdfbfe98ce5c181380bc6", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/06a98513ac72c03e8366b5a0cb00750b487032e4", + "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4", "shasum": "" }, "require": { @@ -8959,27 +8957,27 @@ "type": "tidelift" } ], - "time": "2023-12-08T14:34:28+00:00" + "time": "2023-12-13T10:59:42+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "9.2.29", + "version": "9.2.30", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76" + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76", - "reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/ca2bd87d2f9215904682a9cb9bb37dda98e76089", + "reference": "ca2bd87d2f9215904682a9cb9bb37dda98e76089", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3", "phpunit/php-file-iterator": "^3.0.3", "phpunit/php-text-template": "^2.0.2", @@ -9029,7 +9027,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.30" }, "funding": [ { @@ -9037,7 +9035,7 @@ "type": "github" } ], - "time": "2023-09-19T04:57:46+00:00" + "time": "2023-12-22T06:47:57+00:00" }, { "name": "phpunit/php-file-iterator", @@ -9626,20 +9624,20 @@ }, { "name": "sebastian/complexity", - "version": "2.0.2", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88" + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88", - "reference": "739b35e53379900cc9ac327b2147867b8b6efd88", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a", + "reference": "25f207c40d62b8b7aa32f5ab026c53561964053a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.7", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -9671,7 +9669,7 @@ "homepage": "https://github.com/sebastianbergmann/complexity", "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", - "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3" }, "funding": [ { @@ -9679,7 +9677,7 @@ "type": "github" } ], - "time": "2020-10-26T15:52:27+00:00" + "time": "2023-12-22T06:19:30+00:00" }, { "name": "sebastian/diff", @@ -9953,20 +9951,20 @@ }, { "name": "sebastian/lines-of-code", - "version": "1.0.3", + "version": "1.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc" + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc", - "reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5", + "reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5", "shasum": "" }, "require": { - "nikic/php-parser": "^4.6", + "nikic/php-parser": "^4.18 || ^5.0", "php": ">=7.3" }, "require-dev": { @@ -9998,7 +9996,7 @@ "homepage": "https://github.com/sebastianbergmann/lines-of-code", "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4" }, "funding": [ { @@ -10006,7 +10004,7 @@ "type": "github" } ], - "time": "2020-11-28T06:42:11+00:00" + "time": "2023-12-22T06:20:34+00:00" }, { "name": "sebastian/object-enumerator", From 02d94c87985eda533a4495e33191ef03e704a11c Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 23 Dec 2023 13:35:57 +0000 Subject: [PATCH 019/124] Permissions: Updated generation querying to be more efficient Query of existing entity permissions during view permission generation could cause timeouts or SQL placeholder limits due to massive whereOr query generation, where an "or where" clause would be created for each entity type/id combo involved, which could be all within 20 books. This updates the query handling to use a query per type involved, with no "or where"s, and to be chunked at large entity counts. Also tweaked role-specific permission regen to chunk books at half-previous rate to prevent such a large scope being involved on each chunk. For #4695 --- app/Permissions/EntityPermissionEvaluator.php | 56 +++++++++++++------ app/Permissions/JointPermissionBuilder.php | 4 +- database/seeders/LargeContentSeeder.php | 14 +++-- 3 files changed, 50 insertions(+), 24 deletions(-) diff --git a/app/Permissions/EntityPermissionEvaluator.php b/app/Permissions/EntityPermissionEvaluator.php index 06f0126ad..98ec03306 100644 --- a/app/Permissions/EntityPermissionEvaluator.php +++ b/app/Permissions/EntityPermissionEvaluator.php @@ -9,11 +9,9 @@ use Illuminate\Database\Eloquent\Builder; class EntityPermissionEvaluator { - protected string $action; - - public function __construct(string $action) - { - $this->action = $action; + public function __construct( + protected string $action + ) { } public function evaluateEntityForUser(Entity $entity, array $userRoleIds): ?bool @@ -82,23 +80,25 @@ class EntityPermissionEvaluator */ protected function getPermissionsMapByTypeId(array $typeIdChain, array $filterRoleIds): array { - $query = EntityPermission::query()->where(function (Builder $query) use ($typeIdChain) { - foreach ($typeIdChain as $typeId) { - $query->orWhere(function (Builder $query) use ($typeId) { - [$type, $id] = explode(':', $typeId); - $query->where('entity_type', '=', $type) - ->where('entity_id', '=', $id); - }); + $idsByType = []; + foreach ($typeIdChain as $typeId) { + [$type, $id] = explode(':', $typeId); + if (!isset($idsByType[$type])) { + $idsByType[$type] = []; } - }); - if (!empty($filterRoleIds)) { - $query->where(function (Builder $query) use ($filterRoleIds) { - $query->whereIn('role_id', [...$filterRoleIds, 0]); - }); + $idsByType[$type][] = $id; } - $relevantPermissions = $query->get(['entity_id', 'entity_type', 'role_id', $this->action])->all(); + $relevantPermissions = []; + + foreach ($idsByType as $type => $ids) { + $idsChunked = array_chunk($ids, 10000); + foreach ($idsChunked as $idChunk) { + $permissions = $this->getPermissionsForEntityIdsOfType($type, $idChunk, $filterRoleIds); + array_push($relevantPermissions, ...$permissions); + } + } $map = []; foreach ($relevantPermissions as $permission) { @@ -113,6 +113,26 @@ class EntityPermissionEvaluator return $map; } + /** + * @param string[] $ids + * @param int[] $filterRoleIds + * @return EntityPermission[] + */ + protected function getPermissionsForEntityIdsOfType(string $type, array $ids, array $filterRoleIds): array + { + $query = EntityPermission::query() + ->where('entity_type', '=', $type) + ->whereIn('entity_id', $ids); + + if (!empty($filterRoleIds)) { + $query->where(function (Builder $query) use ($filterRoleIds) { + $query->whereIn('role_id', [...$filterRoleIds, 0]); + }); + } + + return $query->get(['entity_id', 'entity_type', 'role_id', $this->action])->all(); + } + /** * @return string[] */ diff --git a/app/Permissions/JointPermissionBuilder.php b/app/Permissions/JointPermissionBuilder.php index 945909631..8c961fb13 100644 --- a/app/Permissions/JointPermissionBuilder.php +++ b/app/Permissions/JointPermissionBuilder.php @@ -83,13 +83,13 @@ class JointPermissionBuilder $role->load('permissions'); // Chunk through all books - $this->bookFetchQuery()->chunk(20, function ($books) use ($roles) { + $this->bookFetchQuery()->chunk(10, function ($books) use ($roles) { $this->buildJointPermissionsForBooks($books, $roles); }); // Chunk through all bookshelves Bookshelf::query()->select(['id', 'owned_by']) - ->chunk(50, function ($shelves) use ($roles) { + ->chunk(100, function ($shelves) use ($roles) { $this->createManyJointPermissions($shelves->all(), $roles); }); } diff --git a/database/seeders/LargeContentSeeder.php b/database/seeders/LargeContentSeeder.php index bb9b087d2..ac551dd93 100644 --- a/database/seeders/LargeContentSeeder.php +++ b/database/seeders/LargeContentSeeder.php @@ -28,12 +28,18 @@ class LargeContentSeeder extends Seeder /** @var Book $largeBook */ $largeBook = Book::factory()->create(['name' => 'Large book' . Str::random(10), 'created_by' => $editorUser->id, 'updated_by' => $editorUser->id]); - $pages = Page::factory()->count(200)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id]); $chapters = Chapter::factory()->count(50)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id]); - - $largeBook->pages()->saveMany($pages); $largeBook->chapters()->saveMany($chapters); - $all = array_merge([$largeBook], array_values($pages->all()), array_values($chapters->all())); + + $allPages = []; + + foreach ($chapters as $chapter) { + $pages = Page::factory()->count(100)->make(['created_by' => $editorUser->id, 'updated_by' => $editorUser->id, 'chapter_id' => $chapter->id]); + $largeBook->pages()->saveMany($pages); + array_push($allPages, ...$pages->all()); + } + + $all = array_merge([$largeBook], $allPages, array_values($chapters->all())); app()->make(JointPermissionBuilder::class)->rebuildForEntity($largeBook); app()->make(SearchIndex::class)->indexEntities($all); From 5b1929a39a263e9e4a9864bc33b7425c7036a279 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 28 Dec 2023 15:24:51 +0000 Subject: [PATCH 020/124] Languages: Added Finnish to language list --- lang/en/settings.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lang/en/settings.php b/lang/en/settings.php index c5ca662c3..03e9bf462 100644 --- a/lang/en/settings.php +++ b/lang/en/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', From c017f5bed1bf32c15200f4551c637149dc8abd9b Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 28 Dec 2023 17:49:38 +0000 Subject: [PATCH 021/124] Updated translations with latest Crowdin changes (#4658) --- lang/ar/activities.php | 74 +-- lang/ar/auth.php | 22 +- lang/ar/common.php | 42 +- lang/ar/entities.php | 8 +- lang/ar/errors.php | 35 +- lang/ar/notifications.php | 1 + lang/ar/settings.php | 25 +- lang/ar/validation.php | 6 +- lang/bg/entities.php | 8 +- lang/bg/errors.php | 1 - lang/bg/notifications.php | 1 + lang/bg/settings.php | 1 + lang/bs/entities.php | 8 +- lang/bs/errors.php | 1 - lang/bs/notifications.php | 1 + lang/bs/settings.php | 1 + lang/ca/activities.php | 128 ++--- lang/ca/auth.php | 116 ++--- lang/ca/common.php | 60 +-- lang/ca/components.php | 32 +- lang/ca/editor.php | 302 +++++------ lang/ca/entities.php | 344 ++++++------- lang/ca/errors.php | 137 +++-- lang/ca/notifications.php | 33 +- lang/ca/passwords.php | 10 +- lang/ca/preferences.php | 76 +-- lang/ca/settings.php | 293 +++++------ lang/ca/validation.php | 28 +- lang/cs/common.php | 2 +- lang/cs/components.php | 4 +- lang/cs/entities.php | 10 +- lang/cs/errors.php | 9 +- lang/cs/notifications.php | 1 + lang/cs/preferences.php | 38 +- lang/cs/settings.php | 15 +- lang/cy/entities.php | 8 +- lang/cy/errors.php | 1 - lang/cy/notifications.php | 1 + lang/cy/settings.php | 1 + lang/da/entities.php | 8 +- lang/da/errors.php | 1 - lang/da/notifications.php | 1 + lang/da/settings.php | 1 + lang/de/entities.php | 10 +- lang/de/errors.php | 1 - lang/de/notifications.php | 1 + lang/de/settings.php | 1 + lang/de_informal/entities.php | 10 +- lang/de_informal/errors.php | 1 - lang/de_informal/notifications.php | 1 + lang/de_informal/settings.php | 1 + lang/el/entities.php | 8 +- lang/el/errors.php | 1 - lang/el/notifications.php | 1 + lang/el/settings.php | 1 + lang/es/entities.php | 6 +- lang/es/errors.php | 1 - lang/es/notifications.php | 1 + lang/es/settings.php | 1 + lang/es_AR/entities.php | 8 +- lang/es_AR/errors.php | 1 - lang/es_AR/notifications.php | 1 + lang/es_AR/settings.php | 1 + lang/et/entities.php | 8 +- lang/et/errors.php | 1 - lang/et/notifications.php | 1 + lang/et/preferences.php | 2 +- lang/et/settings.php | 13 +- lang/eu/entities.php | 8 +- lang/eu/errors.php | 1 - lang/eu/notifications.php | 1 + lang/eu/settings.php | 1 + lang/fa/entities.php | 8 +- lang/fa/errors.php | 1 - lang/fa/notifications.php | 1 + lang/fa/settings.php | 1 + lang/fi/activities.php | 170 +++---- lang/fi/auth.php | 186 +++---- lang/fi/common.php | 168 +++--- lang/fi/components.php | 72 +-- lang/fi/editor.php | 308 +++++------ lang/fi/entities.php | 790 +++++++++++++++-------------- lang/fi/errors.php | 172 +++---- lang/fi/notifications.php | 33 +- lang/fi/pagination.php | 4 +- lang/fi/passwords.php | 10 +- lang/fi/preferences.php | 76 +-- lang/fi/settings.php | 497 +++++++++--------- lang/fi/validation.php | 162 +++--- lang/fr/entities.php | 8 +- lang/fr/errors.php | 1 - lang/fr/notifications.php | 7 +- lang/fr/settings.php | 1 + lang/he/entities.php | 8 +- lang/he/errors.php | 1 - lang/he/notifications.php | 1 + lang/he/settings.php | 1 + lang/hr/entities.php | 8 +- lang/hr/errors.php | 1 - lang/hr/notifications.php | 1 + lang/hr/settings.php | 1 + lang/hu/entities.php | 8 +- lang/hu/errors.php | 1 - lang/hu/notifications.php | 1 + lang/hu/settings.php | 1 + lang/id/entities.php | 8 +- lang/id/errors.php | 1 - lang/id/notifications.php | 1 + lang/id/settings.php | 1 + lang/it/entities.php | 8 +- lang/it/errors.php | 1 - lang/it/notifications.php | 1 + lang/it/settings.php | 1 + lang/ja/entities.php | 8 +- lang/ja/errors.php | 1 - lang/ja/notifications.php | 1 + lang/ja/settings.php | 1 + lang/ka/entities.php | 8 +- lang/ka/errors.php | 1 - lang/ka/notifications.php | 1 + lang/ka/settings.php | 1 + lang/ko/activities.php | 38 +- lang/ko/components.php | 16 +- lang/ko/entities.php | 8 +- lang/ko/errors.php | 1 - lang/ko/notifications.php | 1 + lang/ko/settings.php | 1 + lang/lt/entities.php | 8 +- lang/lt/errors.php | 1 - lang/lt/notifications.php | 1 + lang/lt/settings.php | 1 + lang/lv/entities.php | 8 +- lang/lv/errors.php | 1 - lang/lv/notifications.php | 1 + lang/lv/settings.php | 1 + lang/nb/common.php | 4 +- lang/nb/components.php | 2 +- lang/nb/entities.php | 12 +- lang/nb/errors.php | 1 - lang/nb/notifications.php | 1 + lang/nb/settings.php | 1 + lang/nl/entities.php | 8 +- lang/nl/errors.php | 1 - lang/nl/notifications.php | 1 + lang/nl/settings.php | 1 + lang/nn/activities.php | 6 +- lang/nn/auth.php | 98 ++-- lang/nn/common.php | 72 +-- lang/nn/components.php | 56 +- lang/nn/entities.php | 268 +++++----- lang/nn/errors.php | 21 +- lang/nn/notifications.php | 1 + lang/nn/settings.php | 15 +- lang/pl/common.php | 2 +- lang/pl/components.php | 4 +- lang/pl/entities.php | 10 +- lang/pl/errors.php | 9 +- lang/pl/notifications.php | 1 + lang/pl/preferences.php | 38 +- lang/pl/settings.php | 13 +- lang/pt/activities.php | 8 +- lang/pt/common.php | 4 +- lang/pt/components.php | 4 +- lang/pt/entities.php | 48 +- lang/pt/errors.php | 3 +- lang/pt/notifications.php | 33 +- lang/pt/preferences.php | 26 +- lang/pt/settings.php | 5 +- lang/pt_BR/entities.php | 10 +- lang/pt_BR/errors.php | 1 - lang/pt_BR/notifications.php | 1 + lang/pt_BR/settings.php | 1 + lang/ro/entities.php | 8 +- lang/ro/errors.php | 1 - lang/ro/notifications.php | 1 + lang/ro/preferences.php | 10 +- lang/ro/settings.php | 1 + lang/ru/entities.php | 8 +- lang/ru/errors.php | 1 - lang/ru/notifications.php | 1 + lang/ru/settings.php | 1 + lang/sk/entities.php | 8 +- lang/sk/errors.php | 1 - lang/sk/notifications.php | 1 + lang/sk/settings.php | 1 + lang/sl/entities.php | 8 +- lang/sl/errors.php | 1 - lang/sl/notifications.php | 1 + lang/sl/settings.php | 1 + lang/sq/entities.php | 8 +- lang/sq/errors.php | 1 - lang/sq/notifications.php | 1 + lang/sq/settings.php | 1 + lang/sv/entities.php | 8 +- lang/sv/errors.php | 1 - lang/sv/notifications.php | 1 + lang/sv/settings.php | 1 + lang/tr/activities.php | 12 +- lang/tr/entities.php | 8 +- lang/tr/errors.php | 1 - lang/tr/notifications.php | 1 + lang/tr/settings.php | 1 + lang/uk/activities.php | 64 +-- lang/uk/common.php | 6 +- lang/uk/components.php | 26 +- lang/uk/entities.php | 88 ++-- lang/uk/errors.php | 17 +- lang/uk/notifications.php | 33 +- lang/uk/preferences.php | 58 +-- lang/uk/settings.php | 1 + lang/uz/entities.php | 8 +- lang/uz/errors.php | 1 - lang/uz/notifications.php | 1 + lang/uz/settings.php | 1 + lang/vi/entities.php | 8 +- lang/vi/errors.php | 1 - lang/vi/notifications.php | 1 + lang/vi/settings.php | 1 + lang/zh_CN/entities.php | 8 +- lang/zh_CN/errors.php | 1 - lang/zh_CN/notifications.php | 1 + lang/zh_CN/settings.php | 1 + lang/zh_TW/entities.php | 8 +- lang/zh_TW/errors.php | 1 - lang/zh_TW/notifications.php | 1 + lang/zh_TW/settings.php | 1 + 226 files changed, 3140 insertions(+), 2914 deletions(-) diff --git a/lang/ar/activities.php b/lang/ar/activities.php index f4065b1f1..51f814d3d 100644 --- a/lang/ar/activities.php +++ b/lang/ar/activities.php @@ -15,7 +15,7 @@ return [ 'page_restore' => 'تمت استعادة الصفحة', 'page_restore_notification' => 'تمت استعادة الصفحة بنجاح', 'page_move' => 'تم نقل الصفحة', - 'page_move_notification' => 'Page successfully moved', + 'page_move_notification' => 'تم نقل الصفحة بنجاح', // Chapters 'chapter_create' => 'تم إنشاء فصل', @@ -25,7 +25,7 @@ return [ 'chapter_delete' => 'تم حذف الفصل', 'chapter_delete_notification' => 'تم حذف الفصل بنجاح', 'chapter_move' => 'تم نقل الفصل', - 'chapter_move_notification' => 'Chapter successfully moved', + 'chapter_move_notification' => 'تم نقل الفصل بنجاح', // Books 'book_create' => 'تم إنشاء كتاب', @@ -50,31 +50,31 @@ return [ 'bookshelf_delete_notification' => 'تم حذف الرف بنجاح', // Revisions - 'revision_restore' => 'restored revision', - 'revision_delete' => 'deleted revision', - 'revision_delete_notification' => 'Revision successfully deleted', + 'revision_restore' => 'استعادة مراجعة', + 'revision_delete' => 'مراجعة محذوفة', + 'revision_delete_notification' => 'تم حذف المراجعة بنجاح', // Favourites 'favourite_add_notification' => 'تم إضافة ":name" إلى المفضلة لديك', 'favourite_remove_notification' => 'تم إزالة ":name" من المفضلة لديك', // Watching - 'watch_update_level_notification' => 'Watch preferences successfully updated', + 'watch_update_level_notification' => 'تم تحديث الإعدادات المشاهدة بنجاح', // Auth - 'auth_login' => 'logged in', - 'auth_register' => 'registered as new user', - 'auth_password_reset_request' => 'requested user password reset', - 'auth_password_reset_update' => 'reset user password', - 'mfa_setup_method' => 'configured MFA method', + 'auth_login' => 'تم تسجيل الدخول', + 'auth_register' => 'سجل كمستخدم جديد', + 'auth_password_reset_request' => 'طلب رابط جديد لإعادة تعيين كلمة المرور', + 'auth_password_reset_update' => 'إعادة تعيين كلمة مرور المستخدم', + 'mfa_setup_method' => 'طريقة MFA المكونة', 'mfa_setup_method_notification' => 'تم تكوين طريقة متعددة العوامل بنجاح', - 'mfa_remove_method' => 'removed MFA method', + 'mfa_remove_method' => 'إزالة طريقة MFA', 'mfa_remove_method_notification' => 'تمت إزالة طريقة متعددة العوامل بنجاح', // Settings - 'settings_update' => 'updated settings', - 'settings_update_notification' => 'Settings successfully updated', - 'maintenance_action_run' => 'ran maintenance action', + 'settings_update' => 'تحديث الإعدادات', + 'settings_update_notification' => 'تم تحديث الإعدادات', + 'maintenance_action_run' => 'إجراء الصيانة', // Webhooks 'webhook_create' => 'تم إنشاء webhook', @@ -85,39 +85,39 @@ return [ 'webhook_delete_notification' => 'تم حذف Webhook بنجاح', // Users - 'user_create' => 'created user', - 'user_create_notification' => 'User successfully created', - 'user_update' => 'updated user', + 'user_create' => 'إنشاء مستخدم', + 'user_create_notification' => 'تم انشاء الحساب', + 'user_update' => 'المستخدم المحدث', 'user_update_notification' => 'تم تحديث المستخدم بنجاح', - 'user_delete' => 'deleted user', + 'user_delete' => 'المستخدم المحذوف', 'user_delete_notification' => 'تم إزالة المستخدم بنجاح', // API Tokens - 'api_token_create' => 'created api token', - 'api_token_create_notification' => 'API token successfully created', - 'api_token_update' => 'updated api token', - 'api_token_update_notification' => 'API token successfully updated', - 'api_token_delete' => 'deleted api token', - 'api_token_delete_notification' => 'API token successfully deleted', + 'api_token_create' => 'تم إنشاء رمز api', + 'api_token_create_notification' => 'تم إنشاء رمز الـ API بنجاح', + 'api_token_update' => 'تم تحديث رمز api', + 'api_token_update_notification' => 'تم تحديث رمز الـ API بنجاح', + 'api_token_delete' => 'رمز api المحذوف', + 'api_token_delete_notification' => 'تم حذف رمز الـ API بنجاح', // Roles - 'role_create' => 'created role', - 'role_create_notification' => 'Role successfully created', - 'role_update' => 'updated role', - 'role_update_notification' => 'Role successfully updated', - 'role_delete' => 'deleted role', - 'role_delete_notification' => 'Role successfully deleted', + 'role_create' => 'إنشاء صَلاحِيَة', + 'role_create_notification' => 'تم إنشاء الدور بنجاح', + 'role_update' => 'حدّث الدور', + 'role_update_notification' => 'تم تحديث الدور بنجاح', + 'role_delete' => 'حذف الدور', + 'role_delete_notification' => 'تم حذف الدور بنجاح', // Recycle Bin - 'recycle_bin_empty' => 'emptied recycle bin', - 'recycle_bin_restore' => 'restored from recycle bin', - 'recycle_bin_destroy' => 'removed from recycle bin', + 'recycle_bin_empty' => 'سلة إعادة التدوير المفرغة', + 'recycle_bin_restore' => 'استعادة من سلة المحذوفات', + 'recycle_bin_destroy' => 'إزالة من سلة المحذوفات', // Comments 'commented_on' => 'تم التعليق', - 'comment_create' => 'added comment', - 'comment_update' => 'updated comment', - 'comment_delete' => 'deleted comment', + 'comment_create' => 'تعليق مضاف', + 'comment_update' => 'تعليق محدث', + 'comment_delete' => 'تعليق محذوف', // Other 'permissions_update' => 'تحديث الأذونات', diff --git a/lang/ar/auth.php b/lang/ar/auth.php index 55c9aae41..7d64babc4 100644 --- a/lang/ar/auth.php +++ b/lang/ar/auth.php @@ -39,8 +39,8 @@ return [ 'register_success' => 'شكراً لإنشاء حسابكم! تم تسجيلكم ودخولكم للحساب الخاص بكم.', // Login auto-initiation - 'auto_init_starting' => 'Attempting Login', - 'auto_init_starting_desc' => 'We\'re contacting your authentication system to start the login process. If there\'s no progress after 5 seconds you can try clicking the link below.', + 'auto_init_starting' => 'محاولة تسجيل الدخول', + 'auto_init_starting_desc' => 'نحن نتصل بنظام المصادقة الخاص بك لبدء عملية تسجيل الدخول. إذا لم يحدث أي تقدم بعد 5 ثوان يمكنك محاولة النقر على الرابط أدناه.', 'auto_init_start_link' => 'المتابعة مع المصادقة', // Password Reset @@ -59,10 +59,10 @@ return [ 'email_confirm_text' => 'الرجاء تأكيد بريدكم الإلكتروني بالضغط على الزر أدناه:', 'email_confirm_action' => 'تأكيد البريد الإلكتروني', 'email_confirm_send_error' => 'تأكيد البريد الإلكتروني مطلوب ولكن النظام لم يستطع إرسال الرسالة. تواصل مع مشرف النظام للتأكد من إعدادات البريد.', - 'email_confirm_success' => 'Your email has been confirmed! You should now be able to login using this email address.', + 'email_confirm_success' => 'تم تأكيد بريدك الإلكتروني! يمكنك الآن تسجيل الدخول باستخدام عنوان البريد الإلكتروني هذا.', 'email_confirm_resent' => 'تمت إعادة إرسال رسالة التأكيد. الرجاء مراجعة صندوق الوارد', - 'email_confirm_thanks' => 'Thanks for confirming!', - 'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.', + 'email_confirm_thanks' => 'شكرا للتأكيد!', + 'email_confirm_thanks_desc' => 'الرجاء الانتظار لحظة بينما يتم التعامل مع التأكيد الخاص بك. إذا لم يتم إعادة توجيهك بعد 3 ثوان اضغط على الرابط "المتابعة" أدناه للمتابعة.', 'email_not_confirmed' => 'لم يتم تأكيد البريد الإلكتروني', 'email_not_confirmed_text' => 'لم يتم بعد تأكيد عنوان البريد الإلكتروني.', @@ -78,14 +78,14 @@ return [ 'user_invite_page_welcome' => 'مرحبا بكم في :appName!', 'user_invite_page_text' => 'لإكمال حسابك والحصول على حق الوصول تحتاج إلى تعيين كلمة مرور سيتم استخدامها لتسجيل الدخول إلى :appName في الزيارات المستقبلية.', 'user_invite_page_confirm_button' => 'تأكيد كلمة المرور', - 'user_invite_success_login' => 'Password set, you should now be able to login using your set password to access :appName!', + 'user_invite_success_login' => 'تم تأكيد كلمة المرور. يمكنك الآن تسجيل الدخول باستخدام كلمة المرور المحددة للوصول إلى :appName!', // Multi-factor Authentication - 'mfa_setup' => 'Setup Multi-Factor Authentication', - 'mfa_setup_desc' => 'Setup multi-factor authentication as an extra layer of security for your user account.', - 'mfa_setup_configured' => 'Already configured', - 'mfa_setup_reconfigure' => 'Reconfigure', - 'mfa_setup_remove_confirmation' => 'Are you sure you want to remove this multi-factor authentication method?', + 'mfa_setup' => 'إعداد المصادقة متعددة العوامل', + 'mfa_setup_desc' => 'إعداد المصادقة متعددة العوامل كطبقة إضافية من الأمان لحساب المستخدم الخاص بك.', + 'mfa_setup_configured' => 'تم إعداده مسبقاً', + 'mfa_setup_reconfigure' => 'إعادة التكوين', + 'mfa_setup_remove_confirmation' => 'هل أنت متأكد من أنك تريد إزالة طريقة المصادقة متعددة العناصر هذه؟', 'mfa_setup_action' => 'إعداد (تنصيب)', 'mfa_backup_codes_usage_limit_warning' => 'You have less than 5 backup codes remaining, Please generate and store a new set before you run out of codes to prevent being locked out of your account.', 'mfa_option_totp_title' => 'تطبيق الجوال', diff --git a/lang/ar/common.php b/lang/ar/common.php index 75bdfdec2..5a391cd6d 100644 --- a/lang/ar/common.php +++ b/lang/ar/common.php @@ -6,7 +6,7 @@ return [ // Buttons 'cancel' => 'إلغاء', - 'close' => 'Close', + 'close' => 'إغلاق', 'confirm' => 'تأكيد', 'back' => 'رجوع', 'save' => 'حفظ', @@ -26,7 +26,7 @@ return [ 'actions' => 'إجراءات', 'view' => 'عرض', 'view_all' => 'عرض الكل', - 'new' => 'New', + 'new' => 'جديد', 'create' => 'إنشاء', 'update' => 'تحديث', 'edit' => 'تعديل', @@ -41,18 +41,18 @@ return [ 'reset' => 'إعادة تعيين', 'remove' => 'إزالة', 'add' => 'إضافة', - 'configure' => 'Configure', - 'manage' => 'Manage', + 'configure' => 'ضبط', + 'manage' => 'إدارة', 'fullscreen' => 'شاشة كاملة', - 'favourite' => 'Favourite', - 'unfavourite' => 'Unfavourite', - 'next' => 'Next', - 'previous' => 'Previous', - 'filter_active' => 'Active Filter:', - 'filter_clear' => 'Clear Filter', - 'download' => 'Download', - 'open_in_tab' => 'Open in Tab', - 'open' => 'Open', + 'favourite' => 'أضف إلى المفضلة', + 'unfavourite' => 'إزالة من المفضلة', + 'next' => 'التالي', + 'previous' => 'السابق', + 'filter_active' => 'الفلاتر المفعلة:', + 'filter_clear' => 'مسح الفلاتر', + 'download' => 'تنزيل', + 'open_in_tab' => 'فتح في علامة تبويب', + 'open' => 'فتح', // Sort Options 'sort_options' => 'خيارات الفرز', @@ -69,7 +69,7 @@ return [ 'no_activity' => 'لا يوجد نشاط لعرضه', 'no_items' => 'لا توجد عناصر متوفرة', 'back_to_top' => 'العودة إلى الأعلى', - 'skip_to_main_content' => 'Skip to main content', + 'skip_to_main_content' => 'تخطى إلى المحتوى الرئيسي', 'toggle_details' => 'عرض / إخفاء التفاصيل', 'toggle_thumbnails' => 'عرض / إخفاء الصور المصغرة', 'details' => 'التفاصيل', @@ -77,21 +77,21 @@ return [ 'list_view' => 'عرض منسدل', 'default' => 'افتراضي', 'breadcrumb' => 'شريط التنقل', - 'status' => 'Status', - 'status_active' => 'Active', - 'status_inactive' => 'Inactive', - 'never' => 'Never', - 'none' => 'None', + 'status' => 'الحالة', + 'status_active' => 'نشط', + 'status_inactive' => 'غير نشط', + 'never' => 'مطلقاً', + 'none' => 'لا شَيْء', // Header - 'homepage' => 'Homepage', + 'homepage' => 'الصفحة الرئيسية', 'header_menu_expand' => 'عرض القائمة', 'profile_menu' => 'قائمة ملف التعريف', 'view_profile' => 'عرض الملف الشخصي', 'edit_profile' => 'تعديل الملف الشخصي', 'dark_mode' => 'الوضع المظلم', 'light_mode' => 'الوضع المضيء', - 'global_search' => 'Global Search', + 'global_search' => 'البحث العام', // Layout tabs 'tab_info' => 'معلومات', diff --git a/lang/ar/entities.php b/lang/ar/entities.php index 1c9c15bd9..30ad78454 100644 --- a/lang/ar/entities.php +++ b/lang/ar/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'مُحدث :timeLength', 'meta_updated_name' => 'مُحدث :timeLength بواسطة :user', 'meta_owned_name' => 'Owned by :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'اختيار الكيان', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'صور', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'تعديل كتاب :bookName', 'books_form_book_name' => 'اسم الكتاب', 'books_save' => 'حفظ الكتاب', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'أذونات الكتاب', 'books_permissions_updated' => 'تم تحديث أذونات الكتاب', 'books_empty_contents' => 'لم يتم إنشاء أي صفحات أو فصول لهذا الكتاب.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'حذف المسودة', 'pages_delete_success' => 'تم حذف الصفحة', 'pages_delete_draft_success' => 'تم حذف المسودة', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'تأكيد حذف الصفحة؟', 'pages_delete_draft_confirm' => 'تأكيد حذف المسودة؟', 'pages_editing_named' => ':pageName قيد التعديل', @@ -405,7 +409,7 @@ return [ // References 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/ar/errors.php b/lang/ar/errors.php index ffd7b2e11..70bcf2137 100644 --- a/lang/ar/errors.php +++ b/lang/ar/errors.php @@ -19,14 +19,13 @@ return [ 'ldap_extension_not_installed' => 'لم يتم تثبيت إضافة LDAP PHP', 'ldap_cannot_connect' => 'لا يمكن الاتصال بخادم ldap, فشل الاتصال المبدئي', 'saml_already_logged_in' => 'تم تسجيل الدخول بالفعل', - 'saml_user_not_registered' => 'المستخدم :name غير مسجل ويتم تعطيل التسجيل التلقائي', 'saml_no_email_address' => 'تعذر العثور على عنوان بريد إلكتروني، لهذا المستخدم، في البيانات المقدمة من نظام المصادقة الخارجي', 'saml_invalid_response_id' => 'لم يتم التعرف على الطلب من نظام التوثيق الخارجي من خلال عملية تبدأ بهذا التطبيق. العودة بعد تسجيل الدخول يمكن أن يسبب هذه المشكلة.', 'saml_fail_authed' => 'تسجيل الدخول باستخدام :system فشل، النظام لم يوفر التفويض الناجح', - 'oidc_already_logged_in' => 'Already logged in', - 'oidc_user_not_registered' => 'The user :name is not registered and automatic registration is disabled', - 'oidc_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system', - 'oidc_fail_authed' => 'Login using :system failed, system did not provide successful authorization', + 'oidc_already_logged_in' => 'تم تسجيل الدخول مسبقاً', + 'oidc_user_not_registered' => 'المستخدم :name غير مسجل و لايمكن التسجيل بشكل اتوماتيكي', + 'oidc_no_email_address' => 'تعذر العثور على عنوان بريد إلكتروني، لهذا المستخدم، في البيانات المقدمة من نظام المصادقة الخارجي', + 'oidc_fail_authed' => 'تسجيل الدخول باستخدام :system فشل، النظام لم يوفر التفويض الناجح', 'social_no_action_defined' => 'لم يتم تعريف أي إجراء', 'social_login_bad_response' => "حصل خطأ خلال تسجيل الدخول باستخدام :socialAccount \n:error", 'social_account_in_use' => 'حساب :socialAccount قيد الاستخدام حالياً, الرجاء محاولة الدخول باستخدام خيار :socialAccount.', @@ -44,30 +43,30 @@ return [ 'cannot_get_image_from_url' => 'لا يمكن الحصول على الصورة من :url', 'cannot_create_thumbs' => 'لا يمكن للخادم إنشاء صور مصغرة. الرجاء التأكد من تثبيت إضافة GD PHP.', 'server_upload_limit' => 'الخادم لا يسمح برفع ملفات بهذا الحجم. الرجاء محاولة الرفع بحجم أصغر.', - 'server_post_limit' => 'The server cannot receive the provided amount of data. Try again with less data or a smaller file.', + 'server_post_limit' => 'لا يمكن للخادم تلقي كمية البيانات المتاحة. حاول مرة أخرى باستخدام بيانات أقل أو ملف أصغر.', 'uploaded' => 'الخادم لا يسمح برفع ملفات بهذا الحجم. الرجاء محاولة الرفع بحجم أصغر.', // Drawing & Images 'image_upload_error' => 'حدث خطأ خلال رفع الصورة', 'image_upload_type_error' => 'صيغة الصورة المرفوعة غير صالحة', - 'image_upload_replace_type' => 'Image file replacements must be of the same type', - 'image_upload_memory_limit' => 'Failed to handle image upload and/or create thumbnails due to system resource limits.', - 'image_thumbnail_memory_limit' => 'Failed to create image size variations due to system resource limits.', - 'image_gallery_thumbnail_memory_limit' => 'Failed to create gallery thumbnails due to system resource limits.', - 'drawing_data_not_found' => 'Drawing data could not be loaded. The drawing file might no longer exist or you may not have permission to access it.', + 'image_upload_replace_type' => 'يجب أن يكون استبدال ملف الصورة من نفس النوع', + 'image_upload_memory_limit' => 'فشل في التعامل مع تحميل الصورة و/أو إنشاء الصور المصغرة بسبب حدود موارد النظام.', + 'image_thumbnail_memory_limit' => 'فشل في إنشاء تغيرات حجم الصورة بسبب حدود موارد النظام.', + 'image_gallery_thumbnail_memory_limit' => 'فشل في إنشاء الصور المصغرة للمعرض بسبب حدود موارد النظام.', + 'drawing_data_not_found' => 'تعذر تحميل بيانات الرسم. قد لا يكون ملف الرسم موجودا أو قد لا يكون لديك إذن للوصول إليه.', // Attachments 'attachment_not_found' => 'لم يتم العثور على المرفق', - 'attachment_upload_error' => 'An error occurred uploading the attachment file', + 'attachment_upload_error' => 'حدث خطأ أثناء تحميل الملف المرفق', // Pages 'page_draft_autosave_fail' => 'فشل حفظ المسودة. الرجاء التأكد من وجود اتصال بالإنترنت قبل حفظ الصفحة', - 'page_draft_delete_fail' => 'Failed to delete page draft and fetch current page saved content', + 'page_draft_delete_fail' => 'فشل في حذف مسودة الصفحة وجلب محتوى الصفحة الحالية المحفوظة', 'page_custom_home_deletion' => 'لا يمكن حذف الصفحة إذا كانت محددة كصفحة رئيسية', // Entities 'entity_not_found' => 'الكيان غير موجود', - 'bookshelf_not_found' => 'Shelf not found', + 'bookshelf_not_found' => 'رف الكتب غير موجود', 'book_not_found' => 'لم يتم العثور على الكتاب', 'page_not_found' => 'لم يتم العثور على الصفحة', 'chapter_not_found' => 'لم يتم العثور على الفصل', @@ -96,9 +95,9 @@ return [ '404_page_not_found' => 'لم يتم العثور على الصفحة', 'sorry_page_not_found' => 'عفواً, لا يمكن العثور على الصفحة التي تبحث عنها.', 'sorry_page_not_found_permission_warning' => 'إذا كنت تتوقع أن تكون هذه الصفحة موجودة، قد لا يكون لديك تصريح بمشاهدتها.', - 'image_not_found' => 'Image Not Found', - 'image_not_found_subtitle' => 'Sorry, The image file you were looking for could not be found.', - 'image_not_found_details' => 'If you expected this image to exist it might have been deleted.', + 'image_not_found' => 'لم يتم العثور على الصورة', + 'image_not_found_subtitle' => 'عذراً، لم يتم العثور على ملف الصورة الذي كنت تبحث عنه.', + 'image_not_found_details' => 'إذا كنت تتوقع وجود هذه الصورة ربما تم حذفها.', 'return_home' => 'العودة للصفحة الرئيسية', 'error_occurred' => 'حدث خطأ', 'app_down' => ':appName لا يعمل حالياً', @@ -116,5 +115,5 @@ return [ 'maintenance_test_email_failure' => 'حدث خطأ عند إرسال بريد إلكتروني تجريبي:', // HTTP errors - 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', + 'http_ssr_url_no_match' => 'الرابط لا يتطابق مع الاعدادات المسموح بها لاستضافة SSR', ]; diff --git a/lang/ar/notifications.php b/lang/ar/notifications.php index 5539ae9a9..1afd23f1d 100644 --- a/lang/ar/notifications.php +++ b/lang/ar/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/ar/settings.php b/lang/ar/settings.php index 309c9a06c..ca0f2fdb3 100644 --- a/lang/ar/settings.php +++ b/lang/ar/settings.php @@ -9,8 +9,8 @@ return [ // Common Messages 'settings' => 'الإعدادات', 'settings_save' => 'حفظ الإعدادات', - 'system_version' => 'System Version', - 'categories' => 'Categories', + 'system_version' => 'إصدار النظام', + 'categories' => 'التصنيفات', // App Settings 'app_customization' => 'تخصيص', @@ -26,23 +26,23 @@ return [ 'app_secure_images' => 'تفعيل حماية أكبر لرفع الصور؟', 'app_secure_images_toggle' => 'لمزيد من الحماية', 'app_secure_images_desc' => 'لتحسين أداء النظام, ستكون جميع الصور متاحة للعامة. هذا الخيار يضيف سلسلة من الحروف والأرقام العشوائية صعبة التخمين إلى رابط الصورة. الرجاء التأكد من تعطيل فهرسة المسارات لمنع الوصول السهل.', - 'app_default_editor' => 'Default Page Editor', - 'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.', + 'app_default_editor' => 'محرر الصفحة الافتراضي', + 'app_default_editor_desc' => 'حدد أي محرر سيتم استخدامه بشكل افتراضي عند تحرير صفحات جديدة. يمكن تجاوز هذا على مستوى الصفحة حيث تسمح الأذونات.', 'app_custom_html' => 'Custom HTML head content', 'app_custom_html_desc' => 'سيتم إدراج أي محتوى مضاف هنا في الجزء السفلي من قسم من كل صفحة. هذا أمر مفيد لتجاوز الأنماط أو إضافة رمز التحليل.', 'app_custom_html_disabled_notice' => 'تم تعطيل محتوى HTML الرئيسي المخصص في صفحة الإعدادات هذه لضمان عكس أي تغييرات متتالية.', 'app_logo' => 'شعار التطبيق', - 'app_logo_desc' => 'This is used in the application header bar, among other areas. This image should be 86px in height. Large images will be scaled down.', - 'app_icon' => 'Application Icon', - 'app_icon_desc' => 'This icon is used for browser tabs and shortcut icons. This should be a 256px square PNG image.', + 'app_logo_desc' => 'يستخدم هذا في شريط رأس التطبيق، ضمن مجالات أخرى. يجب أن تكون هذه الصورة 86 بكسل في الطول. سيتم تقليص الصور الكبيرة.', + 'app_icon' => 'أيقونة التطبيق', + 'app_icon_desc' => 'يستخدم هذا الرمز لعلامات تبويب المتصفح والرموز المختصرة. يجب أن تكون هذه صورة PNG مربعة 256px.', 'app_homepage' => 'الصفحة الرئيسية للتطبيق', 'app_homepage_desc' => 'الرجاء اختيار صفحة لتصبح الصفحة الرئيسية بدل من الافتراضية. سيتم تجاهل جميع الأذونات الخاصة بالصفحة المختارة.', 'app_homepage_select' => 'اختر صفحة', - 'app_footer_links' => 'Footer Links', - 'app_footer_links_desc' => 'Add links to show within the site footer. These will be displayed at the bottom of most pages, including those that do not require login. You can use a label of "trans::" to use system-defined translations. For example: Using "trans::common.privacy_policy" will provide the translated text "Privacy Policy" and "trans::common.terms_of_service" will provide the translated text "Terms of Service".', - 'app_footer_links_label' => 'Link Label', - 'app_footer_links_url' => 'Link URL', - 'app_footer_links_add' => 'Add Footer Link', + 'app_footer_links' => 'روابط تذييل الصفحة', + 'app_footer_links_desc' => 'إضافة روابط لعرضها داخل تذييل الموقع. سيتم عرضها في أسفل معظم الصفحات، بما في ذلك تلك التي لا تتطلب تسجيل الدخول. يمكنك استخدام تسمية "trans::لاستخدام الترجمات المحددة في النظام. على سبيل المثال: باستخدام "trans::common.privacy_policy" سيوفر النص المترجم "Privacy Policy" و "trans::common.terms_of_service" سيوفر النص المترجم "شروط الخدمة".', + 'app_footer_links_label' => 'تسمية الرابط', + 'app_footer_links_url' => 'عنوان الرابط', + 'app_footer_links_add' => 'إضافة رابط تذييل الصفحة', 'app_disable_comments' => 'تعطيل التعليقات', 'app_disable_comments_toggle' => 'تعطيل التعليقات', 'app_disable_comments_desc' => 'تعطيل التعليقات على جميع الصفحات داخل التطبيق. التعليقات الموجودة من الأصل لن تكون ظاهرة.', @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/ar/validation.php b/lang/ar/validation.php index 94112789d..a9793c548 100644 --- a/lang/ar/validation.php +++ b/lang/ar/validation.php @@ -15,7 +15,7 @@ return [ 'alpha_dash' => 'يجب أن يقتصر :attribute على حروف أو أرقام أو شرطات فقط.', 'alpha_num' => 'يجب أن يقتصر :attribute على الحروف والأرقام فقط.', 'array' => 'يجب أن تكون السمة مصفوفة.', - 'backup_codes' => 'The provided code is not valid or has already been used.', + 'backup_codes' => 'الرمز المقدم غير صالح أو تم استخدامه بالفعل.', 'before' => 'يجب أن يكون التاريخ :attribute قبل :date.', 'between' => [ 'numeric' => 'يجب أن يكون :attribute بين :min و :max.', @@ -32,7 +32,7 @@ return [ 'digits_between' => 'يجب أن يكون :attribute بعدد خانات بين :min و :max.', 'email' => 'يجب أن يكون :attribute عنوان بريد إلكتروني صالح.', 'ends_with' => 'يجب أن تنتهي السمة بأحد القيم التالية', - 'file' => 'The :attribute must be provided as a valid file.', + 'file' => 'يجب توفير :attribute كملف صالح.', 'filled' => 'حقل :attribute مطلوب.', 'gt' => [ 'numeric' => 'يجب أن تكون السمة أكبر من: القيمة.', @@ -100,7 +100,7 @@ return [ ], 'string' => 'يجب أن تكون السمة: سلسلة.', 'timezone' => 'يجب أن تكون :attribute منطقة صالحة.', - 'totp' => 'The provided code is not valid or has expired.', + 'totp' => 'الرمز المقدم غير صالح أو انتهت صلاحيته.', 'unique' => 'تم حجز :attribute من قبل.', 'url' => 'صيغة :attribute غير صالحة.', 'uploaded' => 'تعذر تحميل الملف. قد لا يقبل الخادم ملفات بهذا الحجم.', diff --git a/lang/bg/entities.php b/lang/bg/entities.php index e4ed83ef9..fb57bf1bc 100644 --- a/lang/bg/entities.php +++ b/lang/bg/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Актуализирано :timeLength', 'meta_updated_name' => 'Актуализирано преди :timeLength от :user', 'meta_owned_name' => 'Притежавано от :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Избор на обект', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Изображения', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Редактирай книга :bookName', 'books_form_book_name' => 'Име на книга', 'books_save' => 'Запази книга', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Настройки за достъп до книгата', 'books_permissions_updated' => 'Настройките за достъп до книгата бяха обновени', 'books_empty_contents' => 'Няма създадени страници или глави към тази книга.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Изтрий чернова', 'pages_delete_success' => 'Страницата е изтрита', 'pages_delete_draft_success' => 'Черновата на страницата бе изтрита', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Сигурни ли сте, че искате да изтриете тази страница?', 'pages_delete_draft_confirm' => 'Сигурни ли сте, че искате да изтриете тази чернова?', 'pages_editing_named' => 'Редактиране на страница :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/bg/errors.php b/lang/bg/errors.php index 91c7b6f60..fbc042a05 100644 --- a/lang/bg/errors.php +++ b/lang/bg/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'LDAP PHP не беше инсталирана', 'ldap_cannot_connect' => 'Не може да се свържете с Ldap сървъра, първоначалната връзка се разпадна', 'saml_already_logged_in' => 'Вече сте влезли', - 'saml_user_not_registered' => 'Потребителят :name не е регистриран и автоматичната регистрация не е достъпна', 'saml_no_email_address' => 'Не успяхме да намерим емейл адрес, за този потребител, от информацията предоставена от външната система', 'saml_invalid_response_id' => 'Заявката от външната система не е разпознат от процеса започнат от това приложение. Връщането назад след влизане може да породи този проблем.', 'saml_fail_authed' => 'Влизането чрез :system не беше успешно, системата не успя да удостовери потребителя', diff --git a/lang/bg/notifications.php b/lang/bg/notifications.php index 5539ae9a9..1afd23f1d 100644 --- a/lang/bg/notifications.php +++ b/lang/bg/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/bg/settings.php b/lang/bg/settings.php index df500db44..f4654135b 100644 --- a/lang/bg/settings.php +++ b/lang/bg/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/bs/entities.php b/lang/bs/entities.php index 5955b47e7..a8a49fe1c 100644 --- a/lang/bs/entities.php +++ b/lang/bs/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Ažurirana :timeLength', 'meta_updated_name' => 'Ažurirana :timeLength od :user', 'meta_owned_name' => 'Vlasnik je :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Odaberi entitet', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Slike', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Uredi knjigu :bookName', 'books_form_book_name' => 'Naziv knjige', 'books_save' => 'Spremi knjigu', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Dozvole knjige', 'books_permissions_updated' => 'Dozvole knjige su ažurirane', 'books_empty_contents' => 'Za ovu knjigu nisu napravljene ni stranice ni poglavlja.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Delete Draft Page', 'pages_delete_success' => 'Page deleted', 'pages_delete_draft_success' => 'Draft page deleted', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Are you sure you want to delete this page?', 'pages_delete_draft_confirm' => 'Are you sure you want to delete this draft page?', 'pages_editing_named' => 'Editing Page :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/bs/errors.php b/lang/bs/errors.php index 8af5a6c78..4f3d61066 100644 --- a/lang/bs/errors.php +++ b/lang/bs/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'LDAP PHP ekstenzija nije instalirana', 'ldap_cannot_connect' => 'Nije se moguće povezati sa ldap serverom, incijalna konekcija nije uspjela', 'saml_already_logged_in' => 'Već prijavljeni', - 'saml_user_not_registered' => 'Korisnik :user nije registrovan i automatska registracija je onemogućena', 'saml_no_email_address' => 'E-mail adresa za ovog korisnika nije nađena u podacima dobijenim od eksternog autentifikacijskog sistema', 'saml_invalid_response_id' => 'Proces, koji je pokrenula ova aplikacija, nije prepoznao zahtjev od eksternog sistema za autentifikaciju. Navigacija nazad nakon prijave može uzrokovati ovaj problem.', 'saml_fail_authed' => 'Prijava koristeći :system nije uspjela, sistem nije obezbijedio uspješnu autorizaciju', diff --git a/lang/bs/notifications.php b/lang/bs/notifications.php index 5539ae9a9..1afd23f1d 100644 --- a/lang/bs/notifications.php +++ b/lang/bs/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/bs/settings.php b/lang/bs/settings.php index c5ca662c3..03e9bf462 100644 --- a/lang/bs/settings.php +++ b/lang/bs/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/ca/activities.php b/lang/ca/activities.php index 03174ff85..fb9071174 100644 --- a/lang/ca/activities.php +++ b/lang/ca/activities.php @@ -11,11 +11,11 @@ return [ 'page_update' => 'ha actualitzat la pàgina', 'page_update_notification' => 'Pàgina actualitzada correctament', 'page_delete' => 'ha suprimit una pàgina', - 'page_delete_notification' => 'Imatge esborrada correctament', + 'page_delete_notification' => 'Pàgina suprimida correctament', 'page_restore' => 'ha restaurat la pàgina', 'page_restore_notification' => 'Pàgina restaurada correctament', 'page_move' => 'ha mogut la pàgina', - 'page_move_notification' => 'Page successfully moved', + 'page_move_notification' => 'Pàgina moguda correctament', // Chapters 'chapter_create' => 'ha creat el capítol', @@ -23,101 +23,101 @@ return [ 'chapter_update' => 'ha actualitzat el capítol', 'chapter_update_notification' => 'Capítol actualitzat correctament', 'chapter_delete' => 'ha suprimit un capítol', - 'chapter_delete_notification' => 'Capítol esborrat correctament', + 'chapter_delete_notification' => 'Capítol suprimit correctament', 'chapter_move' => 'ha mogut el capítol', - 'chapter_move_notification' => 'Chapter successfully moved', + 'chapter_move_notification' => 'Capítol mogut correctament', // Books 'book_create' => 'ha creat el llibre', 'book_create_notification' => 'Llibre creat correctament', - 'book_create_from_chapter' => 'converted chapter to book', - 'book_create_from_chapter_notification' => 'Chapter successfully converted to a book', + 'book_create_from_chapter' => 'ha convertit un capítol en el llibre', + 'book_create_from_chapter_notification' => 'Capítol convertit en llibre correctament', 'book_update' => 'ha actualitzat el llibre', - 'book_update_notification' => 'Book successfully updated', + 'book_update_notification' => 'Llibre actualitzat correctament', 'book_delete' => 'ha suprimit un llibre', - 'book_delete_notification' => 'Book successfully deleted', + 'book_delete_notification' => 'Llibre suprimit correctament', 'book_sort' => 'ha ordenat el llibre', - 'book_sort_notification' => 'Book successfully re-sorted', + 'book_sort_notification' => 'Llibre reordenat correctament', // Bookshelves - 'bookshelf_create' => 'created shelf', - 'bookshelf_create_notification' => 'Shelf successfully created', - 'bookshelf_create_from_book' => 'converted book to shelf', - 'bookshelf_create_from_book_notification' => 'Book successfully converted to a shelf', - 'bookshelf_update' => 'updated shelf', - 'bookshelf_update_notification' => 'Shelf successfully updated', - 'bookshelf_delete' => 'deleted shelf', - 'bookshelf_delete_notification' => 'Shelf successfully deleted', + 'bookshelf_create' => 'ha creat el prestatge', + 'bookshelf_create_notification' => 'Prestatge creat correctament', + 'bookshelf_create_from_book' => 'ha convertit un llibre en el prestatge', + 'bookshelf_create_from_book_notification' => 'Llibre convertit en prestatge correctament', + 'bookshelf_update' => 'ha actualitzat el prestatge', + 'bookshelf_update_notification' => 'Prestatge actualitzat correctament', + 'bookshelf_delete' => 'ha suprimit un prestatge', + 'bookshelf_delete_notification' => 'Prestatge suprimit correctament', // Revisions - 'revision_restore' => 'restored revision', - 'revision_delete' => 'deleted revision', - 'revision_delete_notification' => 'Revision successfully deleted', + 'revision_restore' => 'ha restaurat la revisió', + 'revision_delete' => 'ha suprimit una revisió', + 'revision_delete_notification' => 'Revisió suprimida correctament', // Favourites - 'favourite_add_notification' => '":name" has been added to your favourites', - 'favourite_remove_notification' => '":name" has been removed from your favourites', + 'favourite_add_notification' => 'S’ha afegit «:name» als vostres preferits', + 'favourite_remove_notification' => 'S’ha suprimit «:name» dels vostres preferits', // Watching - 'watch_update_level_notification' => 'Watch preferences successfully updated', + 'watch_update_level_notification' => 'Preferències de seguiment actualitzades correctament', // Auth - 'auth_login' => 'logged in', - 'auth_register' => 'registered as new user', - 'auth_password_reset_request' => 'requested user password reset', - 'auth_password_reset_update' => 'reset user password', - 'mfa_setup_method' => 'configured MFA method', - 'mfa_setup_method_notification' => 'Multi-factor method successfully configured', - 'mfa_remove_method' => 'removed MFA method', - 'mfa_remove_method_notification' => 'Multi-factor method successfully removed', + 'auth_login' => 'ha iniciat la sessió', + 'auth_register' => 's’ha registrat com a usuari nou', + 'auth_password_reset_request' => 'ha sol·licitat un restabliment de la contrasenya', + 'auth_password_reset_update' => 'ha restablert la contrasenya de l’usuari', + 'mfa_setup_method' => 'ha configurat un mètode d’autenticació de múltiple factor', + 'mfa_setup_method_notification' => 'Mètode d’autenticació de múltiple factor configurat correctament', + 'mfa_remove_method' => 'ha suprimit un mètode d’autenticació de múltiple factor', + 'mfa_remove_method_notification' => 'Mètode d’autenticació de múltiple factor suprimit correctament', // Settings - 'settings_update' => 'updated settings', - 'settings_update_notification' => 'Settings successfully updated', - 'maintenance_action_run' => 'ran maintenance action', + 'settings_update' => 'ha actualitzat la configuració', + 'settings_update_notification' => 'Configuració actualitzada correctament', + 'maintenance_action_run' => 'ha executat una acció de manteniment', // Webhooks - 'webhook_create' => 'created webhook', - 'webhook_create_notification' => 'Webhook successfully created', - 'webhook_update' => 'updated webhook', - 'webhook_update_notification' => 'Webhook successfully updated', - 'webhook_delete' => 'deleted webhook', - 'webhook_delete_notification' => 'Webhook successfully deleted', + 'webhook_create' => 'ha creat un webhook', + 'webhook_create_notification' => 'Webhook creat correctament', + 'webhook_update' => 'ha actualitzat un webhook', + 'webhook_update_notification' => 'Webhook actualitzat correctament', + 'webhook_delete' => 'ha suprimit un webhook', + 'webhook_delete_notification' => 'Webhook suprimit correctament', // Users - 'user_create' => 'created user', - 'user_create_notification' => 'User successfully created', - 'user_update' => 'updated user', - 'user_update_notification' => 'User successfully updated', - 'user_delete' => 'deleted user', - 'user_delete_notification' => 'User successfully removed', + 'user_create' => 'ha creat l’usuari', + 'user_create_notification' => 'Usuari creat correctament', + 'user_update' => 'ha actualitzat l’usuari', + 'user_update_notification' => 'Usuari actualitzat correctament', + 'user_delete' => 'ha suprimit un usuari', + 'user_delete_notification' => 'Usuari suprimit correctament', // API Tokens - 'api_token_create' => 'created api token', - 'api_token_create_notification' => 'API token successfully created', - 'api_token_update' => 'updated api token', - 'api_token_update_notification' => 'API token successfully updated', - 'api_token_delete' => 'deleted api token', - 'api_token_delete_notification' => 'API token successfully deleted', + 'api_token_create' => 'ha creat un testimoni d’API', + 'api_token_create_notification' => 'Testimoni d’API creat correctament', + 'api_token_update' => 'ha actualitzat un testimoni d’API', + 'api_token_update_notification' => 'Testimoni d’API actualitzat correctament', + 'api_token_delete' => 'ha suprimit un testumoni d’API', + 'api_token_delete_notification' => 'Testimoni d’API suprimit correctament', // Roles - 'role_create' => 'created role', - 'role_create_notification' => 'Role successfully created', - 'role_update' => 'updated role', - 'role_update_notification' => 'Role successfully updated', - 'role_delete' => 'deleted role', - 'role_delete_notification' => 'Role successfully deleted', + 'role_create' => 'ha creat el rol', + 'role_create_notification' => 'Rol creat correctament', + 'role_update' => 'ha actualitzat el rol', + 'role_update_notification' => 'Rol actualitzat correctament', + 'role_delete' => 'ha suprimit un rol', + 'role_delete_notification' => 'Rol suprimit correctament', // Recycle Bin - 'recycle_bin_empty' => 'emptied recycle bin', - 'recycle_bin_restore' => 'restored from recycle bin', - 'recycle_bin_destroy' => 'removed from recycle bin', + 'recycle_bin_empty' => 'ha buidat la paperera de reciclatge', + 'recycle_bin_restore' => 'ha restaurat de la paperera de reciclatge', + 'recycle_bin_destroy' => 'ha suprimit de la paperera de reciclatge', // Comments 'commented_on' => 'ha comentat a', - 'comment_create' => 'added comment', - 'comment_update' => 'updated comment', - 'comment_delete' => 'deleted comment', + 'comment_create' => 'ha afegit el comentari', + 'comment_update' => 'ha actualitzat el comentari', + 'comment_delete' => 'ha suprimit un comentari', // Other 'permissions_update' => 'ha actualitzat els permisos', diff --git a/lang/ca/auth.php b/lang/ca/auth.php index a156ecee3..714c095b6 100644 --- a/lang/ca/auth.php +++ b/lang/ca/auth.php @@ -7,29 +7,29 @@ return [ 'failed' => 'Les credencials no coincideixen amb les que hi ha emmagatzemades.', - 'throttle' => 'Massa intents d\'inici de sessió. Torna-ho a provar d\'aquí a :seconds segons.', + 'throttle' => 'Massa intents d’inici de sessió. Torneu-ho a provar d’aquí a :seconds segons.', // Login & Register - 'sign_up' => 'Registra-m\'hi', + 'sign_up' => 'Registra-m’hi', 'log_in' => 'Inicia la sessió', 'log_in_with' => 'Inicia la sessió amb :socialDriver', - 'sign_up_with' => 'Registra-m\'hi amb :socialDriver', + 'sign_up_with' => 'Registra-m’hi amb :socialDriver', 'logout' => 'Tanca la sessió', 'name' => 'Nom', - 'username' => 'Nom d\'usuari', + 'username' => 'Nom d’usuari', 'email' => 'Adreça electrònica', 'password' => 'Contrasenya', 'password_confirm' => 'Confirmeu la contrasenya', - 'password_hint' => 'Must be at least 8 characters', + 'password_hint' => 'Cal que tingui un mínim de 8 caràcters', 'forgot_password' => 'Heu oblidat la contrasenya?', - 'remember_me' => 'Recorda\'m', + 'remember_me' => 'Recorda’m', 'ldap_email_hint' => 'Introduïu una adreça electrònica per a aquest compte.', 'create_account' => 'Crea el compte', 'already_have_account' => 'Ja teniu un compte?', 'dont_have_account' => 'No teniu cap compte?', - 'social_login' => 'Inici de sessió amb xarxes social', - 'social_registration' => 'Registre social', + 'social_login' => 'Inici de sessió amb xarxes socials', + 'social_registration' => 'Registre amb xarxes socials', 'social_registration_text' => 'Registreu-vos i inicieu la sessió fent servir un altre servei.', 'register_thanks' => 'Gràcies per registrar-vos!', @@ -39,79 +39,79 @@ return [ 'register_success' => 'Gràcies per registrar-vos! Ja us hi heu registrat i heu iniciat la sessió.', // Login auto-initiation - 'auto_init_starting' => 'Attempting Login', - 'auto_init_starting_desc' => 'We\'re contacting your authentication system to start the login process. If there\'s no progress after 5 seconds you can try clicking the link below.', - 'auto_init_start_link' => 'Proceed with authentication', + 'auto_init_starting' => 'S’està provant d’iniciar la sessió', + 'auto_init_starting_desc' => 'Estem contactant amb el vostre sistema d’autenticació per a començar el procés d’inici de sessió. Si no hi ha cap progrés d’aquí a 5 segons, proveu de fer clic a l’enllaç de sota.', + 'auto_init_start_link' => 'Continua amb l’autenticació', // Password Reset 'reset_password' => 'Restableix la contrasenya', 'reset_password_send_instructions' => 'Introduïu la vostra adreça electrònica a continuació i us enviarem un correu electrònic amb un enllaç per a restablir la contrasenya.', - 'reset_password_send_button' => 'Envia l\'enllaç de restabliment', - 'reset_password_sent' => 'S\'enviarà un enllaç per a restablir la contrasenya a :email, si es troba aquesta adreça al sistema.', - 'reset_password_success' => 'La vostra contrasenya s\'ha restablert correctament.', + 'reset_password_send_button' => 'Envia l’enllaç de restabliment', + 'reset_password_sent' => 'S’enviarà un enllaç per a restablir la contrasenya a :email, si es troba aquesta adreça al sistema.', + 'reset_password_success' => 'La contrasenya s’ha restablert correctament.', 'email_reset_subject' => 'Restabliu la contrasenya a :appName', - 'email_reset_text' => 'Rebeu aquest correu electrònic perquè heu rebut una petició de restabliment de contrasenya per al vostre compte.', - 'email_reset_not_requested' => 'Si no heu demanat restablir la contrasenya, no cal que prengueu cap acció.', + 'email_reset_text' => 'Rebeu aquest correu electrònic perquè s’ha fet una petició de restabliment de contrasenya per al vostre compte.', + 'email_reset_not_requested' => 'Si no heu demanat restablir la contrasenya, no cal que emprengueu cap acció.', // Email Confirmation 'email_confirm_subject' => 'Confirmeu la vostra adreça electrònica a :appName', 'email_confirm_greeting' => 'Gràcies per unir-vos a :appName!', 'email_confirm_text' => 'Confirmeu la vostra adreça electrònica fent clic al botó a continuació:', 'email_confirm_action' => 'Confirma el correu', - 'email_confirm_send_error' => 'Cal confirmar l\'adreça electrònica, però el sistema no ha pogut enviar el correu electrònic. Poseu-vos en contacte amb l\'administrador perquè s\'asseguri que el correu electrònic està ben configurat.', - 'email_confirm_success' => 'Your email has been confirmed! You should now be able to login using this email address.', - 'email_confirm_resent' => 'S\'ha tornat a enviar el correu electrònic de confirmació. Reviseu la vostra safata d\'entrada.', - 'email_confirm_thanks' => 'Thanks for confirming!', - 'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.', + 'email_confirm_send_error' => 'Cal confirmar l’adreça electrònica, però el sistema no ha pogut enviar el correu electrònic. Poseu-vos en contacte amb l’administrador perquè s’asseguri que el correu electrònic està ben configurat.', + 'email_confirm_success' => 'Heu confirmat el vostre correu electrònic! Ara hauríeu de poder iniciar la sessió fent servir aquesta adreça electrònica.', + 'email_confirm_resent' => 'S’ha tornat a enviar el correu electrònic de confirmació. Reviseu la vostra safata d’entrada.', + 'email_confirm_thanks' => 'Gràcies per la confirmació!', + 'email_confirm_thanks_desc' => 'Espereu uns instants mentre gestionem la confirmació. Si no se us redirigeix d’aquí a 3 segons, premeu l’enllaç «Continua» de sota per a continuar.', 'email_not_confirmed' => 'Adreça electrònica no confirmada', 'email_not_confirmed_text' => 'La vostra adreça electrònica encara no està confirmada.', - 'email_not_confirmed_click_link' => 'Feu clic a l\'enllaç del correu electrònic que us vam enviar poc després que us registréssiu.', - 'email_not_confirmed_resend' => 'Si no podeu trobar el correu, podeu tornar a enviar el correu electrònic de confirmació enviant el formulari a continuació.', + 'email_not_confirmed_click_link' => 'Feu clic a l’enllaç del correu electrònic que us vam enviar poc després que us registréssiu.', + 'email_not_confirmed_resend' => 'Si no el trobeu, podeu tornar a enviar el correu electrònic de confirmació enviant el formulari a continuació.', 'email_not_confirmed_resend_button' => 'Torna a enviar el correu de confirmació', // User Invite 'user_invite_email_subject' => 'Us han convidat a unir-vos a :appName!', - 'user_invite_email_greeting' => 'Us hem creat un compte en el vostre nom a :appName.', + 'user_invite_email_greeting' => 'S’ha creat un compte en el vostre nom a :appName.', 'user_invite_email_text' => 'Feu clic al botó a continuació per a definir una contrasenya per al compte i obtenir-hi accés:', 'user_invite_email_action' => 'Defineix una contrasenya per al compte', 'user_invite_page_welcome' => 'Us donem la benvinguda a :appName!', 'user_invite_page_text' => 'Per a enllestir el vostre compte i obtenir-hi accés, cal que definiu una contrasenya, que es farà servir per a iniciar la sessió a :appName en futures visites.', 'user_invite_page_confirm_button' => 'Confirma la contrasenya', - 'user_invite_success_login' => 'Password set, you should now be able to login using your set password to access :appName!', + 'user_invite_success_login' => 'S’ha definit la contrasenya. Ara hauríeu de poder iniciar la sessió fent servir la contrasenya que heu definit per a accedir a :appName!', // Multi-factor Authentication - 'mfa_setup' => 'Setup Multi-Factor Authentication', - 'mfa_setup_desc' => 'Setup multi-factor authentication as an extra layer of security for your user account.', - 'mfa_setup_configured' => 'Already configured', - 'mfa_setup_reconfigure' => 'Reconfigure', - 'mfa_setup_remove_confirmation' => 'Are you sure you want to remove this multi-factor authentication method?', - 'mfa_setup_action' => 'Setup', - 'mfa_backup_codes_usage_limit_warning' => 'You have less than 5 backup codes remaining, Please generate and store a new set before you run out of codes to prevent being locked out of your account.', - 'mfa_option_totp_title' => 'Mobile App', - 'mfa_option_totp_desc' => 'To use multi-factor authentication you\'ll need a mobile application that supports TOTP such as Google Authenticator, Authy or Microsoft Authenticator.', - 'mfa_option_backup_codes_title' => 'Backup Codes', - 'mfa_option_backup_codes_desc' => 'Securely store a set of one-time-use backup codes which you can enter to verify your identity.', - 'mfa_gen_confirm_and_enable' => 'Confirm and Enable', - 'mfa_gen_backup_codes_title' => 'Backup Codes Setup', - 'mfa_gen_backup_codes_desc' => 'Store the below list of codes in a safe place. When accessing the system you\'ll be able to use one of the codes as a second authentication mechanism.', - 'mfa_gen_backup_codes_download' => 'Download Codes', - 'mfa_gen_backup_codes_usage_warning' => 'Each code can only be used once', - 'mfa_gen_totp_title' => 'Mobile App Setup', - 'mfa_gen_totp_desc' => 'To use multi-factor authentication you\'ll need a mobile application that supports TOTP such as Google Authenticator, Authy or Microsoft Authenticator.', - 'mfa_gen_totp_scan' => 'Scan the QR code below using your preferred authentication app to get started.', - 'mfa_gen_totp_verify_setup' => 'Verify Setup', - 'mfa_gen_totp_verify_setup_desc' => 'Verify that all is working by entering a code, generated within your authentication app, in the input box below:', - 'mfa_gen_totp_provide_code_here' => 'Provide your app generated code here', - 'mfa_verify_access' => 'Verify Access', - 'mfa_verify_access_desc' => 'Your user account requires you to confirm your identity via an additional level of verification before you\'re granted access. Verify using one of your configured methods to continue.', - 'mfa_verify_no_methods' => 'No Methods Configured', - 'mfa_verify_no_methods_desc' => 'No multi-factor authentication methods could be found for your account. You\'ll need to set up at least one method before you gain access.', - 'mfa_verify_use_totp' => 'Verify using a mobile app', - 'mfa_verify_use_backup_codes' => 'Verify using a backup code', - 'mfa_verify_backup_code' => 'Backup Code', - 'mfa_verify_backup_code_desc' => 'Enter one of your remaining backup codes below:', - 'mfa_verify_backup_code_enter_here' => 'Enter backup code here', - 'mfa_verify_totp_desc' => 'Enter the code, generated using your mobile app, below:', - 'mfa_setup_login_notification' => 'Multi-factor method configured, Please now login again using the configured method.', + 'mfa_setup' => 'Configura l’autenticació de múltiple factor', + 'mfa_setup_desc' => 'Configureu l’autenticació de múltiple factor com a capa extra de seguretat en el vostre compte d’usuari.', + 'mfa_setup_configured' => 'Ja està configurada', + 'mfa_setup_reconfigure' => 'Torna-la a configurar', + 'mfa_setup_remove_confirmation' => 'Segur que voleu suprimir aquest mètode d’autenticació de múltiple factor?', + 'mfa_setup_action' => 'Configura', + 'mfa_backup_codes_usage_limit_warning' => 'Teniu menys de 5 codis de seguretat restants. Genereu-ne i emmagatzemeu-ne un nou conjunt abans que se us acabin perquè no perdeu l’accés al vostre compte.', + 'mfa_option_totp_title' => 'Aplicació mòbil', + 'mfa_option_totp_desc' => 'Per a fer servir l’autenticació de múltiple factor us caldrà una aplicació mòbil que suporti TOTP, com ara Google Authenticador, Authy o Microsoft Authenticator.', + 'mfa_option_backup_codes_title' => 'Codis de seguretat', + 'mfa_option_backup_codes_desc' => 'Deseu de manera segura codis de seguretat d’un sol ús que podeu introduir per a verificar la vostra identitat.', + 'mfa_gen_confirm_and_enable' => 'Confirma i activa', + 'mfa_gen_backup_codes_title' => 'Configuració de codis de seguretat', + 'mfa_gen_backup_codes_desc' => 'Deseu la següent llista de codis en un lloc segur. Quan accediu al sistema, podeu fer servir un dels codis com a segon mètode d’autenticació.', + 'mfa_gen_backup_codes_download' => 'Baixa els codis', + 'mfa_gen_backup_codes_usage_warning' => 'Cada codi es pot fer servir només una vegada', + 'mfa_gen_totp_title' => 'Configuració de l’aplicació mòbil', + 'mfa_gen_totp_desc' => 'Per a fer servir l’autenticació de múltiple factor us caldrà una aplicació mòbil que suporti TOTP, com ara Google Authenticador, Authy o Microsoft Authenticator.', + 'mfa_gen_totp_scan' => 'Per a començar, escanegeu el codi QR següent fent servir la vostre aplicació d’autenticació preferida.', + 'mfa_gen_totp_verify_setup' => 'Verifica la configuració', + 'mfa_gen_totp_verify_setup_desc' => 'Verifiqueu que tot funciona introduint un codi, generat amb l’aplicació d’autenticació, a la capsa de text següent:', + 'mfa_gen_totp_provide_code_here' => 'Proporcioneu aquí el codi generat per l’aplicació', + 'mfa_verify_access' => 'Verifica l’accés', + 'mfa_verify_access_desc' => 'El vostre compte d’usuari requereix que confirmeu la vostra identitat amb un nivell addicional de verificació abans que pugueu obtenir-hi accés. Verifiqueu-la fent servir un dels vostres mètodes configurats per a continuar.', + 'mfa_verify_no_methods' => 'No hi ha cap mètode configurat', + 'mfa_verify_no_methods_desc' => 'No s’ha trobat cap mètode d’autenticació de múltiple factor al vostre compte. Cal que configureu almenys un mètode abans d’obtenir-hi accés.', + 'mfa_verify_use_totp' => 'Verifica fent servir una aplicació mòbil', + 'mfa_verify_use_backup_codes' => 'Verifica fent servir un codi de seguretat', + 'mfa_verify_backup_code' => 'Codi de seguretat', + 'mfa_verify_backup_code_desc' => 'Introduïu un dels vostres codis de seguretat restants a continuació:', + 'mfa_verify_backup_code_enter_here' => 'Introduïu aquí el codi de seguretat', + 'mfa_verify_totp_desc' => 'Introduïu el codi generat amb la vostra aplicació mòbil a continuació:', + 'mfa_setup_login_notification' => 'S’ha configurat el mètode d’autenticació de múltiple factor. Torneu a iniciar la sessió fent servir el mètode que heu configurat.', ]; diff --git a/lang/ca/common.php b/lang/ca/common.php index 4b8f2eddc..1798c82b7 100644 --- a/lang/ca/common.php +++ b/lang/ca/common.php @@ -6,8 +6,8 @@ return [ // Buttons 'cancel' => 'Cancel·la', - 'close' => 'Close', - 'confirm' => 'D\'acord', + 'close' => 'Tanca', + 'confirm' => 'D’acord', 'back' => 'Enrere', 'save' => 'Desa', 'continue' => 'Continua', @@ -26,7 +26,7 @@ return [ 'actions' => 'Accions', 'view' => 'Visualitza', 'view_all' => 'Visualitza-ho tot', - 'new' => 'New', + 'new' => 'Nou', 'create' => 'Crea', 'update' => 'Actualitza', 'edit' => 'Edita', @@ -41,35 +41,35 @@ return [ 'reset' => 'Reinicialitza', 'remove' => 'Elimina', 'add' => 'Afegeix', - 'configure' => 'Configure', - 'manage' => 'Manage', + 'configure' => 'Configura', + 'manage' => 'Gestiona', 'fullscreen' => 'Pantalla completa', - 'favourite' => 'Favourite', - 'unfavourite' => 'Unfavourite', - 'next' => 'Next', - 'previous' => 'Previous', - 'filter_active' => 'Active Filter:', - 'filter_clear' => 'Clear Filter', - 'download' => 'Download', - 'open_in_tab' => 'Open in Tab', - 'open' => 'Open', + 'favourite' => 'Afegeix a preferits', + 'unfavourite' => 'Suprimeix de preferits', + 'next' => 'Següent', + 'previous' => 'Anterior', + 'filter_active' => 'Filtre actiu:', + 'filter_clear' => 'Esborra el filtre', + 'download' => 'Baixa', + 'open_in_tab' => 'Obre en una pestanya', + 'open' => 'Obre', // Sort Options - 'sort_options' => 'Opcions d\'ordenació', - 'sort_direction_toggle' => 'Commuta la direcció de l\'ordenació', + 'sort_options' => 'Opcions d’ordenació', + 'sort_direction_toggle' => 'Commuta la direcció de l’ordenació', 'sort_ascending' => 'Ordre ascendent', 'sort_descending' => 'Ordre descendent', 'sort_name' => 'Nom', 'sort_default' => 'Per defecte', 'sort_created_at' => 'Data de creació', - 'sort_updated_at' => 'Data d\'actualització', + 'sort_updated_at' => 'Data d’actualització', // Misc - 'deleted_user' => 'Usuari eliminat', + 'deleted_user' => 'Usuari suprimit', 'no_activity' => 'No hi ha activitat', 'no_items' => 'No hi ha cap element', 'back_to_top' => 'Torna a dalt', - 'skip_to_main_content' => 'Skip to main content', + 'skip_to_main_content' => 'Vés al contingut', 'toggle_details' => 'Commuta els detalls', 'toggle_thumbnails' => 'Commuta les miniatures', 'details' => 'Detalls', @@ -77,30 +77,30 @@ return [ 'list_view' => 'Visualització en llista', 'default' => 'Per defecte', 'breadcrumb' => 'Ruta de navegació', - 'status' => 'Status', - 'status_active' => 'Active', - 'status_inactive' => 'Inactive', - 'never' => 'Never', - 'none' => 'None', + 'status' => 'Estat', + 'status_active' => 'Actiu', + 'status_inactive' => 'Inactiu', + 'never' => 'Mai', + 'none' => 'Cap', // Header - 'homepage' => 'Homepage', - 'header_menu_expand' => 'Expand Header Menu', + 'homepage' => 'Pàgina principal', + 'header_menu_expand' => 'Expandeix el menú de la capçalera', 'profile_menu' => 'Menú del perfil', 'view_profile' => 'Mostra el perfil', 'edit_profile' => 'Edita el perfil', 'dark_mode' => 'Mode fosc', 'light_mode' => 'Mode clar', - 'global_search' => 'Global Search', + 'global_search' => 'Cerca global', // Layout tabs 'tab_info' => 'Informació', - 'tab_info_label' => 'Tab: Show Secondary Information', + 'tab_info_label' => 'Pestanya: Mostra la informació secundària', 'tab_content' => 'Contingut', - 'tab_content_label' => 'Tab: Show Primary Content', + 'tab_content_label' => 'Pestanya: Mostra el contingut principal', // Email Content - 'email_action_help' => 'Si teniu problemes per fer clic al botó ":actionText", copieu i enganxeu l\'URL següent al vostre navegador web:', + 'email_action_help' => 'Si teniu problemes per a fer clic al botó «:actionText», copieu i enganxeu l’URL següent al vostre navegador web:', 'email_rights' => 'Tots els drets reservats', // Footer Link Options diff --git a/lang/ca/components.php b/lang/ca/components.php index e002886d7..9c3485021 100644 --- a/lang/ca/components.php +++ b/lang/ca/components.php @@ -6,36 +6,36 @@ return [ // Image Manager 'image_select' => 'Selecciona una imatge', - 'image_list' => 'Image List', - 'image_details' => 'Image Details', - 'image_upload' => 'Upload Image', - 'image_intro' => 'Here you can select and manage images that have been previously uploaded to the system.', - 'image_intro_upload' => 'Upload a new image by dragging an image file into this window, or by using the "Upload Image" button above.', + 'image_list' => 'Llista d’imatges', + 'image_details' => 'Detalls de la imatge', + 'image_upload' => 'Puja una imatge', + 'image_intro' => 'Aquí podeu seleccionar i gestionar imatges que s’hagin pujat anteriorment al sistema.', + 'image_intro_upload' => 'Pugeu una imatge nova arrossegant un fitxer d’imatge a aquesta finestra o fent servir el botó de «Puja una imatge».', 'image_all' => 'Totes', 'image_all_title' => 'Mostra totes les imatges', 'image_book_title' => 'Mostra les imatges pujades a aquest llibre', 'image_page_title' => 'Mostra les imatges pujades a aquesta pàgina', - 'image_search_hint' => 'Cerca per nom d\'imatge', + 'image_search_hint' => 'Cerca per nom d’imatge', 'image_uploaded' => 'Pujada :uploadedDate', - 'image_uploaded_by' => 'Uploaded by :userName', - 'image_uploaded_to' => 'Uploaded to :pageLink', - 'image_updated' => 'Updated :updateDate', - 'image_load_more' => 'Carrega\'n més', + 'image_uploaded_by' => 'Pujada per :userName', + 'image_uploaded_to' => 'Pujada a :pageLink', + 'image_updated' => 'Actualitzada :updateDate', + 'image_load_more' => 'Carrega’n més', 'image_image_name' => 'Nom de la imatge', - 'image_delete_used' => 'Aquesta imatge s\'utilitza a les pàgines següents.', + 'image_delete_used' => 'Aquesta imatge s’utilitza a les pàgines següents.', 'image_delete_confirm_text' => 'Segur que voleu suprimir aquesta imatge?', 'image_select_image' => 'Selecciona una imatge', 'image_dropzone' => 'Arrossegueu imatges o feu clic aquí per a pujar-les', - 'image_dropzone_drop' => 'Drop images here to upload', + 'image_dropzone_drop' => 'Arrossegueu imatges aquí per a pujar-les', 'images_deleted' => 'Imatges suprimides', 'image_preview' => 'Previsualització de la imatge', 'image_upload_success' => 'Imatge pujada correctament', 'image_update_success' => 'Detalls de la imatge actualitzats correctament', 'image_delete_success' => 'Imatge suprimida correctament', - 'image_replace' => 'Replace Image', - 'image_replace_success' => 'Image file successfully updated', - 'image_rebuild_thumbs' => 'Regenerate Size Variations', - 'image_rebuild_thumbs_success' => 'Image size variations successfully rebuilt!', + 'image_replace' => 'Substitueix la imatge', + 'image_replace_success' => 'Fitxer de la imatge actualitzat correctament', + 'image_rebuild_thumbs' => 'Regenera les variacions de mida', + 'image_rebuild_thumbs_success' => 'Variacions de mida de la imatge regenerades correctament!', // Code Editor 'code_editor' => 'Edita el codi', diff --git a/lang/ca/editor.php b/lang/ca/editor.php index 670c1c5e1..eaef6402f 100644 --- a/lang/ca/editor.php +++ b/lang/ca/editor.php @@ -8,167 +8,167 @@ return [ // General editor terms 'general' => 'General', - 'advanced' => 'Advanced', - 'none' => 'None', - 'cancel' => 'Cancel', - 'save' => 'Save', - 'close' => 'Close', - 'undo' => 'Undo', - 'redo' => 'Redo', - 'left' => 'Left', - 'center' => 'Center', - 'right' => 'Right', - 'top' => 'Top', - 'middle' => 'Middle', - 'bottom' => 'Bottom', - 'width' => 'Width', - 'height' => 'Height', - 'More' => 'More', - 'select' => 'Select...', + 'advanced' => 'Avançat', + 'none' => 'Cap', + 'cancel' => 'Cancel·la', + 'save' => 'Desa', + 'close' => 'Tanca', + 'undo' => 'Desfés', + 'redo' => 'Refés', + 'left' => 'Esquerra', + 'center' => 'Centre', + 'right' => 'Dreta', + 'top' => 'Superior', + 'middle' => 'Centre', + 'bottom' => 'Inferior', + 'width' => 'Amplada', + 'height' => 'Alçada', + 'More' => 'Més', + 'select' => 'Selecciona...', // Toolbar 'formats' => 'Formats', - 'header_large' => 'Large Header', - 'header_medium' => 'Medium Header', - 'header_small' => 'Small Header', - 'header_tiny' => 'Tiny Header', - 'paragraph' => 'Paragraph', - 'blockquote' => 'Blockquote', - 'inline_code' => 'Inline code', - 'callouts' => 'Callouts', - 'callout_information' => 'Information', - 'callout_success' => 'Success', - 'callout_warning' => 'Warning', - 'callout_danger' => 'Danger', - 'bold' => 'Bold', - 'italic' => 'Italic', - 'underline' => 'Underline', - 'strikethrough' => 'Strikethrough', - 'superscript' => 'Superscript', - 'subscript' => 'Subscript', - 'text_color' => 'Text color', - 'custom_color' => 'Custom color', - 'remove_color' => 'Remove color', - 'background_color' => 'Background color', - 'align_left' => 'Align left', - 'align_center' => 'Align center', - 'align_right' => 'Align right', - 'align_justify' => 'Justify', - 'list_bullet' => 'Bullet list', - 'list_numbered' => 'Numbered list', - 'list_task' => 'Task list', - 'indent_increase' => 'Increase indent', - 'indent_decrease' => 'Decrease indent', - 'table' => 'Table', - 'insert_image' => 'Insert image', - 'insert_image_title' => 'Insert/Edit Image', - 'insert_link' => 'Insert/edit link', - 'insert_link_title' => 'Insert/Edit Link', - 'insert_horizontal_line' => 'Insert horizontal line', - 'insert_code_block' => 'Insert code block', - 'edit_code_block' => 'Edit code block', - 'insert_drawing' => 'Insert/edit drawing', - 'drawing_manager' => 'Drawing manager', - 'insert_media' => 'Insert/edit media', - 'insert_media_title' => 'Insert/Edit Media', - 'clear_formatting' => 'Clear formatting', - 'source_code' => 'Source code', - 'source_code_title' => 'Source Code', - 'fullscreen' => 'Fullscreen', - 'image_options' => 'Image options', + 'header_large' => 'Capçalera grossa', + 'header_medium' => 'Capçalera mitjana', + 'header_small' => 'Capçalera petita', + 'header_tiny' => 'Capçalera minúscula', + 'paragraph' => 'Paràgraf', + 'blockquote' => 'Citació', + 'inline_code' => 'Codi en línia', + 'callouts' => 'Llegendes', + 'callout_information' => 'Informació', + 'callout_success' => 'Èxit', + 'callout_warning' => 'Advertència', + 'callout_danger' => 'Perill', + 'bold' => 'Negreta', + 'italic' => 'Cursiva', + 'underline' => 'Subratllat', + 'strikethrough' => 'Ratllat', + 'superscript' => 'Superíndex', + 'subscript' => 'Subíndex', + 'text_color' => 'Color del text', + 'custom_color' => 'Color personalitzat', + 'remove_color' => 'Suprimeix el color', + 'background_color' => 'Color de fons', + 'align_left' => 'Alinea a l’esquerra', + 'align_center' => 'Alinea al centre', + 'align_right' => 'Alinea a la dreta', + 'align_justify' => 'Justifica', + 'list_bullet' => 'Llista amb pics', + 'list_numbered' => 'Llista numerada', + 'list_task' => 'Llista de tasques', + 'indent_increase' => 'Augmenta el sagnat', + 'indent_decrease' => 'Redueix el sagnat', + 'table' => 'Taula', + 'insert_image' => 'Insereix una imatge', + 'insert_image_title' => 'Insereix/edita una imatge', + 'insert_link' => 'Insereix/edita un enllaç', + 'insert_link_title' => 'Insereix/edita un enllaç', + 'insert_horizontal_line' => 'Insereix una línia horitzontal', + 'insert_code_block' => 'Insereix un bloc de codi', + 'edit_code_block' => 'Edita un bloc de codi', + 'insert_drawing' => 'Insereix/edita un dibuix', + 'drawing_manager' => 'Gestor de dibuixos', + 'insert_media' => 'Insereix/edita un mitjà', + 'insert_media_title' => 'Insereix/edita un mitjà', + 'clear_formatting' => 'Neteja el format', + 'source_code' => 'Codi font', + 'source_code_title' => 'Codi font', + 'fullscreen' => 'Pantalla completa', + 'image_options' => 'Opcions d’imatge', // Tables - 'table_properties' => 'Table properties', - 'table_properties_title' => 'Table Properties', - 'delete_table' => 'Delete table', - 'insert_row_before' => 'Insert row before', - 'insert_row_after' => 'Insert row after', - 'delete_row' => 'Delete row', - 'insert_column_before' => 'Insert column before', - 'insert_column_after' => 'Insert column after', - 'delete_column' => 'Delete column', - 'table_cell' => 'Cell', - 'table_row' => 'Row', - 'table_column' => 'Column', - 'cell_properties' => 'Cell properties', - 'cell_properties_title' => 'Cell Properties', - 'cell_type' => 'Cell type', - 'cell_type_cell' => 'Cell', - 'cell_scope' => 'Scope', - 'cell_type_header' => 'Header cell', - 'merge_cells' => 'Merge cells', - 'split_cell' => 'Split cell', - 'table_row_group' => 'Row Group', - 'table_column_group' => 'Column Group', - 'horizontal_align' => 'Horizontal align', - 'vertical_align' => 'Vertical align', - 'border_width' => 'Border width', - 'border_style' => 'Border style', - 'border_color' => 'Border color', - 'row_properties' => 'Row properties', - 'row_properties_title' => 'Row Properties', - 'cut_row' => 'Cut row', - 'copy_row' => 'Copy row', - 'paste_row_before' => 'Paste row before', - 'paste_row_after' => 'Paste row after', - 'row_type' => 'Row type', - 'row_type_header' => 'Header', - 'row_type_body' => 'Body', - 'row_type_footer' => 'Footer', - 'alignment' => 'Alignment', - 'cut_column' => 'Cut column', - 'copy_column' => 'Copy column', - 'paste_column_before' => 'Paste column before', - 'paste_column_after' => 'Paste column after', - 'cell_padding' => 'Cell padding', - 'cell_spacing' => 'Cell spacing', - 'caption' => 'Caption', - 'show_caption' => 'Show caption', - 'constrain' => 'Constrain proportions', - 'cell_border_solid' => 'Solid', - 'cell_border_dotted' => 'Dotted', - 'cell_border_dashed' => 'Dashed', - 'cell_border_double' => 'Double', - 'cell_border_groove' => 'Groove', - 'cell_border_ridge' => 'Ridge', - 'cell_border_inset' => 'Inset', - 'cell_border_outset' => 'Outset', - 'cell_border_none' => 'None', - 'cell_border_hidden' => 'Hidden', + 'table_properties' => 'Propietats de la taula', + 'table_properties_title' => 'Propietats de la taula', + 'delete_table' => 'Suprimeix la taula', + 'insert_row_before' => 'Insereix una fila abans', + 'insert_row_after' => 'Insereix una fila després', + 'delete_row' => 'Suprimeix la fila', + 'insert_column_before' => 'Insereix una columna abans', + 'insert_column_after' => 'Insereix una columna després', + 'delete_column' => 'Suprimeix la columna', + 'table_cell' => 'Cel·la', + 'table_row' => 'Fila', + 'table_column' => 'Columna', + 'cell_properties' => 'Propietats de la cel·la', + 'cell_properties_title' => 'Propietats de la cel·la', + 'cell_type' => 'Tipus de cel·la', + 'cell_type_cell' => 'Cel·la', + 'cell_scope' => 'Àmbit', + 'cell_type_header' => 'Cel·la de capçalera', + 'merge_cells' => 'Fusiona les cel·les', + 'split_cell' => 'Divideix les cel·les', + 'table_row_group' => 'Grup de files', + 'table_column_group' => 'Grup de columnes', + 'horizontal_align' => 'Alineació horitzontal', + 'vertical_align' => 'Alineació vertical', + 'border_width' => 'Amplada de la vora', + 'border_style' => 'Estil de la vora', + 'border_color' => 'Color de la vora', + 'row_properties' => 'Propietats de la fila', + 'row_properties_title' => 'Propietats de la fila', + 'cut_row' => 'Retalla la fila', + 'copy_row' => 'Copia la fila', + 'paste_row_before' => 'Enganxa la fila abans', + 'paste_row_after' => 'Enganxa la fila després', + 'row_type' => 'Tipus de fila', + 'row_type_header' => 'Capçalera', + 'row_type_body' => 'Cos', + 'row_type_footer' => 'Peu de pàgina', + 'alignment' => 'Alineació', + 'cut_column' => 'Retalla la columna', + 'copy_column' => 'Copia la columna', + 'paste_column_before' => 'Enganxa la columna abans', + 'paste_column_after' => 'Enganxa la columna després', + 'cell_padding' => 'Separació de la cel·la', + 'cell_spacing' => 'Espaiat de la cel·la', + 'caption' => 'Títol', + 'show_caption' => 'Mostra el títol', + 'constrain' => 'Mantén les proporcions', + 'cell_border_solid' => 'Sòlida', + 'cell_border_dotted' => 'Puntejada', + 'cell_border_dashed' => 'Discontínua', + 'cell_border_double' => 'Doble', + 'cell_border_groove' => 'Bisellada', + 'cell_border_ridge' => 'Emmarcada', + 'cell_border_inset' => 'Enfonsada', + 'cell_border_outset' => 'Ressaltada', + 'cell_border_none' => 'Cap', + 'cell_border_hidden' => 'Oculta', // Images, links, details/summary & embed - 'source' => 'Source', - 'alt_desc' => 'Alternative description', - 'embed' => 'Embed', - 'paste_embed' => 'Paste your embed code below:', + 'source' => 'Origen', + 'alt_desc' => 'Descripció alternativa', + 'embed' => 'Incrustació', + 'paste_embed' => 'Enganxeu el codi d’incrustació a continuació:', 'url' => 'URL', - 'text_to_display' => 'Text to display', - 'title' => 'Title', - 'open_link' => 'Open link', - 'open_link_in' => 'Open link in...', - 'open_link_current' => 'Current window', - 'open_link_new' => 'New window', - 'remove_link' => 'Remove link', - 'insert_collapsible' => 'Insert collapsible block', - 'collapsible_unwrap' => 'Unwrap', - 'edit_label' => 'Edit label', - 'toggle_open_closed' => 'Toggle open/closed', - 'collapsible_edit' => 'Edit collapsible block', - 'toggle_label' => 'Toggle label', + 'text_to_display' => 'Text a mostrar', + 'title' => 'Títol', + 'open_link' => 'Obre l’enllaç', + 'open_link_in' => 'Obre l’enllaç a...', + 'open_link_current' => 'Finestra actual', + 'open_link_new' => 'Finestra nova', + 'remove_link' => 'Suprimeix l’enllaç', + 'insert_collapsible' => 'Insereix un bloc contraïble', + 'collapsible_unwrap' => 'Deixa de contraure', + 'edit_label' => 'Edita l’etiqueta', + 'toggle_open_closed' => 'Obre/tanca', + 'collapsible_edit' => 'Edita el bloc contraïble', + 'toggle_label' => 'Commuta l’etiqueta', // About view - 'about' => 'About the editor', - 'about_title' => 'About the WYSIWYG Editor', - 'editor_license' => 'Editor License & Copyright', - 'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under the MIT license.', - 'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.', - 'save_continue' => 'Save Page & Continue', - 'callouts_cycle' => '(Keep pressing to toggle through types)', - 'link_selector' => 'Link to content', - 'shortcuts' => 'Shortcuts', - 'shortcut' => 'Shortcut', - 'shortcuts_intro' => 'The following shortcuts are available in the editor:', + 'about' => 'Quant a l’editor', + 'about_title' => 'Quant a l’editor WYSIWYG', + 'editor_license' => 'Llicència i copyright de l’editor', + 'editor_tiny_license' => 'Aquest editor s’ha construït amb :tinyLink, que es proporciona sota la llicència MIT.', + 'editor_tiny_license_link' => 'Podeu trobar els detalls de la llicència i el copyright del TinyMCE aquí.', + 'save_continue' => 'Desa la pàgina i continua', + 'callouts_cycle' => '(Continueu prement per a commutar els diferents tipus)', + 'link_selector' => 'Enllaç al contingut', + 'shortcuts' => 'Dreceres', + 'shortcut' => 'Drecera', + 'shortcuts_intro' => 'Hi ha les següents dreceres disponibles a l’editor:', 'windows_linux' => '(Windows/Linux)', 'mac' => '(Mac)', - 'description' => 'Description', + 'description' => 'Descripció', ]; diff --git a/lang/ca/entities.php b/lang/ca/entities.php index a53a7a3bd..9bffe493c 100644 --- a/lang/ca/entities.php +++ b/lang/ca/entities.php @@ -15,7 +15,7 @@ return [ 'recently_update' => 'Actualitzat fa poc', 'recently_viewed' => 'Vist fa poc', 'recent_activity' => 'Activitat recent', - 'create_now' => 'Crea\'n ara', + 'create_now' => 'Crea’n ara', 'revisions' => 'Revisions', 'meta_revision' => 'Revisió núm. :revisionCount', 'meta_created' => 'Creat :timeLength', @@ -23,38 +23,38 @@ return [ 'meta_updated' => 'Actualitzat :timeLength', 'meta_updated_name' => 'Actualitzat :timeLength per :user', 'meta_owned_name' => 'Propietat de :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Selecciona una entitat', - 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', + 'entity_select_lack_permission' => 'No teniu els permisos necessaris per a seleccionar aquest element', 'images' => 'Imatges', 'my_recent_drafts' => 'Els vostres esborranys recents', 'my_recently_viewed' => 'Les vostres visualitzacions recents', - 'my_most_viewed_favourites' => 'My Most Viewed Favourites', - 'my_favourites' => 'My Favourites', + 'my_most_viewed_favourites' => 'Els vostres preferits més vistos', + 'my_favourites' => 'Els vostres preferits', 'no_pages_viewed' => 'No heu vist cap pàgina', - 'no_pages_recently_created' => 'No s\'ha creat cap pàgina fa poc', - 'no_pages_recently_updated' => 'No s\'ha actualitzat cap pàgina fa poc', + 'no_pages_recently_created' => 'No s’ha creat cap pàgina fa poc', + 'no_pages_recently_updated' => 'No s’ha actualitzat cap pàgina fa poc', 'export' => 'Exporta', 'export_html' => 'Fitxer web independent', 'export_pdf' => 'Fitxer PDF', 'export_text' => 'Fitxer de text sense format', - 'export_md' => 'Markdown File', + 'export_md' => 'Fitxer Markdown', // Permissions and restrictions 'permissions' => 'Permisos', - 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', - 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', - 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', + 'permissions_desc' => 'Definiu aquí permisos que tindran preferència sobre les permisos per defecte proporcionats pels rols d’usuari.', + 'permissions_book_cascade' => 'Els permisos definits en llibres s’hereten automàticament en pàgines i capítols inferiors, llevat que tinguin permisos propis definits.', + 'permissions_chapter_cascade' => 'Els permisos definits en capítols s’hereten automàticament en pàgines inferiors, llevat que tinguin permisos propis definits.', 'permissions_save' => 'Desa els permisos', 'permissions_owner' => 'Propietari', - 'permissions_role_everyone_else' => 'Everyone Else', - 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', - 'permissions_role_override' => 'Override permissions for role', - 'permissions_inherit_defaults' => 'Inherit defaults', + 'permissions_role_everyone_else' => 'Tota la resta', + 'permissions_role_everyone_else_desc' => 'Definiu permisos per a tots els rols no definits específicament.', + 'permissions_role_override' => 'Força els permisos per al rol', + 'permissions_inherit_defaults' => 'Hereta els permisos per defecte', // Search 'search_results' => 'Resultats de la cerca', - 'search_total_results_found' => 'S\'ha trobat :count resultat en total|S\'han trobat :count resultats en total', + 'search_total_results_found' => 'S’ha trobat :count resultat|S’han trobat :count resultats en total', 'search_clear' => 'Esborra la cerca', 'search_no_pages' => 'La cerca no coincideix amb cap pàgina', 'search_for_term' => 'Cerca :term', @@ -63,14 +63,14 @@ return [ 'search_terms' => 'Termes de la cerca', 'search_content_type' => 'Tipus de contingut', 'search_exact_matches' => 'Coincidències exactes', - 'search_tags' => 'Cerca d\'etiquetes', + 'search_tags' => 'Cerca d’etiquetes', 'search_options' => 'Opcions', 'search_viewed_by_me' => 'Visualitzat per mi', 'search_not_viewed_by_me' => 'No visualitzat per mi', 'search_permissions_set' => 'Amb permisos definits', 'search_created_by_me' => 'Creat per mi', 'search_updated_by_me' => 'Actualitzat per mi', - 'search_owned_by_me' => 'Owned by me', + 'search_owned_by_me' => 'En sóc propietari', 'search_date_options' => 'Opcions de dates', 'search_updated_before' => 'Actualitzat abans de', 'search_updated_after' => 'Actualitzat després de', @@ -93,24 +93,24 @@ return [ 'shelves_save' => 'Desa el prestatge', 'shelves_books' => 'Llibres en aquest prestatge', 'shelves_add_books' => 'Afegeix llibres a aquest prestatge', - 'shelves_drag_books' => 'Drag books below to add them to this shelf', + 'shelves_drag_books' => 'Arrossegueu llibres a sota per a afegir-los a aquest prestatge', 'shelves_empty_contents' => 'Aquest prestatge no té cap llibre assignat', 'shelves_edit_and_assign' => 'Editeu el prestatge per a assignar-hi llibres', - 'shelves_edit_named' => 'Edit Shelf :name', - 'shelves_edit' => 'Edit Shelf', - 'shelves_delete' => 'Delete Shelf', - 'shelves_delete_named' => 'Delete Shelf :name', - 'shelves_delete_explain' => "This will delete the shelf with the name ':name'. Contained books will not be deleted.", - 'shelves_delete_confirmation' => 'Are you sure you want to delete this shelf?', - 'shelves_permissions' => 'Shelf Permissions', - 'shelves_permissions_updated' => 'Shelf Permissions Updated', - 'shelves_permissions_active' => 'Shelf Permissions Active', - 'shelves_permissions_cascade_warning' => 'Permissions on shelves do not automatically cascade to contained books. This is because a book can exist on multiple shelves. Permissions can however be copied down to child books using the option found below.', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', + 'shelves_edit_named' => 'Edita el prestatge :name', + 'shelves_edit' => 'Edita el prestatge', + 'shelves_delete' => 'Suprimeix el prestatge', + 'shelves_delete_named' => 'Suprimeix el prestatge :name', + 'shelves_delete_explain' => "Se suprimirà el prestatge amb el nom «:name». Els llibres que contingui no se suprimiran.", + 'shelves_delete_confirmation' => 'Segur que voleu suprimir aquest prestatge?', + 'shelves_permissions' => 'Permisos del prestatge', + 'shelves_permissions_updated' => 'S’han actualitzat els permisos del prestatge', + 'shelves_permissions_active' => 'Permisos del prestatge actius', + 'shelves_permissions_cascade_warning' => 'Els permisos dels prestatges no s’hereten automàticament en els llibres que contenen. És així perquè un llibre pot estar en múltiples prestatges. Es poden copiar, però, els permisos als llibres continguts fent servir l’opció a continuació.', + 'shelves_permissions_create' => 'Els permisos de crear prestatges només es fan servir per a copiar permisos a llibres continguts fent servir l’acció següent. No controlen la capacitat de crear llibres.', 'shelves_copy_permissions_to_books' => 'Copia els permisos als llibres', 'shelves_copy_permissions' => 'Copia els permisos', - 'shelves_copy_permissions_explain' => 'This will apply the current permission settings of this shelf to all books contained within. Before activating, ensure any changes to the permissions of this shelf have been saved.', - 'shelves_copy_permission_success' => 'Shelf permissions copied to :count books', + 'shelves_copy_permissions_explain' => 'S’aplicarà la configuració de permisos actual a tots els llibres continguts. Abans d’activar-la, assegureu-vos que hàgiu desat qualsevol canvi en els permisos d’aquest prestatge.', + 'shelves_copy_permission_success' => 'S’han copiat els permisos del prestatge a :count llibres', // Books 'book' => 'Llibre', @@ -126,44 +126,47 @@ return [ 'books_create' => 'Crea un llibre nou', 'books_delete' => 'Suprimeix el llibre', 'books_delete_named' => 'Suprimeix el llibre :bookName', - 'books_delete_explain' => 'Se suprimirà el llibre amb el nom \':bookName\'. Se\'n suprimiran les pàgines i els capítols.', + 'books_delete_explain' => 'Se suprimirà el llibre amb el nom «:bookName». Se’n suprimiran totes les pàgines i tots els capítols.', 'books_delete_confirmation' => 'Segur que voleu suprimir aquest llibre?', 'books_edit' => 'Edita el llibre', 'books_edit_named' => 'Edita el llibre :bookName', 'books_form_book_name' => 'Nom del llibre', 'books_save' => 'Desa el llibre', + 'books_default_template' => 'Plantilla de pàgina per defecte', + 'books_default_template_explain' => 'Assigneu una plantilla de pàgina que s’utilitzarà com a contingut per defecte de totes les pàgines noves d’aquest llibre. Tingueu en compte que només es farà servir si qui crea la pàgina té accés de visualització a la plantilla de pàgina elegida.', + 'books_default_template_select' => 'Seleccioneu una plantilla de pàgina', 'books_permissions' => 'Permisos del llibre', - 'books_permissions_updated' => 'S\'han actualitzat els permisos del llibre', + 'books_permissions_updated' => 'S’han actualitzat els permisos del llibre', 'books_empty_contents' => 'No hi ha cap pàgina ni cap capítol creat en aquest llibre.', 'books_empty_create_page' => 'Crea una pàgina nova', 'books_empty_sort_current_book' => 'Ordena el llibre actual', 'books_empty_add_chapter' => 'Afegeix un capítol', - 'books_permissions_active' => 'S\'han activat els permisos del llibre', + 'books_permissions_active' => 'S’han activat els permisos del llibre', 'books_search_this' => 'Cerca en aquest llibre', 'books_navigation' => 'Navegació pel llibre', 'books_sort' => 'Ordena el contingut del llibre', - 'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books.', + 'books_sort_desc' => 'Moveu capítols i pàgines dins d’un llibre per a reorganitzar-ne el contingut. Podeu afegir altres llibres, la qual cosa permet moure fàcilment capítols i pàgines entre llibres.', 'books_sort_named' => 'Ordena el llibre :bookName', 'books_sort_name' => 'Ordena per nom', 'books_sort_created' => 'Ordena per data de creació', - 'books_sort_updated' => 'Ordena per data d\'actualització', + 'books_sort_updated' => 'Ordena per data d’actualització', 'books_sort_chapters_first' => 'Els capítols al principi', 'books_sort_chapters_last' => 'Els capítols al final', 'books_sort_show_other' => 'Mostra altres llibres', - 'books_sort_save' => 'Desa l\'ordre nou', - 'books_sort_show_other_desc' => 'Add other books here to include them in the sort operation, and allow easy cross-book reorganisation.', - 'books_sort_move_up' => 'Move Up', - 'books_sort_move_down' => 'Move Down', - 'books_sort_move_prev_book' => 'Move to Previous Book', - 'books_sort_move_next_book' => 'Move to Next Book', - 'books_sort_move_prev_chapter' => 'Move Into Previous Chapter', - 'books_sort_move_next_chapter' => 'Move Into Next Chapter', - 'books_sort_move_book_start' => 'Move to Start of Book', - 'books_sort_move_book_end' => 'Move to End of Book', - 'books_sort_move_before_chapter' => 'Move to Before Chapter', - 'books_sort_move_after_chapter' => 'Move to After Chapter', - 'books_copy' => 'Copy Book', - 'books_copy_success' => 'Book successfully copied', + 'books_sort_save' => 'Desa l’ordre nou', + 'books_sort_show_other_desc' => 'Afegiu aquí altres llibres per a incloure’ls a l’operació d’ordenació i permetre la reorganització entre llibres de manera fàcil.', + 'books_sort_move_up' => 'Mou amunt', + 'books_sort_move_down' => 'Mou avall', + 'books_sort_move_prev_book' => 'Mou al llibre anterior', + 'books_sort_move_next_book' => 'Mou al llibre següent', + 'books_sort_move_prev_chapter' => 'Mou al capítol anterior', + 'books_sort_move_next_chapter' => 'Mou al capítol següent', + 'books_sort_move_book_start' => 'Mou a l’inici del llibre', + 'books_sort_move_book_end' => 'Mou al final del llibre', + 'books_sort_move_before_chapter' => 'Mou al capítol anterior', + 'books_sort_move_after_chapter' => 'Mou al capítol següent', + 'books_copy' => 'Copia el llibre', + 'books_copy_success' => 'Llibre copiat correctament', // Chapters 'chapter' => 'Capítol', @@ -174,21 +177,21 @@ return [ 'chapters_create' => 'Crea un capítol nou', 'chapters_delete' => 'Suprimeix el capítol', 'chapters_delete_named' => 'Suprimeix el capítol :chapterName', - 'chapters_delete_explain' => 'Se suprimirà el capítol amb el nom \':chapterName\'. Totes les pàgines que contingui també se suprimiran.', + 'chapters_delete_explain' => 'Se suprimirà el capítol amb el nom «:chapterName». Totes les pàgines que contingui també se suprimiran.', 'chapters_delete_confirm' => 'Segur que voleu suprimir aquest capítol?', 'chapters_edit' => 'Edita el capítol', 'chapters_edit_named' => 'Edita el capítol :chapterName', 'chapters_save' => 'Desa el capítol', 'chapters_move' => 'Mou el capítol', 'chapters_move_named' => 'Mou el capítol :chapterName', - 'chapters_copy' => 'Copy Chapter', - 'chapters_copy_success' => 'Chapter successfully copied', + 'chapters_copy' => 'Copia el capítol', + 'chapters_copy_success' => 'Capítol copiat correctament', 'chapters_permissions' => 'Permisos del capítol', 'chapters_empty' => 'De moment, aquest capítol no conté cap pàgina.', - 'chapters_permissions_active' => 'S\'han activat els permisos del capítol', - 'chapters_permissions_success' => 'S\'han actualitzat els permisos del capítol', + 'chapters_permissions_active' => 'S’han activat els permisos del capítol', + 'chapters_permissions_success' => 'S’han actualitzat els permisos del capítol', 'chapters_search_this' => 'Cerca en aquest capítol', - 'chapter_sort_book' => 'Sort Book', + 'chapter_sort_book' => 'Ordena el llibre', // Pages 'page' => 'Pàgina', @@ -200,35 +203,36 @@ return [ 'pages_navigation' => 'Navegació per la pàgina', 'pages_delete' => 'Suprimeix la pàgina', 'pages_delete_named' => 'Suprimeix la pàgina :pageName', - 'pages_delete_draft_named' => 'Suprimeix l\'esborrany de pàgina :pageName', - 'pages_delete_draft' => 'Suprimeix l\'esborrany de pàgina', - 'pages_delete_success' => 'S\'ha suprimit la pàgina', - 'pages_delete_draft_success' => 'S\'ha suprimit l\'esborrany de pàgina', + 'pages_delete_draft_named' => 'Suprimeix l’esborrany de pàgina :pageName', + 'pages_delete_draft' => 'Suprimeix l’esborrany de pàgina', + 'pages_delete_success' => 'S’ha suprimit la pàgina', + 'pages_delete_draft_success' => 'S’ha suprimit l’esborrany de pàgina', + 'pages_delete_warning_template' => 'Aquesta pàgina es fa servir com a plantilla de pàgina per defecte en algun llibre. Quan l’hàgiu suprimida, aquests llibres ja no tindran assignada cap plantilla de pàgina per defecte.', 'pages_delete_confirm' => 'Segur que voleu suprimir aquesta pàgina?', 'pages_delete_draft_confirm' => 'Segur que voleu suprimir aquest esborrany de pàgina?', 'pages_editing_named' => 'Esteu editant :pageName', - 'pages_edit_draft_options' => 'Opcions d\'esborrany', - 'pages_edit_save_draft' => 'Desa l\'esborrany', - 'pages_edit_draft' => 'Edita l\'esborrany de pàgina', - 'pages_editing_draft' => 'Esteu editant l\'esborrany', + 'pages_edit_draft_options' => 'Opcions d’esborrany', + 'pages_edit_save_draft' => 'Desa l’esborrany', + 'pages_edit_draft' => 'Edita l’esborrany de pàgina', + 'pages_editing_draft' => 'Esteu editant l’esborrany', 'pages_editing_page' => 'Esteu editant la pàgina', 'pages_edit_draft_save_at' => 'Esborrany desat ', - 'pages_edit_delete_draft' => 'Suprimeix l\'esborrany', - 'pages_edit_delete_draft_confirm' => 'Are you sure you want to delete your draft page changes? All of your changes, since the last full save, will be lost and the editor will be updated with the latest page non-draft save state.', - 'pages_edit_discard_draft' => 'Descarta l\'esborrany', - 'pages_edit_switch_to_markdown' => 'Switch to Markdown Editor', - 'pages_edit_switch_to_markdown_clean' => '(Clean Content)', - 'pages_edit_switch_to_markdown_stable' => '(Stable Content)', - 'pages_edit_switch_to_wysiwyg' => 'Switch to WYSIWYG Editor', + 'pages_edit_delete_draft' => 'Suprimeix l’esborrany', + 'pages_edit_delete_draft_confirm' => 'Segur que voleu suprimir els canvis a l’esborrany de pàgina? Es perdran tots els vostres canvis d’ençà de la darrera vegada que heu desat i s’actualitzarà l’editor amb l’estat de la darrera pàgina desada sense ser un esborrany.', + 'pages_edit_discard_draft' => 'Descarta l’esborrany', + 'pages_edit_switch_to_markdown' => 'Canvia a l’editor Markdown', + 'pages_edit_switch_to_markdown_clean' => '(Contingut net)', + 'pages_edit_switch_to_markdown_stable' => '(Contingut estable)', + 'pages_edit_switch_to_wysiwyg' => 'Canvia a l’editor WYSIWYG', 'pages_edit_set_changelog' => 'Defineix el registre de canvis', 'pages_edit_enter_changelog_desc' => 'Introduïu una breu descripció dels canvis que heu fet', 'pages_edit_enter_changelog' => 'Introduïu un registre de canvis', - 'pages_editor_switch_title' => 'Switch Editor', - 'pages_editor_switch_are_you_sure' => 'Are you sure you want to change the editor for this page?', - 'pages_editor_switch_consider_following' => 'Consider the following when changing editors:', - 'pages_editor_switch_consideration_a' => 'Once saved, the new editor option will be used by any future editors, including those that may not be able to change editor type themselves.', - 'pages_editor_switch_consideration_b' => 'This can potentially lead to a loss of detail and syntax in certain circumstances.', - 'pages_editor_switch_consideration_c' => 'Tag or changelog changes, made since last save, won\'t persist across this change.', + 'pages_editor_switch_title' => 'Canvia d’editor', + 'pages_editor_switch_are_you_sure' => 'Segur que voleu canviar l’editor d’aquesta pàgina?', + 'pages_editor_switch_consider_following' => 'Considereu el següent en canviar d’editor:', + 'pages_editor_switch_consideration_a' => 'Quan hàgiu desat, l’opció del nou editor serà la que utilitzaran tots els futurs editors, incloent-hi els que no poden canviar de tipus d’editor amb el seu usuari.', + 'pages_editor_switch_consideration_b' => 'En algunes circumstàncies, això pot comportar una pèrdua de detalls i de sintaxi.', + 'pages_editor_switch_consideration_c' => 'Els canvis al registre de canvis o a les etiquetes fets d’ençà de la darrera vegada que s’ha desat no es mantindran en aquest canvi.', 'pages_save' => 'Desa la pàgina', 'pages_title' => 'Títol de la pàgina', 'pages_name' => 'Nom de la pàgina', @@ -237,110 +241,110 @@ return [ 'pages_md_insert_image' => 'Insereix una imatge', 'pages_md_insert_link' => 'Insereix un enllaç a una entitat', 'pages_md_insert_drawing' => 'Insereix un diagrama', - 'pages_md_show_preview' => 'Show preview', - 'pages_md_sync_scroll' => 'Sync preview scroll', - 'pages_drawing_unsaved' => 'Unsaved Drawing Found', - 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', + 'pages_md_show_preview' => 'Mostra la previsualització', + 'pages_md_sync_scroll' => 'Sincronitza el desplaçament de la previsualització', + 'pages_drawing_unsaved' => 'S’ha trobat un diagrama no desat', + 'pages_drawing_unsaved_confirm' => 'S’han trobat dades d’un diagrama d’un intent de desat fallit anterior. Voleu restaurar-les i continuar editant aquest diagrama no desat?', 'pages_not_in_chapter' => 'La pàgina no pertany a cap capítol', 'pages_move' => 'Mou la pàgina', 'pages_copy' => 'Copia la pàgina', 'pages_copy_desination' => 'Destinació de la còpia', 'pages_copy_success' => 'Pàgina copiada correctament', 'pages_permissions' => 'Permisos de la pàgina', - 'pages_permissions_success' => 'S\'han actualitzat els permisos de la pàgina', + 'pages_permissions_success' => 'S’han actualitzat els permisos de la pàgina', 'pages_revision' => 'Revisió', 'pages_revisions' => 'Revisions de la pàgina', - 'pages_revisions_desc' => 'Listed below are all the past revisions of this page. You can look back upon, compare, and restore old page versions if permissions allow. The full history of the page may not be fully reflected here since, depending on system configuration, old revisions could be auto-deleted.', + 'pages_revisions_desc' => 'A continuació hi ha les revisions anteriors de la pàgina. Podeu mirar-les, comparar-les i restaurar-ne versions antigues si us ho permeten els permisos. És possible que l’historial complet de la pàgina no s’hi vegi reflectit perquè, depenent de la configuració del sistema, es poden haver esborrat automàticament revisions antigues.', 'pages_revisions_named' => 'Revisions de la pàgina :pageName', 'pages_revision_named' => 'Revisió de la pàgina :pageName', 'pages_revision_restored_from' => 'Restaurada de núm. :id; :summary', 'pages_revisions_created_by' => 'Creada per', 'pages_revisions_date' => 'Data de la revisió', 'pages_revisions_number' => 'Núm. ', - 'pages_revisions_sort_number' => 'Revision Number', + 'pages_revisions_sort_number' => 'Número de revisió', 'pages_revisions_numbered' => 'Revisió núm. :id', 'pages_revisions_numbered_changes' => 'Canvis de la revisió núm. :id', - 'pages_revisions_editor' => 'Editor Type', + 'pages_revisions_editor' => 'Tipus d’editor', 'pages_revisions_changelog' => 'Registre de canvis', 'pages_revisions_changes' => 'Canvis', 'pages_revisions_current' => 'Versió actual', 'pages_revisions_preview' => 'Previsualitza', 'pages_revisions_restore' => 'Restaura', 'pages_revisions_none' => 'Aquesta pàgina no té cap revisió', - 'pages_copy_link' => 'Copia l\'enllaç', - 'pages_edit_content_link' => 'Jump to section in editor', - 'pages_pointer_enter_mode' => 'Enter section select mode', - 'pages_pointer_label' => 'Page Section Options', - 'pages_pointer_permalink' => 'Page Section Permalink', - 'pages_pointer_include_tag' => 'Page Section Include Tag', - 'pages_pointer_toggle_link' => 'Permalink mode, Press to show include tag', - 'pages_pointer_toggle_include' => 'Include tag mode, Press to show permalink', - 'pages_permissions_active' => 'S\'han activat els permisos de la pàgina', + 'pages_copy_link' => 'Copia l’enllaç', + 'pages_edit_content_link' => 'Vés a la secció a l’editor', + 'pages_pointer_enter_mode' => 'Activa el mode de selecció de secció', + 'pages_pointer_label' => 'Opcions de la secció de la pàgina', + 'pages_pointer_permalink' => 'Enllaç permanent de la secció de la pàgina', + 'pages_pointer_include_tag' => 'Etiqueta d’inclusió de la secció de la pàgina', + 'pages_pointer_toggle_link' => 'Mode d’enllaç permanent, premeu per a mostrar l’etiqueta d‘inclusió', + 'pages_pointer_toggle_include' => 'Mode d’etiqueta d’inclusió, premeu per a mostrar l’enllaç permanent', + 'pages_permissions_active' => 'S’han activat els permisos de la pàgina', 'pages_initial_revision' => 'Publicació inicial', - 'pages_references_update_revision' => 'System auto-update of internal links', + 'pages_references_update_revision' => 'Actualització automàtica dels enllaços interns del sistema', 'pages_initial_name' => 'Pàgina nova', 'pages_editing_draft_notification' => 'Esteu editant un esborrany que es va desar per darrer cop :timeDiff.', - 'pages_draft_edited_notification' => 'Aquesta pàgina s\'ha actualitzat d\'ençà d\'aleshores. Us recomanem que descarteu aquest esborrany.', - 'pages_draft_page_changed_since_creation' => 'This page has been updated since this draft was created. It is recommended that you discard this draft or take care not to overwrite any page changes.', + 'pages_draft_edited_notification' => 'Aquesta pàgina s’ha actualitzat d’ençà d’aleshores. Us recomanem que descarteu aquest esborrany.', + 'pages_draft_page_changed_since_creation' => 'Aquesta pàgina s’ha actualitzat d‘ençà que es va crear l’esborrany. Us recomanem que descarteu aquest esborrany i que aneu amb compte de no sobreescriure els canvis de la pàgina.', 'pages_draft_edit_active' => [ 'start_a' => ':count usuaris han començat a editar aquesta pàgina', 'start_b' => ':userName ha començat a editar aquesta pàgina', - 'time_a' => 'd\'ençà que la pàgina es va actualitzar per darrer cop', + 'time_a' => 'd’ençà que la pàgina es va actualitzar per darrer cop', 'time_b' => 'en els darrers :minCount minuts', 'message' => ':start :time. Aneu amb compte de no trepitjar-vos les actualitzacions entre vosaltres!', ], - 'pages_draft_discarded' => 'Draft discarded! The editor has been updated with the current page content', - 'pages_draft_deleted' => 'Draft deleted! The editor has been updated with the current page content', + 'pages_draft_discarded' => 'S’ha descartat l’esborrany! S’ha actualitzat l’editor amb el contingut actual de la pàgina', + 'pages_draft_deleted' => 'S’ha suprimit l’esborrany! S’ha actualitzat l’editor amb el contingut actual de la pàgina', 'pages_specific' => 'Una pàgina específica', 'pages_is_template' => 'Plantilla de pàgina', // Editor Sidebar - 'toggle_sidebar' => 'Toggle Sidebar', + 'toggle_sidebar' => 'Commuta la barra lateral', 'page_tags' => 'Etiquetes de la pàgina', 'chapter_tags' => 'Etiquetes del capítol', 'book_tags' => 'Etiquetes del llibre', 'shelf_tags' => 'Etiquetes del prestatge', 'tag' => 'Etiqueta', 'tags' => 'Etiquetes', - 'tags_index_desc' => 'Tags can be applied to content within the system to apply a flexible form of categorization. Tags can have both a key and value, with the value being optional. Once applied, content can then be queried using the tag name and value.', - 'tag_name' => 'Nom de l\'etiqueta', - 'tag_value' => 'Valor de l\'etiqueta (opcional)', + 'tags_index_desc' => 'Es poden aplicar etiquetes al contingut dins del sistema per a aplicar un mode de categorització flexible. Les etiquetes poden tenir una clau i un valor, però el valor és opcional. Quan s’han aplican, el contingut es pot cercar fent servir el nom o el valor de l’etiqueta.', + 'tag_name' => 'Nom de l’etiqueta', + 'tag_value' => 'Valor de l’etiqueta (opcional)', 'tags_explain' => "Afegiu etiquetes per a categoritzar millor el contingut. \n Podeu assignar un valor a cada etiqueta per a una organització més detallada.", 'tags_add' => 'Afegeix una altra etiqueta', 'tags_remove' => 'Elimina aquesta etiqueta', - 'tags_usages' => 'Total tag usages', - 'tags_assigned_pages' => 'Assigned to Pages', - 'tags_assigned_chapters' => 'Assigned to Chapters', - 'tags_assigned_books' => 'Assigned to Books', - 'tags_assigned_shelves' => 'Assigned to Shelves', - 'tags_x_unique_values' => ':count unique values', - 'tags_all_values' => 'All values', - 'tags_view_tags' => 'View Tags', - 'tags_view_existing_tags' => 'View existing tags', - 'tags_list_empty_hint' => 'Tags can be assigned via the page editor sidebar or while editing the details of a book, chapter or shelf.', + 'tags_usages' => 'Usos totals de l’etiqueta', + 'tags_assigned_pages' => 'Assignada a pàgines', + 'tags_assigned_chapters' => 'Assignada a capítols', + 'tags_assigned_books' => 'Assignada a llibres', + 'tags_assigned_shelves' => 'Assignada a prestatges', + 'tags_x_unique_values' => ':count valors únics', + 'tags_all_values' => 'Tots els valors', + 'tags_view_tags' => 'Mostra les etiquetes', + 'tags_view_existing_tags' => 'Mostra les etiquetes existents', + 'tags_list_empty_hint' => 'Es poden assignar etiquetes mitjançant la barra lateral de l’editor de la pàgina o quan s’editen els detalls d’un llibre, capítol o prestatge.', 'attachments' => 'Adjuncions', 'attachments_explain' => 'Pugeu fitxers o adjunteu enllaços per a mostrar-los a la pàgina. Són visibles a la barra lateral de la pàgina.', 'attachments_explain_instant_save' => 'Els canvis fets aquí es desen instantàniament.', 'attachments_upload' => 'Puja un fitxer', 'attachments_link' => 'Adjunta un enllaç', - 'attachments_upload_drop' => 'Alternatively you can drag and drop a file here to upload it as an attachment.', - 'attachments_set_link' => 'Defineix l\'enllaç', - 'attachments_delete' => 'Seguir que voleu suprimir aquesta adjunció?', - 'attachments_dropzone' => 'Drop files here to upload', - 'attachments_no_files' => 'No s\'ha pujat cap fitxer', + 'attachments_upload_drop' => 'De manera alternativa, podeu arrossegar i deixar anar un fitxer aquí per a pujar-lo com a adjunció.', + 'attachments_set_link' => 'Defineix l’enllaç', + 'attachments_delete' => 'Segur que voleu suprimir aquesta adjunció?', + 'attachments_dropzone' => 'Deixeu anar fitxers aquí per a pujar-los', + 'attachments_no_files' => 'No s’ha pujat cap fitxer', 'attachments_explain_link' => 'Podeu adjuntar un enllaç si preferiu no pujar un fitxer. Pot ser un enllaç a una altra pàgina o un enllaç a un fitxer al núvol.', - 'attachments_link_name' => 'Nom de l\'enllaç', - 'attachment_link' => 'Enllaç de l\'adjunció', + 'attachments_link_name' => 'Nom de l’enllaç', + 'attachment_link' => 'Enllaç de l’adjunció', 'attachments_link_url' => 'Enllaç al fitxer', 'attachments_link_url_hint' => 'URL del lloc o fitxer', 'attach' => 'Adjunta', - 'attachments_insert_link' => 'Afegeix un enllaç de l\'adjunció a la pàgina', + 'attachments_insert_link' => 'Afegeix un enllaç de l’adjunció a la pàgina', 'attachments_edit_file' => 'Edita el fitxer', 'attachments_edit_file_name' => 'Nom del fitxer', - 'attachments_edit_drop_upload' => 'Arrossegueu fitxers o feu clic aquí per a pujar-los i sobreescriure\'ls', - 'attachments_order_updated' => 'S\'ha actualitzat l\'ordre de les adjuncions', - 'attachments_updated_success' => 'S\'han actualitzat els detalls de les adjuncions', - 'attachments_deleted' => 'S\'ha suprimit l\'adjunció', + 'attachments_edit_drop_upload' => 'Arrossegueu fitxers o feu clic aquí per a pujar-los i sobreescriure’ls', + 'attachments_order_updated' => 'S’ha actualitzat l’ordre de les adjuncions', + 'attachments_updated_success' => 'S’han actualitzat els detalls de les adjuncions', + 'attachments_deleted' => 'S’ha suprimit l’adjunció', 'attachments_file_uploaded' => 'Fitxer pujat correctament', 'attachments_file_updated' => 'Fitxer actualitzat correctament', 'attachments_link_attached' => 'Enllaç adjuntat a la pàgina correctament', @@ -369,13 +373,13 @@ return [ 'comment_new' => 'Comentari nou', 'comment_created' => 'ha comentat :createDiff', 'comment_updated' => 'Actualitzat :updateDiff per :username', - 'comment_updated_indicator' => 'Updated', + 'comment_updated_indicator' => 'Actualitzat', 'comment_deleted_success' => 'Comentari suprimit', 'comment_created_success' => 'Comentari afegit', 'comment_updated_success' => 'Comentari actualitzat', 'comment_delete_confirm' => 'Segur que voleu suprimir aquest comentari?', 'comment_in_reply_to' => 'En resposta a :commentId', - 'comment_editor_explain' => 'Here are the comments that have been left on this page. Comments can be added & managed when viewing the saved page.', + 'comment_editor_explain' => 'Aquí hi ha els comentaris que s’han deixat en aquesta pàgina. Els comentaris es poden afegir i gestionar en veure una pàgina desada.', // Revision 'revision_delete_confirm' => 'Segur que voleu suprimir aquesta revisió?', @@ -383,51 +387,51 @@ return [ 'revision_cannot_delete_latest' => 'No es pot suprimir la darrera revisió.', // Copy view - 'copy_consider' => 'Please consider the below when copying content.', - 'copy_consider_permissions' => 'Custom permission settings will not be copied.', - 'copy_consider_owner' => 'You will become the owner of all copied content.', - 'copy_consider_images' => 'Page image files will not be duplicated & the original images will retain their relation to the page they were originally uploaded to.', - 'copy_consider_attachments' => 'Page attachments will not be copied.', - 'copy_consider_access' => 'A change of location, owner or permissions may result in this content being accessible to those previously without access.', + 'copy_consider' => 'Tingueu en compte el següent quan copieu contingut.', + 'copy_consider_permissions' => 'No es copiarà la configuració personalitzada de permisos.', + 'copy_consider_owner' => 'Esdevindreu el nou propietari de qualsevol contingut copiat.', + 'copy_consider_images' => 'Els fitxers d’imatge de les pàgines no es duplicaran i les imatges originals mantindran la relació amb la pàgina a la qual es van pujar originalment.', + 'copy_consider_attachments' => 'No es copiaran les adjuncions de la pàgina.', + 'copy_consider_access' => 'Un canvi d’ubicació, propietari o permisos pot provocar que aquest contingut esdevingui accessible a públic que abans no n’hi tenia.', // Conversions - 'convert_to_shelf' => 'Convert to Shelf', - 'convert_to_shelf_contents_desc' => 'You can convert this book to a new shelf with the same contents. Chapters contained within this book will be converted to new books. If this book contains any pages, that are not in a chapter, this book will be renamed and contain such pages, and this book will become part of the new shelf.', - 'convert_to_shelf_permissions_desc' => 'Any permissions set on this book will be copied to the new shelf and to all new child books that don\'t have their own permissions enforced. Note that permissions on shelves do not auto-cascade to content within, as they do for books.', - 'convert_book' => 'Convert Book', - 'convert_book_confirm' => 'Are you sure you want to convert this book?', - 'convert_undo_warning' => 'This cannot be as easily undone.', - 'convert_to_book' => 'Convert to Book', - 'convert_to_book_desc' => 'You can convert this chapter to a new book with the same contents. Any permissions set on this chapter will be copied to the new book but any inherited permissions, from the parent book, will not be copied which could lead to a change of access control.', - 'convert_chapter' => 'Convert Chapter', - 'convert_chapter_confirm' => 'Are you sure you want to convert this chapter?', + 'convert_to_shelf' => 'Converteix en prestatge', + 'convert_to_shelf_contents_desc' => 'Podeu convertir aquest llibre en un prestatge nou amb el mateix contingut. Els capítols continguts en aquest llibre es convertiran en nous lliures. Si aquest llibre conté pàgines que no pertanyin a cap capítol, aquest llibre canviarà de nom i les contindrà, i aquest llibre esdevindrà part del nou prestatge.', + 'convert_to_shelf_permissions_desc' => 'Qualsevol permís definit en aquest llibre es copiarà al nou prestatge i a tots els nous llibres fills que no tinguin permisos explícits. Tingueu en compte que els permisos dels prestatges no s’hereten automàticament al contingut que continguin de la mateixa manera que passa amb els llibres.', + 'convert_book' => 'Converteix el llibre', + 'convert_book_confirm' => 'Segur que voleu convertir aquest llibre?', + 'convert_undo_warning' => 'No es podrà desfer de manera fàcil.', + 'convert_to_book' => 'Converteix en llibre', + 'convert_to_book_desc' => 'Podeu convertir aquest capítol en un llibre nou amb el mateix contingut. Qualsevol permís definit en aquest capítol es copiarà al nou llibre, però qualsevol permís heretat del llibre pare no es copiarà, la qual cosa podria implicar canvis en el control d’accés.', + 'convert_chapter' => 'Converteix el capítol', + 'convert_chapter_confirm' => 'Segur que voleu convertir aquest capítol?', // References - 'references' => 'References', - 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references' => 'Referències', + 'references_none' => 'Ni hi ha cap referència detectada a aquest element.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options - 'watch' => 'Watch', - 'watch_title_default' => 'Default Preferences', - 'watch_desc_default' => 'Revert watching to just your default notification preferences.', - 'watch_title_ignore' => 'Ignore', - 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', - 'watch_title_new' => 'New Pages', - 'watch_desc_new' => 'Notify when any new page is created within this item.', - 'watch_title_updates' => 'All Page Updates', - 'watch_desc_updates' => 'Notify upon all new pages and page changes.', - 'watch_desc_updates_page' => 'Notify upon all page changes.', - 'watch_title_comments' => 'All Page Updates & Comments', - 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', - 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', - 'watch_change_default' => 'Change default notification preferences', - 'watch_detail_ignore' => 'Ignoring notifications', - 'watch_detail_new' => 'Watching for new pages', - 'watch_detail_updates' => 'Watching new pages and updates', - 'watch_detail_comments' => 'Watching new pages, updates & comments', - 'watch_detail_parent_book' => 'Watching via parent book', - 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', - 'watch_detail_parent_chapter' => 'Watching via parent chapter', - 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', + 'watch' => 'Segueix', + 'watch_title_default' => 'Preferències per defecte', + 'watch_desc_default' => 'Restableix el seguiment a només les preferències de notificació per defecte.', + 'watch_title_ignore' => 'Ignora', + 'watch_desc_ignore' => 'Ignora totes les notificacions, incloent-hi les preferències a nivell d’usuari.', + 'watch_title_new' => 'Pàgines noves', + 'watch_desc_new' => 'Notifica’m quan es creï qualsevol pàgina nova dins d’aquest element.', + 'watch_title_updates' => 'Totes les actualitzacions de pàgines', + 'watch_desc_updates' => 'Notifica’m totes les pàgines noves i qualsevol canvi de pàgina.', + 'watch_desc_updates_page' => 'Notifica’m tots els canvis de pàgina.', + 'watch_title_comments' => 'Totes les actualitzacions de pàgines i comentaris', + 'watch_desc_comments' => 'Notifica’m totes les pàgines noves, qualsevol canvi de pàgina i comentaris nous.', + 'watch_desc_comments_page' => 'Notifica’m qualsevol canvi de pàgina i comentaris nous.', + 'watch_change_default' => 'Canvieu les preferències de notificació per deecte', + 'watch_detail_ignore' => 'S’ignoren les notificacions', + 'watch_detail_new' => 'Se segueixen pàgines noves', + 'watch_detail_updates' => 'Se segueixen pàgines noves i actualitzacions', + 'watch_detail_comments' => 'Se segueixen pàgines noves, actualitzacions i comentaris', + 'watch_detail_parent_book' => 'Se segueix mitjançant el llibre pare', + 'watch_detail_parent_book_ignore' => 'S’ignora mitjançant el llibre pare', + 'watch_detail_parent_chapter' => 'Se segueix mitjançant el capítol pare', + 'watch_detail_parent_chapter_ignore' => 'S’ignora mitjançant el capítol pare', ]; diff --git a/lang/ca/errors.php b/lang/ca/errors.php index ba53bd849..22ecb2b0e 100644 --- a/lang/ca/errors.php +++ b/lang/ca/errors.php @@ -6,115 +6,114 @@ return [ // Permissions 'permission' => 'No teniu permís per a accedir a la pàgina sol·licitada.', - 'permissionJson' => 'No teniu permís per a executar l\'acció sol·licitada.', + 'permissionJson' => 'No teniu permís per a executar l’acció sol·licitada.', // Auth - 'error_user_exists_different_creds' => 'Ja hi ha un usuari amb l\'adreça electrònica :email però amb credencials diferents.', - 'email_already_confirmed' => 'L\'adreça electrònica ja està confirmada. Proveu d\'iniciar la sessió.', - 'email_confirmation_invalid' => 'Aquest testimoni de confirmació no és vàlid o ja ha estat utilitzat. Proveu de tornar-vos a registrar.', - 'email_confirmation_expired' => 'El testimoni de confirmació ha caducat. S\'ha enviat un nou correu electrònic de confirmació.', - 'email_confirmation_awaiting' => 'Cal confirmar l\'adreça electrònica del compte que utilitzeu', - 'ldap_fail_anonymous' => 'L\'accés a l\'LDAP ha fallat fent servir un lligam anònim', - 'ldap_fail_authed' => 'L\'accés a l\'LDAP ha fallat fent servir els detalls de DN i contrasenya proporcionats', - 'ldap_extension_not_installed' => 'L\'extensió de l\'LDAP de PHP no està instal·lada', - 'ldap_cannot_connect' => 'No s\'ha pogut connectar amb el servidor de l\'LDAP, la connexió inicial ha fallat', + 'error_user_exists_different_creds' => 'Ja hi ha un usuari amb l’adreça electrònica :email però amb credencials diferents.', + 'email_already_confirmed' => 'L’adreça electrònica ja està confirmada. Proveu d’iniciar la sessió.', + 'email_confirmation_invalid' => 'Aquest testimoni de confirmació no és vàlid o ja s’ha utilitzat. Proveu de tornar-vos a registrar.', + 'email_confirmation_expired' => 'El testimoni de confirmació ha caducat. S’ha enviat un nou correu electrònic de confirmació.', + 'email_confirmation_awaiting' => 'Cal confirmar l’adreça electrònica del compte que utilitzeu', + 'ldap_fail_anonymous' => 'L’accés a l’LDAP ha fallat fent servir un lligam anònim', + 'ldap_fail_authed' => 'L’accés a l’LDAP ha fallat fent servir els detalls de DN i contrasenya proporcionats', + 'ldap_extension_not_installed' => 'L’extensió de l’LDAP del PHP no està instal·lada', + 'ldap_cannot_connect' => 'No s’ha pogut connectar amb el servidor de l’LDAP, la connexió inicial ha fallat', 'saml_already_logged_in' => 'Ja heu iniciat la sessió', - 'saml_user_not_registered' => 'L\'usuari :name no està registrat i els registres automàtics estan desactivats', - 'saml_no_email_address' => 'No s\'ha pogut trobar cap adreça electrònica, per a aquest usuari, en les dades proporcionades pel sistema d\'autenticació extern', - 'saml_invalid_response_id' => 'La petició del sistema d\'autenticació extern no és reconeguda per un procés iniciat per aquesta aplicació. Aquest problema podria ser causat per navegar endarrere després d\'iniciar la sessió.', - 'saml_fail_authed' => 'L\'inici de sessió fent servir :system ha fallat, el sistema no ha proporcionat una autorització satisfactòria', - 'oidc_already_logged_in' => 'Already logged in', - 'oidc_user_not_registered' => 'The user :name is not registered and automatic registration is disabled', - 'oidc_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system', - 'oidc_fail_authed' => 'Login using :system failed, system did not provide successful authorization', + 'saml_no_email_address' => 'No s’ha pogut trobar cap adreça electrònica per a aquest usuari en les dades proporcionades pel sistema d’autenticació extern', + 'saml_invalid_response_id' => 'La petició del sistema d’autenticació extern no és reconeguda per un procés iniciat per aquesta aplicació. Aquest problema podria ser causat per navegar endarrere després d’iniciar la sessió.', + 'saml_fail_authed' => 'L’inici de sessió fent servir :system ha fallat, el sistema no ha proporcionat una autorització satisfactòria', + 'oidc_already_logged_in' => 'Ja teniu una sessió iniciada', + 'oidc_user_not_registered' => 'L’usuari :name no està registrat i els registres automàtics estan desactivats', + 'oidc_no_email_address' => 'No s’ha pogut trobar cap adreça electrònica per a aquest usuari en les dades proporcionades pel sistema d’autenticació extern', + 'oidc_fail_authed' => 'L’inici de sessió fent servir :system ha fallat, el sistema no ha proporcionat una autorització satisfactòria', 'social_no_action_defined' => 'No hi ha cap acció definida', - 'social_login_bad_response' => "S'ha rebut un error mentre s'iniciava la sessió amb :socialAccount: \n:error", - 'social_account_in_use' => 'Aquest compte de :socialAccount ja està en ús, proveu d\'iniciar la sessió mitjançant l\'opció de :socialAccount.', - 'social_account_email_in_use' => 'L\'adreça electrònica :email ja està en ús. Si ja teniu un compte, podeu connectar-hi el vostre compte de :socialAccount a la configuració del vostre perfil.', + 'social_login_bad_response' => "S’ha rebut un error mentre s’iniciava la sessió amb :socialAccount: \n:error", + 'social_account_in_use' => 'Aquest compte de :socialAccount ja està en ús, proveu d’iniciar la sessió mitjançant l’opció de :socialAccount.', + 'social_account_email_in_use' => 'L’adreça electrònica :email ja està en ús. Si ja teniu un compte, podeu connectar-hi el vostre compte de :socialAccount a la configuració del vostre perfil.', 'social_account_existing' => 'Aquest compte de :socialAccount ja està associat al vostre perfil.', 'social_account_already_used_existing' => 'Aquest compte de :socialAccount ja el fa servir un altre usuari.', 'social_account_not_used' => 'Aquest compte de :socialAccount no està associat a cap usuari. Associeu-lo a la configuració del vostre perfil. ', - 'social_account_register_instructions' => 'Si encara no teniu cap compte, podeu registrar-vos fent servir l\'opció de :socialAccount.', - 'social_driver_not_found' => 'No s\'ha trobat el controlador social', + 'social_account_register_instructions' => 'Si encara no teniu cap compte, podeu registrar-vos fent servir l’opció de :socialAccount.', + 'social_driver_not_found' => 'No s’ha trobat el controlador social', 'social_driver_not_configured' => 'La configuració social de :socialAccount no és correcta.', - 'invite_token_expired' => 'Aquest enllaç d\'invitació ha caducat. Podeu provar de restablir la contrasenya del vostre compte.', + 'invite_token_expired' => 'Aquest enllaç d’invitació ha caducat. Podeu provar de restablir la contrasenya del vostre compte.', // System - 'path_not_writable' => 'No s\'ha pogut pujar al camí del fitxer :filePath. Assegureu-vos que el servidor hi té permisos d\'escriptura.', - 'cannot_get_image_from_url' => 'No s\'ha pogut obtenir la imatge de :url', - 'cannot_create_thumbs' => 'El servidor no pot crear miniatures. Reviseu que tingueu instal·lada l\'extensió GD del PHP.', - 'server_upload_limit' => 'El servidor no permet pujades d\'aquesta mida. Proveu-ho amb una mida de fitxer més petita.', - 'server_post_limit' => 'The server cannot receive the provided amount of data. Try again with less data or a smaller file.', - 'uploaded' => 'El servidor no permet pujades d\'aquesta mida. Proveu-ho amb una mida de fitxer més petita.', + 'path_not_writable' => 'No s’ha pogut pujar al camí del fitxer :filePath. Assegureu-vos que el servidor hi té permisos d’escriptura.', + 'cannot_get_image_from_url' => 'No s’ha pogut obtenir la imatge de :url', + 'cannot_create_thumbs' => 'El servidor no pot crear miniatures. Reviseu que tingueu instal·lada l’extensió GD del PHP.', + 'server_upload_limit' => 'El servidor no permet pujades d’aquesta mida. Proveu-ho amb una mida de fitxer més petita.', + 'server_post_limit' => 'El servidor no pot rebre la quantitat de dades que heu proporcionat. Torneu-ho a provar amb menys dades o un fitxer més petit.', + 'uploaded' => 'El servidor no permet pujades d’aquesta mida. Proveu-ho amb una mida de fitxer més petita.', // Drawing & Images - 'image_upload_error' => 'S\'ha produït un error en pujar la imatge', - 'image_upload_type_error' => 'El tipus d\'imatge que heu pujat no és vàlid', - 'image_upload_replace_type' => 'Image file replacements must be of the same type', - 'image_upload_memory_limit' => 'Failed to handle image upload and/or create thumbnails due to system resource limits.', - 'image_thumbnail_memory_limit' => 'Failed to create image size variations due to system resource limits.', - 'image_gallery_thumbnail_memory_limit' => 'Failed to create gallery thumbnails due to system resource limits.', - 'drawing_data_not_found' => 'Drawing data could not be loaded. The drawing file might no longer exist or you may not have permission to access it.', + 'image_upload_error' => 'S’ha produït un error en pujar la imatge', + 'image_upload_type_error' => 'El tipus d’imatge que heu pujat no és vàlid', + 'image_upload_replace_type' => 'Les substitucions de fitxers d’imatge han de ser el mateix tipus', + 'image_upload_memory_limit' => 'No s’ha pogut gestionar la pujada de la imatge i/o crear-ne miniatures a causa dels límits dels recursos del sistema.', + 'image_thumbnail_memory_limit' => 'No s’ha pogut crear les variacions de mida de la imatge a causa dels límits dels recursos del sistema.', + 'image_gallery_thumbnail_memory_limit' => 'No s’han pogut crear les miniatures de la galeria a causa dels límits dels recursos del sistema.', + 'drawing_data_not_found' => 'No s’han pogut carregar les dades de dibuix. És possible que el fitxer de dibuix ja no existeixi o que no tingueu permisos per a accedir-hi.', // Attachments - 'attachment_not_found' => 'No s\'ha trobat l\'adjunció', - 'attachment_upload_error' => 'An error occurred uploading the attachment file', + 'attachment_not_found' => 'No s’ha trobat l’adjunció', + 'attachment_upload_error' => 'S’ha produït un error en pujar el fitxer de l’adjunció', // Pages - 'page_draft_autosave_fail' => 'No s\'ha pogut desar l\'esborrany. Assegureu-vos que tingueu connexió a Internet abans de desar la pàgina', - 'page_draft_delete_fail' => 'Failed to delete page draft and fetch current page saved content', - 'page_custom_home_deletion' => 'No es pot suprimir una pàgina mentre estigui definida com a pàgina d\'inici', + 'page_draft_autosave_fail' => 'No s’ha pogut desar l’esborrany. Assegureu-vos que teniu connexió a Internet abans de desar la pàgina', + 'page_draft_delete_fail' => 'No s’ha pogut suprimir l’esborrany de la pàgina i obtenir el contingut desat actual de la pàgina', + 'page_custom_home_deletion' => 'No es pot suprimir una pàgina mentre estigui definida com a pàgina d’inici', // Entities - 'entity_not_found' => 'No s\'ha trobat l\'entitat', - 'bookshelf_not_found' => 'Shelf not found', - 'book_not_found' => 'No s\'ha trobat el llibre', - 'page_not_found' => 'No s\'ha trobat la pàgina', - 'chapter_not_found' => 'No s\'ha trobat el capítol', - 'selected_book_not_found' => 'No s\'ha trobat el llibre seleccionat', - 'selected_book_chapter_not_found' => 'No s\'ha trobat el llibre o el capítol seleccionat', + 'entity_not_found' => 'No s’ha trobat l’entitat', + 'bookshelf_not_found' => 'No s’ha trobat el prestatge', + 'book_not_found' => 'No s’ha trobat el llibre', + 'page_not_found' => 'No s’ha trobat la pàgina', + 'chapter_not_found' => 'No s’ha trobat el capítol', + 'selected_book_not_found' => 'No s’ha trobat el llibre seleccionat', + 'selected_book_chapter_not_found' => 'No s’ha trobat el llibre o el capítol seleccionat', 'guests_cannot_save_drafts' => 'Els convidats no poden desar esborranys', // Users - 'users_cannot_delete_only_admin' => 'No podeu suprimir l\'únic administrador', - 'users_cannot_delete_guest' => 'No podeu suprimir l\'usuari convidat', + 'users_cannot_delete_only_admin' => 'No podeu suprimir l’únic administrador', + 'users_cannot_delete_guest' => 'No podeu suprimir l’usuari convidat', // Roles 'role_cannot_be_edited' => 'Aquest rol no es pot editar', 'role_system_cannot_be_deleted' => 'Aquest rol és un rol del sistema i no es pot suprimir', 'role_registration_default_cannot_delete' => 'No es pot suprimir aquest rol mentre estigui definit com a rol per defecte dels registres', - 'role_cannot_remove_only_admin' => 'Aquest usuari és l\'únic usuari assignat al rol d\'administrador. Assigneu el rol d\'administrador a un altre usuari abans de provar de suprimir aquest.', + 'role_cannot_remove_only_admin' => 'Aquest usuari és l’únic usuari assignat al rol d’administrador. Assigneu el rol d’administrador a un altre usuari abans de provar de suprimir aquest.', // Comments - 'comment_list' => 'S\'ha produït un error en obtenir els comentaris.', + 'comment_list' => 'S’ha produït un error en obtenir els comentaris.', 'cannot_add_comment_to_draft' => 'No podeu afegir comentaris a un esborrany.', - 'comment_add' => 'S\'ha produït un error en afegir o actualitzar el comentari.', - 'comment_delete' => 'S\'ha produït un error en suprimir el comentari.', + 'comment_add' => 'S’ha produït un error en afegir o actualitzar el comentari.', + 'comment_delete' => 'S’ha produït un error en suprimir el comentari.', 'empty_comment' => 'No podeu afegir un comentari buit.', // Error pages - '404_page_not_found' => 'No s\'ha trobat la pàgina', + '404_page_not_found' => 'No s’ha trobat la pàgina', 'sorry_page_not_found' => 'No hem pogut trobar la pàgina que cerqueu.', 'sorry_page_not_found_permission_warning' => 'Si esperàveu que existís, és possible que no tingueu permisos per a veure-la.', - 'image_not_found' => 'Image Not Found', - 'image_not_found_subtitle' => 'Sorry, The image file you were looking for could not be found.', - 'image_not_found_details' => 'If you expected this image to exist it might have been deleted.', - 'return_home' => 'Torna a l\'inici', - 'error_occurred' => 'S\'ha produït un error', + 'image_not_found' => 'No s’ha trobat la imatge', + 'image_not_found_subtitle' => 'No ha estat possible trobar el fitxer de la imatge que cerqueu.', + 'image_not_found_details' => 'Si esperàveu que existís, és possible que s’hagi suprimit.', + 'return_home' => 'Torna a l’inici', + 'error_occurred' => 'S’ha produït un error', 'app_down' => ':appName està fora de servei en aquests moments', 'back_soon' => 'Tornarà a estar disponible aviat.', // API errors - 'api_no_authorization_found' => 'No s\'ha trobat cap testimoni d\'autorització a la petició', - 'api_bad_authorization_format' => 'S\'ha trobat un testimoni d\'autorització a la petició però el format sembla erroni', - 'api_user_token_not_found' => 'No s\'ha trobat cap testimoni d\'API per al testimoni d\'autorització proporcionat', - 'api_incorrect_token_secret' => 'El secret proporcionat per al testimoni d\'API proporcionat és incorrecte', - 'api_user_no_api_permission' => 'El propietari del testimoni d\'API utilitzat no té permís per a fer crides a l\'API', - 'api_user_token_expired' => 'El testimoni d\'autorització utilitzat ha caducat', + 'api_no_authorization_found' => 'No s’ha trobat cap testimoni d’autorització a la petició', + 'api_bad_authorization_format' => 'S’ha trobat un testimoni d’autorització a la petició, però el format sembla erroni', + 'api_user_token_not_found' => 'No s’ha trobat cap testimoni de l’API per al testimoni d’autorització proporcionat', + 'api_incorrect_token_secret' => 'El secret proporcionat per al testimoni de l’API proporcionat és incorrecte', + 'api_user_no_api_permission' => 'El propietari del testimoni de l’API utilitzat no té permís per a fer crides a l’API', + 'api_user_token_expired' => 'El testimoni d’autorització utilitzat ha caducat', // Settings & Maintenance - 'maintenance_test_email_failure' => 'S\'ha produït un error en enviar un correu electrònic de prova:', + 'maintenance_test_email_failure' => 'S’ha produït un error en enviar un correu electrònic de prova:', // HTTP errors - 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', + 'http_ssr_url_no_match' => 'L’URL no coincideix amb els amfitrions SSR permesos segons la configuració', ]; diff --git a/lang/ca/notifications.php b/lang/ca/notifications.php index 5539ae9a9..8f86b85ad 100644 --- a/lang/ca/notifications.php +++ b/lang/ca/notifications.php @@ -4,23 +4,24 @@ */ return [ - 'new_comment_subject' => 'New comment on page: :pageName', - 'new_comment_intro' => 'A user has commented on a page in :appName:', - 'new_page_subject' => 'New page: :pageName', - 'new_page_intro' => 'A new page has been created in :appName:', - 'updated_page_subject' => 'Updated page: :pageName', - 'updated_page_intro' => 'A page has been updated in :appName:', - 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'new_comment_subject' => 'Comentari nou a la pàgina :pageName', + 'new_comment_intro' => 'Un usuari ha comentat en una pàgina de :appName:', + 'new_page_subject' => 'Pàgina nova: :pageName', + 'new_page_intro' => 'S’ha creat una pàgina nova a :appName:', + 'updated_page_subject' => 'Pàgina actualitzada: :pageName', + 'updated_page_intro' => 'S’ha actualitzat una pàgina a :appName:', + 'updated_page_debounce' => 'Per a evitar les notificacions massives, no us enviarem notificacions si hi ha més edicions en aquesta pàgina fetes pel mateix editor.', - 'detail_page_name' => 'Page Name:', - 'detail_commenter' => 'Commenter:', - 'detail_comment' => 'Comment:', - 'detail_created_by' => 'Created By:', - 'detail_updated_by' => 'Updated By:', + 'detail_page_name' => 'Nom de la pàgina:', + 'detail_page_path' => 'Camí de la pàgina:', + 'detail_commenter' => 'Autor del comentari:', + 'detail_comment' => 'Comentari:', + 'detail_created_by' => 'Creat per:', + 'detail_updated_by' => 'Actualitzat per:', - 'action_view_comment' => 'View Comment', - 'action_view_page' => 'View Page', + 'action_view_comment' => 'Mostra el comentari', + 'action_view_page' => 'Mostra la pàgina', - 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', - 'footer_reason_link' => 'your notification preferences', + 'footer_reason' => 'Rebeu aquesta notificació perquè :link cobreixen aquest tipus d’activitat en aquest element.', + 'footer_reason_link' => 'les vostres preferències de notificacions', ]; diff --git a/lang/ca/passwords.php b/lang/ca/passwords.php index 1f0f75915..60789c8a2 100644 --- a/lang/ca/passwords.php +++ b/lang/ca/passwords.php @@ -6,10 +6,10 @@ */ return [ - 'password' => 'Les contrasenyes han de tenir com a mínim vuit caràcters i la confirmació ha de coincidir.', - 'user' => "No s'ha trobat cap usuari amb aquest correu electrònic.", - 'token' => 'El token de restabliment de contrasenya no és vàlid per aquest correu electrònic.', - 'sent' => 'T\'hem enviat un enllaç per a restablir la contrasenya!', - 'reset' => 'S\'ha restablert la teva contrasenya!', + 'password' => 'Les contrasenyes han de tenir almenys vuit caràcters i coincidir amb la confirmació.', + 'user' => "No s’ha trobat cap usuari amb aquesta adreça electrònica.", + 'token' => 'El testimoni de restabliment de la contrasenya no és vàlid per a aquesta adreça electrònica.', + 'sent' => 'Us hem enviat un enllaç per a restablir la contrasenya!', + 'reset' => 'S’ha restablert la contrasenya!', ]; diff --git a/lang/ca/preferences.php b/lang/ca/preferences.php index 2b88f9671..b56b8b3bb 100644 --- a/lang/ca/preferences.php +++ b/lang/ca/preferences.php @@ -5,47 +5,47 @@ */ return [ - 'my_account' => 'My Account', + 'my_account' => 'El meu compte', - 'shortcuts' => 'Shortcuts', - 'shortcuts_interface' => 'UI Shortcut Preferences', - 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', - 'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.', - 'shortcuts_toggle_label' => 'Keyboard shortcuts enabled', - 'shortcuts_section_navigation' => 'Navigation', - 'shortcuts_section_actions' => 'Common Actions', - 'shortcuts_save' => 'Save Shortcuts', - 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', - 'shortcuts_update_success' => 'Shortcut preferences have been updated!', - 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + 'shortcuts' => 'Dreceres', + 'shortcuts_interface' => 'Preferències de les dreceres de la interfície d’usuari', + 'shortcuts_toggle_desc' => 'Aquí podeu activar o desactivar les dreceres de teclat de la interfície del sistema, que s’utilitzen per a la navegació i les accions.', + 'shortcuts_customize_desc' => 'Podeu personalitzar cadascuna de les dreceres següents. Premeu la combinació de tecles desitjada després de seleccionar la casella d’una drecera.', + 'shortcuts_toggle_label' => 'Dreceres de teclat activades', + 'shortcuts_section_navigation' => 'Navegació', + 'shortcuts_section_actions' => 'Accions habituals', + 'shortcuts_save' => 'Desa les dreceres', + 'shortcuts_overlay_desc' => 'Nota: Quan s’activen les dreceres, hi ha una interfície d’ajuda disponible en prémer «?» que destaca les dreceres disponibles per a accions visibles actualment a la pantalla.', + 'shortcuts_update_success' => 'S’han actualitzat les preferències de les dreceres!', + 'shortcuts_overview_desc' => 'Gestioneu les dreceres de teclat que podeu utilitzar per a navegar per la interfície d’usuari del sistema.', - 'notifications' => 'Notification Preferences', - 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', - 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', - 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', - 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', - 'notifications_save' => 'Save Preferences', - 'notifications_update_success' => 'Notification preferences have been updated!', - 'notifications_watched' => 'Watched & Ignored Items', - 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + 'notifications' => 'Preferències de notificació', + 'notifications_desc' => 'Controleu les notificacions per correu electrònic que rebeu quan es fan certes activitats dins del sistema.', + 'notifications_opt_own_page_changes' => 'Notifica’m quan hi hagi canvis a pàgines de les quals sóc propietari', + 'notifications_opt_own_page_comments' => 'Notifica’m quan hi hagi comentaris a pàgines de les quals sóc propietari', + 'notifications_opt_comment_replies' => 'Notifica’m quan hi hagi respostes als meus comentaris', + 'notifications_save' => 'Desa les preferències', + 'notifications_update_success' => 'S’han actualitzat les preferències de notificacions!', + 'notifications_watched' => 'Elements seguits i ignorats', + 'notifications_watched_desc' => ' A continuació hi ha els elements que tenen aplicades preferències de seguiment personalitzades. Per a actualitzar-ne les preferències, consulteu l’element i seleccioneu les opcions de seguiment a la barra lateral.', - 'auth' => 'Access & Security', - 'auth_change_password' => 'Change Password', - 'auth_change_password_desc' => 'Change the password you use to log-in to the application. This must be at least 8 characters long.', - 'auth_change_password_success' => 'Password has been updated!', + 'auth' => 'Accés i seguretat', + 'auth_change_password' => 'Canvia la contrasenya', + 'auth_change_password_desc' => 'Canvieu la contrasenya que feu servir per a iniciar la sessió a l’aplicació. Cal que tingui un mínim de 8 caràcters.', + 'auth_change_password_success' => 'S’ha actualitzat la contrasenya!', - 'profile' => 'Profile Details', - 'profile_desc' => 'Manage the details of your account which represents you to other users, in addition to details that are used for communication and system personalisation.', - 'profile_view_public' => 'View Public Profile', - 'profile_name_desc' => 'Configure your display name which will be visible to other users in the system through the activity you perform, and content you own.', - 'profile_email_desc' => 'This email will be used for notifications and, depending on active system authentication, system access.', - 'profile_email_no_permission' => 'Unfortunately you don\'t have permission to change your email address. If you want to change this, you\'d need to ask an administrator to change this for you.', - 'profile_avatar_desc' => 'Select an image which will be used to represent yourself to others in the system. Ideally this image should be square and about 256px in width and height.', - 'profile_admin_options' => 'Administrator Options', - 'profile_admin_options_desc' => 'Additional administrator-level options, like those to manage role assignments, can be found for your user account in the "Settings > Users" area of the application.', + 'profile' => 'Detalls del perfil', + 'profile_desc' => 'Gestioneu els detalls del vostre compte, que us representa davant d’altres usuaris, i també els detalls que s’utilitzen per a la comunicació i la personalització del sistema.', + 'profile_view_public' => 'Mostra el perfil públic', + 'profile_name_desc' => 'Configureu el vostre nom públic, que és visible als altres usuaris del sistema a través de les activitats que realitzeu i del contingut del qual sou propietari.', + 'profile_email_desc' => 'Aquest correu s’utilitza per a notificacions i, depenent de l’autenticació activa del sistema, per a l’accés al sistema.', + 'profile_email_no_permission' => 'Malauradament, no teniu permisos per a canviar l’adreça electrònica. Si voleu canviar-la, caldrà que demaneu a un administrador que us faci el canvi.', + 'profile_avatar_desc' => 'Seleccioneu una imatge que us representarà davant d’altres usuaris del sistema. Idealment, aquesta imatge hauria de ser un quadrat de 256 px d’alçada i d’amplada.', + 'profile_admin_options' => 'Opcions d’administració', + 'profile_admin_options_desc' => 'Podeu trobar opcions de nivell d’administració addicionals del vostre compte, com ara les que permeten gestionar les assignacions de rols, a l’àrea de l’aplicació «Configuració > Usuaris».', - 'delete_account' => 'Delete Account', - 'delete_my_account' => 'Delete My Account', - 'delete_my_account_desc' => 'This will fully delete your user account from the system. You will not be able to recover this account or revert this action. Content you\'ve created, such as created pages and uploaded images, will remain.', - 'delete_my_account_warning' => 'Are you sure you want to delete your account?', + 'delete_account' => 'Suprimeix el compte', + 'delete_my_account' => 'Suprimeix el meu compte', + 'delete_my_account_desc' => 'Se suprimirà completament el vostre compte d’usuari del sistema. No podreu recuperar aquest compte ni revertir aquesta acció. El contingut que hàgiu creat, com ara les pàgines creades o les imatges pujades, es mantindrà.', + 'delete_my_account_warning' => 'Segur que voleu suprimir el vostre compte?', ]; diff --git a/lang/ca/settings.php b/lang/ca/settings.php index 48a04e7e2..0305a5c23 100644 --- a/lang/ca/settings.php +++ b/lang/ca/settings.php @@ -9,51 +9,51 @@ return [ // Common Messages 'settings' => 'Configuració', 'settings_save' => 'Desa la configuració', - 'system_version' => 'System Version', + 'system_version' => 'Versió del sistema', 'categories' => 'Categories', // App Settings 'app_customization' => 'Personalització', 'app_features_security' => 'Funcionalitats i seguretat', - 'app_name' => 'Nom de l\'aplicació', + 'app_name' => 'Nom de l’aplicació', 'app_name_desc' => 'Aquest nom es mostra a la capçalera i en tots els correus electrònics enviats pel sistema.', 'app_name_header' => 'Mostra el nom a la capçalera', 'app_public_access' => 'Accés públic', 'app_public_access_desc' => 'Si activeu aquesta opció, es permetrà que els visitants que no hagin iniciat la sessió accedeixin al contingut de la vostra instància del BookStack.', - 'app_public_access_desc_guest' => 'Podeu controlar l\'accés dels visitants públics amb l\'usuari "Convidat".', - 'app_public_access_toggle' => 'Permet l\'accés públic', + 'app_public_access_desc_guest' => 'Podeu controlar l’accés dels visitants públics amb l’usuari «Convidat».', + 'app_public_access_toggle' => 'Permet l’accés públic', 'app_public_viewing' => 'Voleu permetre la visualització pública?', - 'app_secure_images' => 'Pujades d\'imatges amb més seguretat', - 'app_secure_images_toggle' => 'Activa les pujades d\'imatges amb més seguretat', - 'app_secure_images_desc' => 'Per motius de rendiment, totes les imatges són públiques. Aquesta opció afegeix una cadena aleatòria i difícil d\'endevinar al davant dels URL d\'imatges. Assegureu-vos que els índexs de directoris no estiguin activats per a evitar-hi l\'accés de manera fàcil.', - 'app_default_editor' => 'Default Page Editor', - 'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.', + 'app_secure_images' => 'Pujades d’imatges amb més seguretat', + 'app_secure_images_toggle' => 'Activa les pujades d’imatges amb més seguretat', + 'app_secure_images_desc' => 'Per motius de rendiment, totes les imatges són públiques. Aquesta opció afegeix una cadena aleatòria i difícil d’endevinar al davant dels URL d’imatges. Assegureu-vos que els índexs de directoris no estiguin activats per a evitar-hi l’accés de manera fàcil.', + 'app_default_editor' => 'Editor de pàgines per defecte', + 'app_default_editor_desc' => 'Seleccioneu quin editor es farà servir per defecte en editar pàgines noves. Es pot canviar a nivell de pàgina si els permisos ho permeten.', 'app_custom_html' => 'Contingut personalitzat a la capçalera HTML', - 'app_custom_html_desc' => 'Aquí podeu afegir contingut que s\'inserirà a la part final de la secció de cada pàgina. És útil per a sobreescriure estils o afegir-hi codi d\'analítiques.', + 'app_custom_html_desc' => 'Aquí podeu afegir contingut que s’inserirà a la part final de la secció de cada pàgina. És útil per a sobreescriure estils o afegir-hi codi d’analítiques.', 'app_custom_html_disabled_notice' => 'El contingut personalitzat a la capçalera HTML es desactiva en aquesta pàgina de la configuració per a assegurar que qualsevol canvi que trenqui el web es pugui desfer.', - 'app_logo' => 'Logo de l\'aplicació', - 'app_logo_desc' => 'This is used in the application header bar, among other areas. This image should be 86px in height. Large images will be scaled down.', - 'app_icon' => 'Application Icon', - 'app_icon_desc' => 'This icon is used for browser tabs and shortcut icons. This should be a 256px square PNG image.', - 'app_homepage' => 'Pàgina d\'inici de l\'aplicació', - 'app_homepage_desc' => 'Seleccioneu la visualització que es mostrarà a la pàgina d\'inici en lloc de la visualització per defecte. Els permisos de pàgines s\'ignoraran per a les pàgines seleccionades.', + 'app_logo' => 'Logo de l’aplicació', + 'app_logo_desc' => 'Es fa servir a la barra de la capçalera de l’aplicació, a banda d’altres zones. Aquesta imatge ha de fer 86 px d’alçada. Les imatges massa grosses es reduiran.', + 'app_icon' => 'Icona de l’aplicació', + 'app_icon_desc' => 'Aquesta icona es fa servir a la pestanya del navegador i a les icones de les dreceres. Hauria de ser una imatge PNG quadrada de 256 px.', + 'app_homepage' => 'Pàgina d’inici de l’aplicació', + 'app_homepage_desc' => 'Seleccioneu la visualització que es mostrarà a la pàgina d’inici en lloc de la visualització per defecte. Els permisos de pàgines s’ignoraran per a les pàgines seleccionades.', 'app_homepage_select' => 'Selecciona una pàgina', 'app_footer_links' => 'Enllaços al peu de pàgina', - 'app_footer_links_desc' => 'Afegiu enllaços que es mostraran al peu de pàgina del lloc. Es mostraran a la part inferior de la majoria de pàgines, incloent-hi les que no requereixen iniciar la sessió. Podeu utilitzar l\'etiqueta "trans::" per a fer servir traduccions definides pel sistema. Per exemple, si feu servir "trans::common.privacy_policy", es mostrarà el text traduït "Política de privadesa", i amb "trans::common.terms_of_service" es mostrarà el text traduït "Condicions del servei".', - 'app_footer_links_label' => 'Etiqueta de l\'enllaç', - 'app_footer_links_url' => 'URL de l\'enllaç', + 'app_footer_links_desc' => 'Afegiu enllaços que es mostraran al peu de pàgina del lloc. Es mostraran a la part inferior de la majoria de pàgines, incloent-hi les que no requereixen iniciar la sessió. Podeu utilitzar l’etiqueta «trans::» per a fer servir traduccions definides pel sistema. Per exemple, si feu servir «trans::common.privacy_policy», es mostrarà el text traduït «Política de privadesa», i amb «trans::common.terms_of_service» es mostrarà el text traduït «Condicions del servei».', + 'app_footer_links_label' => 'Etiqueta de l’enllaç', + 'app_footer_links_url' => 'URL de l’enllaç', 'app_footer_links_add' => 'Afegeix un enllaç al peu de pàgina', 'app_disable_comments' => 'Desactiva els comentaris', 'app_disable_comments_toggle' => 'Desactiva els comentaris', - 'app_disable_comments_desc' => 'Desactiva els comentaris a totes les pàgines de l\'aplicació.
Els comentaris existents no es mostraran.', + 'app_disable_comments_desc' => 'Desactiva els comentaris a totes les pàgines de l’aplicació.
Els comentaris existents no es mostraran.', // Color settings - 'color_scheme' => 'Application Color Scheme', - 'color_scheme_desc' => 'Set the colors to use in the application user interface. Colors can be configured separately for dark and light modes to best fit the theme and ensure legibility.', - 'ui_colors_desc' => 'Set the application primary color and default link color. The primary color is mainly used for the header banner, buttons and interface decorations. The default link color is used for text-based links and actions, both within written content and in the application interface.', - 'app_color' => 'Primary Color', - 'link_color' => 'Default Link Color', - 'content_colors_desc' => 'Set colors for all elements in the page organisation hierarchy. Choosing colors with a similar brightness to the default colors is recommended for readability.', + 'color_scheme' => 'Esquema de colors de l’aplicació', + 'color_scheme_desc' => 'Definiu els colors que s’utilitzaran a la interfície d’usuari de l’aplicació. Els colors es poden configurar de manera separada per als modes fosc i clar perquè encaixin millor amb el tema i n’assegurin la llegibilitat.', + 'ui_colors_desc' => 'Definiu el color primari de l’aplicació i el color per defecte dels enllaços. El color primari es fa servir sobretot per a la capçalera, els botons i la decoració de la interfície. El color per defecte dels enllaços s’utilitza per als enllaços de text i les accions, tant al contingut escrit com a la interfície de l’aplicació.', + 'app_color' => 'Color primari', + 'link_color' => 'Color per defecte dels enllaços', + 'content_colors_desc' => 'Definiu els colors per a tots els elements de la jerarquia d’organització de pàgines. És recomanable triar colors amb una brillantor similar a la dels colors per defecte per a millorar la llegibilitat.', 'bookshelf_color' => 'Color dels prestatges', 'book_color' => 'Color dels llibres', 'chapter_color' => 'Color dels capítols', @@ -62,46 +62,46 @@ return [ // Registration Settings 'reg_settings' => 'Registre', - 'reg_enable' => 'Activa el registre d\'usuaris', - 'reg_enable_toggle' => 'Activa el registre d\'usuaris', - 'reg_enable_desc' => 'Si els registres estan activats, els usuaris podran registrar-se ells mateixos com a usuaris de l\'aplicació. Un cop registrats, se\'ls assigna un únic rol d\'usuari per defecte.', - 'reg_default_role' => 'Rol d\'usuari per defecte en registrar-se', - 'reg_enable_external_warning' => 'L\'opció anterior s\'ignora quan hi ha activada l\'autenticació SAML o LDAP externa. Els comptes d\'usuari de membres inexistents es creada automàticament si l\'autenticació contra el sistema extern és satisfactòria.', + 'reg_enable' => 'Activa el registre d’usuaris', + 'reg_enable_toggle' => 'Activa el registre d’usuaris', + 'reg_enable_desc' => 'Si els registres estan activats, els usuaris podran registrar-se ells mateixos com a usuaris de l’aplicació. Un cop registrats, se’ls assigna un únic rol d’usuari per defecte.', + 'reg_default_role' => 'Rol d’usuari per defecte en registrar-se', + 'reg_enable_external_warning' => 'L’opció anterior s’ignora quan hi ha activada l’autenticació SAML o LDAP externa. Els comptes d’usuari de membres inexistents es crearan automàticament si l’autenticació contra el sistema extern és satisfactòria.', 'reg_email_confirmation' => 'Confirmació de correu electrònic', 'reg_email_confirmation_toggle' => 'Requereix la confirmació per correu electrònic', - 'reg_confirm_email_desc' => 'Si s\'utilitza la restricció de dominis, serà obligatòria la confirmació per correu electrònic, i s\'ignorarà aquesta opció.', + 'reg_confirm_email_desc' => 'Si s’utilitza la restricció de dominis, serà obligatòria la confirmació per correu electrònic, i s’ignorarà aquesta opció.', 'reg_confirm_restrict_domain' => 'Restricció de dominis', - 'reg_confirm_restrict_domain_desc' => 'Introduïu una llista separada per comes de dominis de correu electrònic als quals voleu restringir els registres. S\'enviarà un correu electrònic als usuaris perquè confirmin la seva adreça abans de permetre\'ls interactuar amb l\'aplicació.
Tingueu en compte que els usuaris podran canviar les seves adreces electròniques després de registrar-se correctament.', + 'reg_confirm_restrict_domain_desc' => 'Introduïu una llista separada per comes de dominis de correu electrònic als quals voleu restringir els registres. S’enviarà un correu electrònic als usuaris perquè confirmin la seva adreça abans de permetre’ls interactuar amb l’aplicació.
Tingueu en compte que els usuaris podran canviar les seves adreces electròniques després de registrar-se correctament.', 'reg_confirm_restrict_domain_placeholder' => 'No hi ha cap restricció', // Maintenance settings 'maint' => 'Manteniment', 'maint_image_cleanup' => 'Neteja les imatges', - 'maint_image_cleanup_desc' => 'Escaneja el contingut de les pàgines i les revisions per a comprovar quines imatges i diagrames estan en ús actualment i quines imatges són redundants. Assegureu-vos de crear una còpia de seguretat completa de la base de dades i de les imatges abans d\'executar això.', + 'maint_image_cleanup_desc' => 'Escaneja el contingut de les pàgines i les revisions per a comprovar quines imatges i diagrames estan en ús actualment i quines imatges són redundants. Assegureu-vos de crear una còpia de seguretat completa de la base de dades i de les imatges abans d’executar això.', 'maint_delete_images_only_in_revisions' => 'Suprimeix també les imatges que només existeixin en revisions antigues de pàgines', 'maint_image_cleanup_run' => 'Executa la neteja', - 'maint_image_cleanup_warning' => 'S\'han trobat :count imatges potencialment no utilitzades. Segur que voleu suprimir aquestes imatges?', - 'maint_image_cleanup_success' => 'S\'han trobat i suprimit :count imatges potencialment no utilitzades!', - 'maint_image_cleanup_nothing_found' => 'No s\'ha trobat cap imatge no utilitzada, i no s\'ha suprimit res!', + 'maint_image_cleanup_warning' => 'S’han trobat :count imatges potencialment no utilitzades. Segur que voleu suprimir aquestes imatges?', + 'maint_image_cleanup_success' => 'S’han trobat i suprimit :count imatges potencialment no utilitzades!', + 'maint_image_cleanup_nothing_found' => 'No s’ha trobat cap imatge no utilitzada, així que no s’ha suprimit res!', 'maint_send_test_email' => 'Envia un correu electrònic de prova', - 'maint_send_test_email_desc' => 'Envia un correu electrònic de prova a l\'adreça electrònica que hàgiu especificat al perfil.', + 'maint_send_test_email_desc' => 'Envia un correu electrònic de prova a l’adreça electrònica que hàgiu especificat al perfil.', 'maint_send_test_email_run' => 'Envia el correu electrònic de prova', - 'maint_send_test_email_success' => 'S\'ha enviat el correu electrònic a :address', + 'maint_send_test_email_success' => 'S’ha enviat el correu electrònic a :address', 'maint_send_test_email_mail_subject' => 'Correu electrònic de prova', 'maint_send_test_email_mail_greeting' => 'El lliurament de correus electrònics sembla que funciona!', 'maint_send_test_email_mail_text' => 'Enhorabona! Com que heu rebut aquesta notificació per correu electrònic, la vostra configuració del correu electrònic sembla que està ben configurada.', - 'maint_recycle_bin_desc' => 'Els prestatges, llibres, capítols i pàgines eliminats s\'envien a la paperera de reciclatge perquè es puguin restaurar o suprimir de manera permanent. Pot ser que els elements més antics de la paperera de reciclatge se suprimeixin automàticament després d\'un temps, depenent de la configuració del sistema.', + 'maint_recycle_bin_desc' => 'Els prestatges, llibres, capítols i pàgines suprimits s’envien a la paperera de reciclatge perquè es puguin restaurar o suprimir de manera permanent. Pot ser que els elements més antics de la paperera de reciclatge se suprimeixin automàticament després d’un temps, depenent de la configuració del sistema.', 'maint_recycle_bin_open' => 'Obre la paperera de reciclatge', - 'maint_regen_references' => 'Regenerate References', - 'maint_regen_references_desc' => 'This action will rebuild the cross-item reference index within the database. This is usually handled automatically but this action can be useful to index old content or content added via unofficial methods.', - 'maint_regen_references_success' => 'Reference index has been regenerated!', - 'maint_timeout_command_note' => 'Note: This action can take time to run, which can lead to timeout issues in some web environments. As an alternative, this action be performed using a terminal command.', + 'maint_regen_references' => 'Regenera les referències', + 'maint_regen_references_desc' => 'Aquesta acció reconstruirà l’índex de referències entre elements de la base de dades. Normalment es gestiona automàticament, però aquesta acció pot ser útil per a indexar contingut antic o contingut afegit mitjançant mètodes no oficials.', + 'maint_regen_references_success' => 'L’índex de referències s’ha regenerat!', + 'maint_timeout_command_note' => 'Nota: Aquesta acció pot trigar estona a executar-se, la qual cosa pot provocar errors de temps d’expera excedits en alguns entorns web. Com a alternativa, podeu executar aquesta acció en una ordre al terminal.', // Recycle Bin 'recycle_bin' => 'Paperera de reciclatge', - 'recycle_bin_desc' => 'Aquí podeu restaurar els elements que hàgiu suprimit o triar suprimir-los del sistema de manera permanent. Aquesta llista no té cap filtre, al contrari que altres llistes d\'activitat similars en què es tenen en compte els filtres de permisos.', + 'recycle_bin_desc' => 'Aquí podeu restaurar els elements que hàgiu suprimit o triar suprimir-los del sistema de manera permanent. Aquesta llista no té cap filtre, al contrari que altres llistes d’activitat similars en què es tenen en compte els filtres de permisos.', 'recycle_bin_deleted_item' => 'Element suprimit', - 'recycle_bin_deleted_parent' => 'Parent', + 'recycle_bin_deleted_parent' => 'Pare', 'recycle_bin_deleted_by' => 'Suprimit per', 'recycle_bin_deleted_at' => 'Moment de la supressió', 'recycle_bin_permanently_delete' => 'Suprimeix permanentment', @@ -112,63 +112,63 @@ return [ 'recycle_bin_destroy_confirm' => 'Aquesta acció suprimirà del sistema de manera permanent aquest element, juntament amb tots els elements fills que es llisten a sota, i no podreu restaurar aquest contingut. Segur que voleu suprimir de manera permanent aquest element?', 'recycle_bin_destroy_list' => 'Elements que es destruiran', 'recycle_bin_restore_list' => 'Elements que es restauraran', - 'recycle_bin_restore_confirm' => 'Aquesta acció restaurarà l\'element suprimit, incloent-hi tots els elements fills, a la seva ubicació original. Si la ubicació original ha estat suprimida, i ara és a la paperera de reciclatge, caldrà que també en restaureu l\'element pare.', - 'recycle_bin_restore_deleted_parent' => 'El pare d\'aquest element també ha estat suprimit. L\'element es mantindrà suprimit fins que el pare també es restauri.', - 'recycle_bin_restore_parent' => 'Restore Parent', - 'recycle_bin_destroy_notification' => 'S\'han suprimit :count elements en total de la paperera de reciclatge.', - 'recycle_bin_restore_notification' => 'S\'han restaurat :count elements en total de la paperera de reciclatge.', + 'recycle_bin_restore_confirm' => 'Aquesta acció restaurarà l’element suprimit, incloent-hi tots els elements fills, a la seva ubicació original. Si la ubicació original ha estat suprimida, i ara és a la paperera de reciclatge, caldrà que també en restaureu l’element pare.', + 'recycle_bin_restore_deleted_parent' => 'El pare d’aquest element també ha estat suprimit. L’element es mantindrà suprimit fins que el pare també es restauri.', + 'recycle_bin_restore_parent' => 'Restaura’n el pare', + 'recycle_bin_destroy_notification' => 'S’han suprimit :count elements en total de la paperera de reciclatge.', + 'recycle_bin_restore_notification' => 'S’han restaurat :count elements en total de la paperera de reciclatge.', // Audit Log - 'audit' => 'Registre d\'auditoria', - 'audit_desc' => 'Aquest registre d\'auditoria mostra una llista d\'activitats registrades al sistema. Aquesta llista no té cap filtre, al contrari que altres llistes d\'activitat similars en què es tenen en compte els filtres de permisos.', - 'audit_event_filter' => 'Filtre d\'esdeveniments', + 'audit' => 'Registre d’auditoria', + 'audit_desc' => 'Aquest registre d’auditoria mostra una llista d’activitats registrades al sistema. Aquesta llista no té cap filtre, al contrari que altres llistes d’activitat similars en què es tenen en compte els filtres de permisos.', + 'audit_event_filter' => 'Filtre d’esdeveniments', 'audit_event_filter_no_filter' => 'Sense filtre', 'audit_deleted_item' => 'Element suprimit', 'audit_deleted_item_name' => 'Nom: :name', 'audit_table_user' => 'Usuari', 'audit_table_event' => 'Esdeveniment', 'audit_table_related' => 'Element relacionat o detall', - 'audit_table_ip' => 'IP Address', - 'audit_table_date' => 'Data de l\'activitat', + 'audit_table_ip' => 'Adreça IP', + 'audit_table_date' => 'Data de l’activitat', 'audit_date_from' => 'Rang de dates a partir de', 'audit_date_to' => 'Rang de rates fins a', // Role Settings 'roles' => 'Rols', - 'role_user_roles' => 'Rols d\'usuari', - 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => ':count user assigned|:count users assigned', - 'roles_x_permissions_provided' => ':count permission|:count permissions', - 'roles_assigned_users' => 'Assigned Users', - 'roles_permissions_provided' => 'Provided Permissions', + 'role_user_roles' => 'Rols d’usuari', + 'roles_index_desc' => 'Els rols s’utilitzen per a agrupar usuaris i proporcionar permisos del sistema a llurs membres. Quan un usuari és membre de múltiples rols, els privilegis que li concedeixin s’acumularan i l’usuari heretarà totes les habilitats.', + 'roles_x_users_assigned' => ':count usuari assignat|:count usuaris assignats', + 'roles_x_permissions_provided' => ':count permís|:count permisos', + 'roles_assigned_users' => 'Usuaris assignats', + 'roles_permissions_provided' => 'Permisos proporcionats', 'role_create' => 'Crea un rol nou', 'role_delete' => 'Suprimeix el rol', - 'role_delete_confirm' => 'Se suprimirà el rol amb el nom \':roleName\'.', - 'role_delete_users_assigned' => 'Aquest rol té :userCount usuaris assignats. Si voleu migrar els usuaris d\'aquest rol, seleccioneu un rol nou a continuació.', + 'role_delete_confirm' => 'Se suprimirà el rol amb el nom «:roleName».', + 'role_delete_users_assigned' => 'Aquest rol té :userCount usuaris assignats. Si voleu migrar els usuaris d’aquest rol, seleccioneu un rol nou a continuació.', 'role_delete_no_migration' => "No migris els usuaris", 'role_delete_sure' => 'Segur que voleu suprimir aquest rol?', 'role_edit' => 'Edita el rol', 'role_details' => 'Detalls del rol', 'role_name' => 'Nom del rol', 'role_desc' => 'Descripció curta del rol', - 'role_mfa_enforced' => 'Requires Multi-Factor Authentication', - 'role_external_auth_id' => 'Identificadors d\'autenticació externa', + 'role_mfa_enforced' => 'Requereix autenticació de múltiple factor', + 'role_external_auth_id' => 'Identificadors d’autenticació externa', 'role_system' => 'Permisos del sistema', 'role_manage_users' => 'Gestiona usuaris', 'role_manage_roles' => 'Gestiona rols i permisos de rols', 'role_manage_entity_permissions' => 'Gestiona els permisos de tots els llibres, capítols i pàgines', 'role_manage_own_entity_permissions' => 'Gestiona els permisos dels llibres, capítols i pàgines propis', 'role_manage_page_templates' => 'Gestiona les plantilles de pàgines', - 'role_access_api' => 'Accedeix a l\'API del sistema', - 'role_manage_settings' => 'Gestiona la configuració de l\'aplicació', - 'role_export_content' => 'Export content', - 'role_editor_change' => 'Change page editor', - 'role_notifications' => 'Receive & manage notifications', + 'role_access_api' => 'Accedeix a l’API del sistema', + 'role_manage_settings' => 'Gestiona la configuració de l’aplicació', + 'role_export_content' => 'Exporta el contingut', + 'role_editor_change' => 'Canvia l’editor de pàgines', + 'role_notifications' => 'Rep i gestiona les notificacions', 'role_asset' => 'Permisos de recursos', - 'roles_system_warning' => 'Tingueu en compte que l\'accés a qualsevol dels tres permisos de dalt pot permetre que un usuari alteri els seus propis permisos o els privilegis d\'altres usuaris del sistema. Assigneu rols amb aquests permisos només a usuaris de confiança.', - 'role_asset_desc' => 'Aquests permisos controlen l\'accés per defecte als recursos del sistema. Els permisos de llibres, capítols i pàgines tindran més importància que aquests permisos.', - 'role_asset_admins' => 'Els administradors tenen accés automàticament a tot el contingut, però aquestes opcions poden mostrar o amagar opcions de la interfície d\'usuari.', - 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', + 'roles_system_warning' => 'Tingueu en compte que l’accés a qualsevol dels tres permisos de dalt pot permetre que un usuari alteri els seus propis permisos o els privilegis d’altres usuaris del sistema. Assigneu rols amb aquests permisos només a usuaris de confiança.', + 'role_asset_desc' => 'Aquests permisos controlen l’accés per defecte als recursos del sistema. Els permisos de llibres, capítols i pàgines tindran més importància que aquests permisos.', + 'role_asset_admins' => 'Els administradors tenen accés automàticament a tot el contingut, però aquestes opcions poden mostrar o amagar opcions de la interfície d’usuari.', + 'role_asset_image_view_note' => 'Això és relatiu a la visibilitat dins del gestor d’imatges. L’accés real als fitxers d’imatge pujats dependrà de l’opció d’emmagatzematge d’imatges del sistema.', 'role_all' => 'Tot', 'role_own' => 'Propi', 'role_controlled_by_asset' => 'Controlat pel recurs en què es pugen', @@ -178,103 +178,103 @@ return [ // Users 'users' => 'Usuaris', - 'users_index_desc' => 'Create & manage individual user accounts within the system. User accounts are used for login and attribution of content & activity. Access permissions are primarily role-based but user content ownership, among other factors, may also affect permissions & access.', - 'user_profile' => 'Perfil de l\'usuari', + 'users_index_desc' => 'Creeu i gestioneu comptes d’usuari individuals dins del sistema. Els comptes d’usuari s’utilitzen per a iniciar la sessió i atribuir el contingut i les activitats. Els permisos d’accés són principalment basats en rols, però si un usuari és propietari o no d’un contingut, entre altres factors, també pot afectar-ne els permisos i l’accés.', + 'user_profile' => 'Perfil de l’usuari', 'users_add_new' => 'Afegeix un usuari nou', 'users_search' => 'Cerca usuaris', 'users_latest_activity' => 'Darrera activitat', - 'users_details' => 'Detalls de l\'usuari', - 'users_details_desc' => 'Definiu un nom públic i una adreça electrònica per a aquest usuari. L\'adreça electrònica es farà servir per a iniciar la sessió a l\'aplicació.', + 'users_details' => 'Detalls de l’usuari', + 'users_details_desc' => 'Definiu un nom públic i una adreça electrònica per a aquest usuari. L’adreça electrònica es farà servir per a iniciar la sessió a l’aplicació.', 'users_details_desc_no_email' => 'Definiu un nom públic per a aquest usuari perquè els altres el puguin reconèixer.', - 'users_role' => 'Rols de l\'usuari', - 'users_role_desc' => 'Seleccioneu a quins rols s\'assignarà l\'usuari. Si un usuari s\'assigna a múltiples rols, els permisos dels rols s\'acumularan i l\'usuari rebrà tots els permisos dels rols assignats.', - 'users_password' => 'Contrasenya de l\'usuari', - 'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 8 characters long.', - 'users_send_invite_text' => 'Podeu elegir enviar un correu d\'invitació a aquest usuari, la qual cosa li permetrà definir la seva contrasenya, o podeu definir-li una contrasenya vós.', - 'users_send_invite_option' => 'Envia un correu d\'invitació a l\'usuari', - 'users_external_auth_id' => 'Identificador d\'autenticació extern', - 'users_external_auth_id_desc' => 'When an external authentication system is in use (such as SAML2, OIDC or LDAP) this is the ID which links this BookStack user to the authentication system account. You can ignore this field if using the default email-based authentication.', - 'users_password_warning' => 'Only fill the below if you would like to change the password for this user.', - 'users_system_public' => 'Aquest usuari representa qualsevol usuari convidat que visita la vostra instància. No es pot fer servir per a iniciar la sessió però s\'assigna automàticament.', - 'users_delete' => 'Suprimeix l\'usuari', - 'users_delete_named' => 'Suprimeix l\'usuari :userName', - 'users_delete_warning' => 'Se suprimirà completament del sistema l\'usuari amb el nom \':userName\'.', + 'users_role' => 'Rols de l’usuari', + 'users_role_desc' => 'Seleccioneu a quins rols s’assignarà l’usuari. Si un usuari s’assigna a múltiples rols, els permisos dels rols s’acumularan i l’usuari rebrà totes les habilitats dels rols assignats.', + 'users_password' => 'Contrasenya de l’usuari', + 'users_password_desc' => 'Definiu una contrasenya que s’utilitzarà per a iniciar la sessió a l’aplicació. Cal que tingui un mínim de 8 caràcters.', + 'users_send_invite_text' => 'Podeu elegir enviar un correu d’invitació a aquest usuari, la qual cosa li permetrà definir la seva contrasenya, o podeu definir-li una contrasenya vós.', + 'users_send_invite_option' => 'Envia un correu d’invitació a l’usuari', + 'users_external_auth_id' => 'Identificador d’autenticació extern', + 'users_external_auth_id_desc' => 'Quan es fa servir un sistema d’autenticació extern (com ara SAML2, OIDC o LDAP), aquest és l’identificador que enllaça aquest usuari del BookStack amb el compte del sistema d’autenticació. Podeu ignorar aquest camp si feu servir l’autenticació basada en correu electrònic per defecte.', + 'users_password_warning' => 'Ompliu-ho només si voleu canviar la contrasenya d’aquest usuari.', + 'users_system_public' => 'Aquest usuari representa qualsevol usuari convidat que visita la vostra instància. No es pot fer servir per a iniciar la sessió, però s’assigna automàticament.', + 'users_delete' => 'Suprimeix l’usuari', + 'users_delete_named' => 'Suprimeix l’usuari :userName', + 'users_delete_warning' => 'Se suprimirà completament del sistema l’usuari amb el nom «:userName».', 'users_delete_confirm' => 'Segur que voleu suprimir aquest usuari?', - 'users_migrate_ownership' => 'Migra l\'autoria', - 'users_migrate_ownership_desc' => 'Seleccioneu un usuari si voleu que un altre usuari esdevingui el propietari de tots els elements que ara són propietat d\'aquest usuari.', + 'users_migrate_ownership' => 'Migra l’autoria', + 'users_migrate_ownership_desc' => 'Seleccioneu un usuari si voleu que un altre usuari esdevingui el propietari de tots els elements que ara són propietat d’aquest usuari.', 'users_none_selected' => 'No hi ha cap usuari seleccionat', - 'users_edit' => 'Edita l\'usuari', + 'users_edit' => 'Edita l’usuari', 'users_edit_profile' => 'Edita el perfil', - 'users_avatar' => 'Avatar de l\'usuari', - 'users_avatar_desc' => 'Seleccioneu una imatge que representi aquest usuari. Hauria de ser un quadrat d\'aproximadament 256 px.', + 'users_avatar' => 'Avatar de l’usuari', + 'users_avatar_desc' => 'Seleccioneu una imatge que representi aquest usuari. Hauria de ser un quadrat d’aproximadament 256 px.', 'users_preferred_language' => 'Llengua preferida', - 'users_preferred_language_desc' => 'Aquesta opció canviarà la llengua utilitzada a la interfície d\'usuari de l\'aplicació. No afectarà el contingut creat pels usuaris.', + 'users_preferred_language_desc' => 'Aquesta opció canviarà la llengua utilitzada a la interfície d’usuari de l’aplicació. No afectarà el contingut creat pels usuaris.', 'users_social_accounts' => 'Comptes socials', - 'users_social_accounts_desc' => 'View the status of the connected social accounts for this user. Social accounts can be used in addition to the primary authentication system for system access.', - 'users_social_accounts_info' => 'Aquí podeu connectar altres comptes per a un inici de sessió més ràpid i còmode. Si desconnecteu un compte aquí, no en revoqueu l\'accés d\'autorització donat amb anterioritat. Revoqueu-hi l\'accés a la configuració del perfil del compte social que hàgiu connectat.', + 'users_social_accounts_desc' => 'Vegeu l’estat dels comptes socials connectats d’aquest usuari. Els comptes socials es poden fer servir per a accedir al sistema de manera addicional al sistema d’autenticació principal.', + 'users_social_accounts_info' => 'Aquí podeu connectar altres comptes per a un inici de sessió més ràpid i còmode. Si desconnecteu un compte aquí, no en revoqueu l’accés d’autorització donat amb anterioritat. Revoqueu-hi l’accés a la configuració del perfil del compte social que hàgiu connectat.', 'users_social_connect' => 'Connecta un compte', 'users_social_disconnect' => 'Desconnecta el compte', - 'users_social_status_connected' => 'Connected', - 'users_social_status_disconnected' => 'Disconnected', - 'users_social_connected' => 'El compte de :socialAccount s\'ha associat correctament al vostre perfil.', - 'users_social_disconnected' => 'El compte de :socialAccount s\'ha desassociat correctament del vostre perfil.', - 'users_api_tokens' => 'Testimonis d\'API', - 'users_api_tokens_desc' => 'Create and manage the access tokens used to authenticate with the BookStack REST API. Permissions for the API are managed via the user that the token belongs to.', - 'users_api_tokens_none' => 'No s\'ha creat cap testimoni d\'API per a aquest usuari', + 'users_social_status_connected' => 'Connectat', + 'users_social_status_disconnected' => 'Desconnectat', + 'users_social_connected' => 'El compte de :socialAccount s’ha associat correctament al vostre perfil.', + 'users_social_disconnected' => 'El compte de :socialAccount ’ha desassociat correctament del vostre perfil.', + 'users_api_tokens' => 'Testimonis de l’API', + 'users_api_tokens_desc' => 'Creeu i gestioneu els testimonis d’accés que s’utilitzen per a autenticar-vos a l’API REST del BookStack. Els permisos de l’API es gestionen fent servir l’usuari al qual pertany el testimoni.', + 'users_api_tokens_none' => 'No s’ha creat cap testimoni de l’API per a aquest usuari', 'users_api_tokens_create' => 'Crea un testimoni', 'users_api_tokens_expires' => 'Caducitat', - 'users_api_tokens_docs' => 'Documentació de l\'API', - 'users_mfa' => 'Multi-Factor Authentication', - 'users_mfa_desc' => 'Setup multi-factor authentication as an extra layer of security for your user account.', - 'users_mfa_x_methods' => ':count method configured|:count methods configured', - 'users_mfa_configure' => 'Configure Methods', + 'users_api_tokens_docs' => 'Documentació de l’API', + 'users_mfa' => 'Autenticació de múltiple factor', + 'users_mfa_desc' => 'Configureu l’autenticació de múltiple factor com a capa extra de seguretat en el vostre compte d’usuari.', + 'users_mfa_x_methods' => ':count mètode configurat|:count mètodes configurats', + 'users_mfa_configure' => 'Configura els mètodes', // API Tokens - 'user_api_token_create' => 'Crea un testimoni d\'API', + 'user_api_token_create' => 'Crea un testimoni de l’API', 'user_api_token_name' => 'Nom', 'user_api_token_name_desc' => 'Poseu un nom llegible al vostre testimoni com a recordatori futur del propòsit al qual el voleu destinar.', 'user_api_token_expiry' => 'Data de caducitat', - 'user_api_token_expiry_desc' => 'Definiu una data en què aquest testimoni caducarà. Després d\'aquesta data, les peticions fetes amb aquest testimoni deixaran de funcionar. Si deixeu aquest camp en blanc, es definirà una caducitat d\'aquí a 100 anys..', - 'user_api_token_create_secret_message' => 'Just després de crear aquest testimoni, es generaran i es mostraran un "Identificador del testimoni" i un "Secret del testimoni". El secret només es mostrarà una única vegada, assegureu-vos de copiar-lo a un lloc segur abans de continuar.', - 'user_api_token' => 'Testimoni d\'API', + 'user_api_token_expiry_desc' => 'Definiu una data en què aquest testimoni caducarà. Després d’aquesta data, les peticions fetes amb aquest testimoni deixaran de funcionar. Si deixeu aquest camp en blanc, es definirà una caducitat d’aquí a 100 anys.', + 'user_api_token_create_secret_message' => 'Just després de crear aquest testimoni, es generaran i es mostraran un «Identificador del testimoni» i un «Secret del testimoni». El secret només es mostrarà una única vegada: assegureu-vos de copiar-lo a un lloc segur abans de continuar.', + 'user_api_token' => 'Testimoni de l’API', 'user_api_token_id' => 'Identificador del testimoni', - 'user_api_token_id_desc' => 'Aquest identificador és generat pel sistema per a aquest testimoni i no és editable, caldrà que el proporcioneu a les peticions a l\'API.', + 'user_api_token_id_desc' => 'Aquest identificador és generat pel sistema per a aquest testimoni i no és editable i caldrà que el proporcioneu a les peticions a l’API.', 'user_api_token_secret' => 'Secret del testimoni', - 'user_api_token_secret_desc' => 'Aquest secret és generat pel sistema per a aquest testimoni, caldrà que el proporcioneu a les peticions a l\'API. Només es mostrarà aquesta única vegada, assegureu-vos de copiar-lo a un lloc segur.', + 'user_api_token_secret_desc' => 'Aquest secret és generat pel sistema per a aquest testimoni i caldrà que el proporcioneu a les peticions a l’API. Només es mostrarà aquesta única vegada, assegureu-vos de copiar-lo a un lloc segur.', 'user_api_token_created' => 'Testimoni creat :timeAgo', 'user_api_token_updated' => 'Testimoni actualitzat :timeAgo', 'user_api_token_delete' => 'Suprimeix el testimoni', - 'user_api_token_delete_warning' => 'Se suprimirà completament del sistema aquest testimoni d\'API amb el nom \':tokenName\'.', - 'user_api_token_delete_confirm' => 'Segur que voleu suprimir aquest testimoni d\'API?', + 'user_api_token_delete_warning' => 'Se suprimirà completament del sistema aquest testimoni de l’API amb el nom «:tokenName».', + 'user_api_token_delete_confirm' => 'Segur que voleu suprimir aquest testimoni de l’API?', // Webhooks 'webhooks' => 'Webhooks', - 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', - 'webhooks_create' => 'Create New Webhook', - 'webhooks_none_created' => 'No webhooks have yet been created.', - 'webhooks_edit' => 'Edit Webhook', - 'webhooks_save' => 'Save Webhook', - 'webhooks_details' => 'Webhook Details', - 'webhooks_details_desc' => 'Provide a user friendly name and a POST endpoint as a location for the webhook data to be sent to.', - 'webhooks_events' => 'Webhook Events', - 'webhooks_events_desc' => 'Select all the events that should trigger this webhook to be called.', - 'webhooks_events_warning' => 'Keep in mind that these events will be triggered for all selected events, even if custom permissions are applied. Ensure that use of this webhook won\'t expose confidential content.', - 'webhooks_events_all' => 'All system events', - 'webhooks_name' => 'Webhook Name', - 'webhooks_timeout' => 'Webhook Request Timeout (Seconds)', - 'webhooks_endpoint' => 'Webhook Endpoint', - 'webhooks_active' => 'Webhook Active', - 'webhook_events_table_header' => 'Events', - 'webhooks_delete' => 'Delete Webhook', - 'webhooks_delete_warning' => 'This will fully delete this webhook, with the name \':webhookName\', from the system.', - 'webhooks_delete_confirm' => 'Are you sure you want to delete this webhook?', - 'webhooks_format_example' => 'Webhook Format Example', - 'webhooks_format_example_desc' => 'Webhook data is sent as a POST request to the configured endpoint as JSON following the format below. The "related_item" and "url" properties are optional and will depend on the type of event triggered.', - 'webhooks_status' => 'Webhook Status', - 'webhooks_last_called' => 'Last Called:', - 'webhooks_last_errored' => 'Last Errored:', - 'webhooks_last_error_message' => 'Last Error Message:', + 'webhooks_index_desc' => 'Els webhooks són una manera d’enviar dades a URL externs quan es produeixen certes accions i esdeveniments al sistema, la qual cosa permet una integració basada en esdeveniments amb plataformes externs, com ara sistemes de missatgeria o de notificacions.', + 'webhooks_x_trigger_events' => ':count esdeveniment disparador|:count elements disparadors', + 'webhooks_create' => 'Crea un webhook nou', + 'webhooks_none_created' => 'Encara no s’ha creat cap webhook.', + 'webhooks_edit' => 'Edita el webhook', + 'webhooks_save' => 'Desa el webhook', + 'webhooks_details' => 'Detalls del webhook', + 'webhooks_details_desc' => 'Proporcioneu un nom amigable i un endpoint POST com a ubicació on s’enviaran les dades del webhook.', + 'webhooks_events' => 'Esdeveniments del webhook', + 'webhooks_events_desc' => 'Seleccioneu tots els esdeveniments que haurien de fer que es cridés aquest webhook.', + 'webhooks_events_warning' => 'Tingueu en compte que aquests esdeveniments es produiran per a tots els esdeveniments seleccionats, fins i tot si s’hi apliquen permisos personalitzats. Assegureu-vos que aquest webhook no exposarà contingut confidencial.', + 'webhooks_events_all' => 'Tots els esdeveniments del sistema', + 'webhooks_name' => 'Nom del webhook', + 'webhooks_timeout' => 'Temps d’espera de les peticions al webhook (en segons)', + 'webhooks_endpoint' => 'Endpoint del webhook', + 'webhooks_active' => 'Webhook actiu', + 'webhook_events_table_header' => 'Esdeveniments', + 'webhooks_delete' => 'Suprimeix el webhook', + 'webhooks_delete_warning' => 'Se suprimirà completament del sistema el webhook amb el nom «:webhookName».', + 'webhooks_delete_confirm' => 'Segur que voleu suprimir aquest webhook?', + 'webhooks_format_example' => 'Exemple del format del webhook', + 'webhooks_format_example_desc' => 'Les dades del webhook s’envien com una petició POST a l’endpoint configurat amb un JSON que segueix el següent format. Les propietats «related_item» i «url» són opcionals i dependran del tipus d’esdeveniment produït.', + 'webhooks_status' => 'Estat del webhook', + 'webhooks_last_called' => 'Cridat per darrera vegada:', + 'webhooks_last_errored' => 'Error per darrera vegada:', + 'webhooks_last_error_message' => 'Darrer missatge d’error:', //! If editing translations files directly please ignore this in all @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/ca/validation.php b/lang/ca/validation.php index bc9be7d05..b73a66c5c 100644 --- a/lang/ca/validation.php +++ b/lang/ca/validation.php @@ -8,17 +8,17 @@ return [ // Standard laravel validation lines - 'accepted' => 'Cal que acceptis :attribute.', - 'active_url' => 'L\':attribute no és un URL vàlid.', + 'accepted' => 'Cal que accepteu el camp :attribute.', + 'active_url' => 'El camp :attribute no és un URL vàlid.', 'after' => 'El camp :attribute ha de ser una data posterior a :date.', 'alpha' => 'El camp :attribute només pot contenir lletres.', 'alpha_dash' => 'El camp :attribute només pot contenir lletres, números, guions i guions baixos.', 'alpha_num' => 'El camp :attribute només pot contenir lletres i números.', 'array' => 'El camp :attribute ha de ser un vector.', - 'backup_codes' => 'The provided code is not valid or has already been used.', + 'backup_codes' => 'El codi que heu proporcionat no és vàlid o ja s’ha fet servir.', 'before' => 'El camp :attribute ha de ser una data anterior a :date.', 'between' => [ - 'numeric' => 'El camp :attribute ha d\'estar entre :min i :max.', + 'numeric' => 'El camp :attribute ha d’estar entre :min i :max.', 'file' => 'El camp :attribute ha de tenir entre :min i :max kilobytes.', 'string' => 'El camp :attribute ha de tenir entre :min i :max caràcters.', 'array' => 'El camp :attribute ha de tenir entre :min i :max elements.', @@ -31,8 +31,8 @@ return [ 'digits' => 'El camp :attribute ha de tenir :digits dígits.', 'digits_between' => 'El camp :attribute ha de tenir entre :min i :max dígits.', 'email' => 'El camp :attribute ha de ser una adreça electrònica vàlida.', - 'ends_with' => 'El camp :attribute ha d\'acabar amb un dels següents valors: :values', - 'file' => 'The :attribute must be provided as a valid file.', + 'ends_with' => 'El camp :attribute ha d’acabar amb un dels següents valors: :values', + 'file' => 'El camp :attribute ha de ser un fitxer vàlid.', 'filled' => 'El camp :attribute és obligatori.', 'gt' => [ 'numeric' => 'El camp :attribute ha de ser més gran que :value.', @@ -46,9 +46,9 @@ return [ 'string' => 'El camp :attribute ha de tenir :value caràcters o més.', 'array' => 'El camp :attribute ha de tenir :value elements o més.', ], - 'exists' => 'El camp :attribute no és vàlid.', + 'exists' => 'El camp :attribute seleccionat no és vàlid.', 'image' => 'El camp :attribute ha de ser una imatge.', - 'image_extension' => 'El camp :attribute ha de tenir una extensió d\'imatge vàlida i suportada.', + 'image_extension' => 'El camp :attribute ha de tenir una extensió d’imatge vàlida i suportada.', 'in' => 'El camp :attribute seleccionat no és vàlid.', 'integer' => 'El camp :attribute ha de ser un enter.', 'ip' => 'El camp :attribute ha de ser una adreça IP vàlida.', @@ -87,11 +87,11 @@ return [ 'required' => 'El camp :attribute és obligatori.', 'required_if' => 'El camp :attribute és obligatori quan :other és :value.', 'required_with' => 'El camp :attribute és obligatori quan hi ha aquest valor: :values.', - 'required_with_all' => 'El camp :attribute és obligatori quan hi ha algun d\'aquests valors: :values.', + 'required_with_all' => 'El camp :attribute és obligatori quan hi ha algun d’aquests valors: :values.', 'required_without' => 'El camp :attribute és obligatori quan no hi ha aquest valor: :values.', - 'required_without_all' => 'El camp :attribute és obligatori quan no hi ha cap d\'aquests valors: :values.', + 'required_without_all' => 'El camp :attribute és obligatori quan no hi ha cap d’aquests valors: :values.', 'same' => 'Els camps :attribute i :other han de coincidir.', - 'safe_url' => 'L\'enllaç proporcionat podria no ser segur.', + 'safe_url' => 'L’enllaç proporcionat podria no ser segur.', 'size' => [ 'numeric' => 'El camp :attribute ha de ser :size.', 'file' => 'El camp :attribute ha de tenir :size kilobytes.', @@ -100,10 +100,10 @@ return [ ], 'string' => 'El camp :attribute ha de ser una cadena.', 'timezone' => 'El camp :attribute ha de ser una zona vàlida.', - 'totp' => 'The provided code is not valid or has expired.', - 'unique' => 'El camp :attribute ja està ocupat.', + 'totp' => 'El codi que heu proporcionat no és vàlid o ha caducat.', + 'unique' => 'El camp :attribute ja es fa servir.', 'url' => 'El format del camp :attribute no és vàlid.', - 'uploaded' => 'No s\'ha pogut pujar el fitxer. És possible que el servidor no accepti fitxers d\'aquesta mida.', + 'uploaded' => 'No s’ha pogut pujar el fitxer. És possible que el servidor no accepti fitxers d’aquesta mida.', // Custom validation lines 'custom' => [ diff --git a/lang/cs/common.php b/lang/cs/common.php index cf1b12922..506f53281 100644 --- a/lang/cs/common.php +++ b/lang/cs/common.php @@ -52,7 +52,7 @@ return [ 'filter_clear' => 'Zrušit filtr', 'download' => 'Stáhnout', 'open_in_tab' => 'Otevřít v nové záložce', - 'open' => 'Open', + 'open' => 'Otevřít', // Sort Options 'sort_options' => 'Možnosti řazení', diff --git a/lang/cs/components.php b/lang/cs/components.php index 8e5b7f009..d265d49b6 100644 --- a/lang/cs/components.php +++ b/lang/cs/components.php @@ -34,8 +34,8 @@ return [ 'image_delete_success' => 'Obrázek byl odstraněn', 'image_replace' => 'Nahradit obrázek', 'image_replace_success' => 'Obrázek úspěšně vytvořen', - 'image_rebuild_thumbs' => 'Regenerate Size Variations', - 'image_rebuild_thumbs_success' => 'Image size variations successfully rebuilt!', + 'image_rebuild_thumbs' => 'Přegenerovat všechny velikosti', + 'image_rebuild_thumbs_success' => 'Všechny velikostní varianty obrázku byly úspěšně znovu vytvořeny!', // Code Editor 'code_editor' => 'Upravit kód', diff --git a/lang/cs/entities.php b/lang/cs/entities.php index 6381c43f2..55b2e71de 100644 --- a/lang/cs/entities.php +++ b/lang/cs/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Aktualizováno :timeLength', 'meta_updated_name' => 'Aktualizováno :timeLength uživatelem :user', 'meta_owned_name' => 'Vlastník :user', - 'meta_reference_page_count' => 'Odkazováno na 1 stránce|Odkazováno na :count stranách', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Výběr entity', 'entity_select_lack_permission' => 'Nemáte dostatečná oprávnění k výběru této položky', 'images' => 'Obrázky', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Upravit knihu :bookName', 'books_form_book_name' => 'Název knihy', 'books_save' => 'Uložit knihu', + 'books_default_template' => 'Výchozí šablona stránky', + 'books_default_template_explain' => 'Přiřadit šablonu stránky, která bude použita jako výchozí obsah pro všechny nové stránky v této knize. Mějte na paměti, že šablona bude použita pouze v případě, že tvůrce stránek bude mít přístup k těmto vybraným stránkám šablony.', + 'books_default_template_select' => 'Vyberte šablonu stránky', 'books_permissions' => 'Oprávnění knihy', 'books_permissions_updated' => 'Oprávnění knihy byla aktualizována', 'books_empty_contents' => 'Pro tuto knihu nebyly vytvořeny žádné stránky ani kapitoly.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Odstranit koncept stránky', 'pages_delete_success' => 'Stránka odstraněna', 'pages_delete_draft_success' => 'Koncept stránky odstraněn', + 'pages_delete_warning_template' => 'Tato stránka je aktivní výchozí šablona. Tyto knihy již nebudou mít výchozí šablonu stránky přiřazenou po odstranění této stránky.', 'pages_delete_confirm' => 'Opravdu chcete odstranit tuto stránku?', 'pages_delete_draft_confirm' => 'Opravdu chcete odstranit tento koncept stránky?', 'pages_editing_named' => 'Úpravy stránky :pageName', @@ -295,7 +299,7 @@ return [ 'pages_is_template' => 'Šablona stránky', // Editor Sidebar - 'toggle_sidebar' => 'Toggle Sidebar', + 'toggle_sidebar' => 'Skrýt/Zobrazit postranní panel', 'page_tags' => 'Štítky stránky', 'chapter_tags' => 'Štítky kapitoly', 'book_tags' => 'Štítky knihy', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Odkazy', 'references_none' => 'Nebyly nalezeny žádné odkazy na tuto položku.', - 'references_to_desc' => 'Níže jsou uvedeny všechny známé stránky systému, které odkazují na tuto položku.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Sledovat', diff --git a/lang/cs/errors.php b/lang/cs/errors.php index 3b38ec00b..5d2cab690 100644 --- a/lang/cs/errors.php +++ b/lang/cs/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'Není nainstalováno rozšíření LDAP pro PHP', 'ldap_cannot_connect' => 'Nelze se připojit k adresáři LDAP. Prvotní připojení selhalo.', 'saml_already_logged_in' => 'Již jste přihlášeni', - 'saml_user_not_registered' => 'Uživatel :name není registrován a automatická registrace je zakázána', 'saml_no_email_address' => 'Nelze najít e-mailovou adresu pro tohoto uživatele v datech poskytnutých externím přihlašovacím systémem', 'saml_invalid_response_id' => 'Požadavek z externího ověřovacího systému nebyl rozpoznám procesem, který tato aplikace spustila. Tento problém může způsobit stisknutí tlačítka Zpět po přihlášení.', 'saml_fail_authed' => 'Přihlášení pomocí :system selhalo, systém neposkytl úspěšnou autorizaci', @@ -44,16 +43,16 @@ return [ 'cannot_get_image_from_url' => 'Nelze získat obrázek z adresy :url', 'cannot_create_thumbs' => 'Server nedokáže udělat náhledy. Zkontrolujte, že rozšíření GD pro PHP je nainstalováno.', 'server_upload_limit' => 'Server nepovoluje nahrávat tak veliké soubory. Zkuste prosím menší soubor.', - 'server_post_limit' => 'The server cannot receive the provided amount of data. Try again with less data or a smaller file.', + 'server_post_limit' => 'Server nemůže přijmout takové množství dat. Zkuste to znovu s méně daty nebo menším souborem.', 'uploaded' => 'Server nepovoluje nahrávat tak veliké soubory. Zkuste prosím menší soubor.', // Drawing & Images 'image_upload_error' => 'Nastala chyba během nahrávání souboru', 'image_upload_type_error' => 'Typ nahrávaného obrázku je neplatný.', 'image_upload_replace_type' => 'Náhrady souboru obrázku musí být stejného typu', - 'image_upload_memory_limit' => 'Failed to handle image upload and/or create thumbnails due to system resource limits.', - 'image_thumbnail_memory_limit' => 'Failed to create image size variations due to system resource limits.', - 'image_gallery_thumbnail_memory_limit' => 'Failed to create gallery thumbnails due to system resource limits.', + 'image_upload_memory_limit' => 'Nepodařilo se zpracovat nahrávaný obrázek anebo vytvořit náhledy z důvodu omezených systémových prostředků.', + 'image_thumbnail_memory_limit' => 'Nepodařilo se vytvořit všechny velikostní varianty obrázku z důvodu omezených systémových prostředků.', + 'image_gallery_thumbnail_memory_limit' => 'Nepodařilo se vytvořit náhledy alba z důvodu omezených systémových prostředků.', 'drawing_data_not_found' => 'Data výkresu nelze načíst. Výkresový soubor již nemusí existovat nebo nemusí mít oprávnění k němu přistupovat.', // Attachments diff --git a/lang/cs/notifications.php b/lang/cs/notifications.php index 00c4030a4..b16eb3429 100644 --- a/lang/cs/notifications.php +++ b/lang/cs/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'Aby se zabránilo hromadnému zasílání upozornění, na nějakou dobu nebudete posílat oznámení o dalších úpravách této stránky stejným editorem.', 'detail_page_name' => 'Název stránky:', + 'detail_page_path' => 'Cesta ke stránce:', 'detail_commenter' => 'Komentoval:', 'detail_comment' => 'Komentář:', 'detail_created_by' => 'Vytvořil:', diff --git a/lang/cs/preferences.php b/lang/cs/preferences.php index 638fe4c63..295fb5ca1 100644 --- a/lang/cs/preferences.php +++ b/lang/cs/preferences.php @@ -5,10 +5,10 @@ */ return [ - 'my_account' => 'My Account', + 'my_account' => 'Můj účet', 'shortcuts' => 'Zkratky', - 'shortcuts_interface' => 'UI Shortcut Preferences', + 'shortcuts_interface' => 'Nastavení klávesových zkratek', 'shortcuts_toggle_desc' => 'Zde můžete povolit nebo zakázat klávesové zkratky systémového rozhraní používané pro navigaci a akce.', 'shortcuts_customize_desc' => 'Po výběru vstupu pro zástupce si můžete přizpůsobit všechny klávesové zkratky.', 'shortcuts_toggle_label' => 'Klávesové zkratky povoleny', @@ -29,23 +29,23 @@ return [ 'notifications_watched' => 'Sledované a ignorované položky', 'notifications_watched_desc' => ' Níže jsou položky, které mají vlastní nastavení hodinek. Chcete-li aktualizovat vaše předvolby, podívejte se na položku a pak najděte možnosti hodinek v postranním panelu.', - 'auth' => 'Access & Security', - 'auth_change_password' => 'Change Password', - 'auth_change_password_desc' => 'Change the password you use to log-in to the application. This must be at least 8 characters long.', - 'auth_change_password_success' => 'Password has been updated!', + 'auth' => 'Přístup a zabezpečení', + 'auth_change_password' => 'Změnit heslo', + 'auth_change_password_desc' => 'Změňte heslo, které používáte pro přihlášení do aplikace. Musí být minimálně 8 znaků dlouhé.', + 'auth_change_password_success' => 'Heslo bylo aktualizováno!', - 'profile' => 'Profile Details', - 'profile_desc' => 'Manage the details of your account which represents you to other users, in addition to details that are used for communication and system personalisation.', - 'profile_view_public' => 'View Public Profile', - 'profile_name_desc' => 'Configure your display name which will be visible to other users in the system through the activity you perform, and content you own.', - 'profile_email_desc' => 'This email will be used for notifications and, depending on active system authentication, system access.', - 'profile_email_no_permission' => 'Unfortunately you don\'t have permission to change your email address. If you want to change this, you\'d need to ask an administrator to change this for you.', - 'profile_avatar_desc' => 'Select an image which will be used to represent yourself to others in the system. Ideally this image should be square and about 256px in width and height.', - 'profile_admin_options' => 'Administrator Options', - 'profile_admin_options_desc' => 'Additional administrator-level options, like those to manage role assignments, can be found for your user account in the "Settings > Users" area of the application.', + 'profile' => 'Podrobnosti profilu', + 'profile_desc' => 'Spravujte údaje k vašemu účtu, které vás reprezentují před ostatními uživateli, kromě údajů, které se používají pro komunikaci a přizpůsobení systému.', + 'profile_view_public' => 'Zobrazit veřejný profil', + 'profile_name_desc' => 'Nastavte vaše zobrazované jméno, které bude viditelné ostatním uživatelům v systému prostřednictvím provedených aktivit a vlastního obsahu.', + 'profile_email_desc' => 'Tento e-mail bude použit pro oznámení a přístup do systému v závislosti na systémové autentizaci.', + 'profile_email_no_permission' => 'Bohužel nemáte oprávnění ke změně své e-mailové adresy. Pokud ji chcete změnit, musíte požádat správce, aby provedl tuto změnu za vás.', + 'profile_avatar_desc' => 'Vyberte obrázek, který bude použit k vaší prezentaci pro ostatní v systému. Ideálně by tento obrázek měl být čtverec o velikosti 256px na šířku a výšku.', + 'profile_admin_options' => 'Možnosti správce', + 'profile_admin_options_desc' => 'Další možnosti na úrovni administrátora. Například ty, které spravují přiřazení rolí lze najít pro váš účet v sekci "Nastavení > Uživatelé".', - 'delete_account' => 'Delete Account', - 'delete_my_account' => 'Delete My Account', - 'delete_my_account_desc' => 'This will fully delete your user account from the system. You will not be able to recover this account or revert this action. Content you\'ve created, such as created pages and uploaded images, will remain.', - 'delete_my_account_warning' => 'Are you sure you want to delete your account?', + 'delete_account' => 'Smazat účet', + 'delete_my_account' => 'Smazat můj účet', + 'delete_my_account_desc' => 'Tímto zcela smažete váš účet ze systému. Nebudete moci tento účet obnovit nebo tuto akci vrátit. Obsah, který jste vytvořili, jako jsou vytvořené stránky a nahrané obrázky, zůstanou zachovány.', + 'delete_my_account_warning' => 'Opravdu si přejete smazat váš účet?', ]; diff --git a/lang/cs/settings.php b/lang/cs/settings.php index 04c290634..9542debe7 100644 --- a/lang/cs/settings.php +++ b/lang/cs/settings.php @@ -9,7 +9,7 @@ return [ // Common Messages 'settings' => 'Nastavení', 'settings_save' => 'Uložit nastavení', - 'system_version' => 'Verze systému: ', + 'system_version' => 'Verze systému', 'categories' => 'Kategorie', // App Settings @@ -193,8 +193,8 @@ return [ 'users_send_invite_text' => 'Uživateli můžete poslat pozvánku e-mailem, která umožní uživateli, aby si zvolil sám svoje heslo do aplikace a nebo můžete zadat heslo sami.', 'users_send_invite_option' => 'Poslat uživateli pozvánku e-mailem', 'users_external_auth_id' => 'Přihlašovací identifikátor třetích stran', - 'users_external_auth_id_desc' => 'When an external authentication system is in use (such as SAML2, OIDC or LDAP) this is the ID which links this BookStack user to the authentication system account. You can ignore this field if using the default email-based authentication.', - 'users_password_warning' => 'Only fill the below if you would like to change the password for this user.', + 'users_external_auth_id_desc' => 'Při použití externího autentizačního systému (např. SAML2, OIDC nebo LDAP) toto je ID, které spojuje tohoto BookStack uživatele s ověřovacím systémem. Toto pole můžete ignorovat, pokud používáte ověření pomocí e-mailu.', + 'users_password_warning' => 'Vyplňte pouze v případě, že chcete změnit heslo pro tohoto uživatele.', 'users_system_public' => 'Symbolizuje každého nepřihlášeného návštěvníka, který navštívil aplikaci. Nelze ho použít k přihlášení ale je přiřazen automaticky nepřihlášeným.', 'users_delete' => 'Odstranit uživatele', 'users_delete_named' => 'Odstranit uživatele :userName', @@ -210,16 +210,16 @@ return [ 'users_preferred_language' => 'Preferovaný jazyk', 'users_preferred_language_desc' => 'Tato volba ovlivní pouze jazyk používaný v uživatelském rozhraní aplikace. Volba nemá vliv na žádný uživateli vytvářený obsah.', 'users_social_accounts' => 'Sociální účty', - 'users_social_accounts_desc' => 'View the status of the connected social accounts for this user. Social accounts can be used in addition to the primary authentication system for system access.', + 'users_social_accounts_desc' => 'Zobrazit stav připojených sociálních účtů tohoto uživatele. Sociální účty mohou být použity pro přístup do systému vedle hlavního systému ověřování.', 'users_social_accounts_info' => 'Zde můžete přidat vaše účty ze sociálních sítí pro pohodlnější přihlašování. Odpojení účtů neznamená, že tato aplikace ztratí práva číst detaily z vašeho účtu. Zakázat této aplikaci přístup k detailům vašeho účtu musíte přímo ve svém profilu na dané sociální síti.', 'users_social_connect' => 'Připojit účet', 'users_social_disconnect' => 'Odpojit účet', - 'users_social_status_connected' => 'Connected', - 'users_social_status_disconnected' => 'Disconnected', + 'users_social_status_connected' => 'Připojeno', + 'users_social_status_disconnected' => 'Odpojeno', 'users_social_connected' => 'Účet :socialAccount byl připojen k vašemu profilu.', 'users_social_disconnected' => 'Účet :socialAccount byl odpojen od vašeho profilu.', 'users_api_tokens' => 'API Tokeny', - 'users_api_tokens_desc' => 'Create and manage the access tokens used to authenticate with the BookStack REST API. Permissions for the API are managed via the user that the token belongs to.', + 'users_api_tokens_desc' => 'Vytvořte a spravujte přístupové tokeny používané pro ověření k REST API aplikace BookStack. Oprávnění pro API jsou spravována prostřednictvím uživatele, kterému token patří.', 'users_api_tokens_none' => 'Tento uživatel nemá vytvořené žádné API Tokeny', 'users_api_tokens_create' => 'Vytvořit Token', 'users_api_tokens_expires' => 'Vyprší', @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/cy/entities.php b/lang/cy/entities.php index cfb5aae1a..f1f915544 100644 --- a/lang/cy/entities.php +++ b/lang/cy/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Updated :timeLength', 'meta_updated_name' => 'Updated :timeLength by :user', 'meta_owned_name' => 'Owned by :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Entity Select', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Images', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Edit Book :bookName', 'books_form_book_name' => 'Book Name', 'books_save' => 'Save Book', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Book Permissions', 'books_permissions_updated' => 'Book Permissions Updated', 'books_empty_contents' => 'No pages or chapters have been created for this book.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Delete Draft Page', 'pages_delete_success' => 'Page deleted', 'pages_delete_draft_success' => 'Draft page deleted', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Are you sure you want to delete this page?', 'pages_delete_draft_confirm' => 'Are you sure you want to delete this draft page?', 'pages_editing_named' => 'Editing Page :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/cy/errors.php b/lang/cy/errors.php index 5ba7edf59..a85f74fd5 100644 --- a/lang/cy/errors.php +++ b/lang/cy/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'Estyniad PHP LDAP heb ei osod', 'ldap_cannot_connect' => 'Methu cysylltu i weinydd ldap, cysylltiad cychwynnol wedi methu', 'saml_already_logged_in' => 'Wedi mewngofnodi yn barod', - 'saml_user_not_registered' => 'Nid yw\'r defnyddiwr :name wedi\'i gofrestru ac mae cofrestriad awtomatig wedi\'i analluogi', 'saml_no_email_address' => 'Methu dod o hyd i gyfeiriad e-bost, ar gyfer y defnyddiwr hwn, yn y data a ddarparwyd gan y system ddilysu allanol', 'saml_invalid_response_id' => 'Nid yw\'r cais o\'r system ddilysu allanol yn cael ei gydnabod gan broses a ddechreuwyd gan y cais hwn. Gallai llywio yn ôl ar ôl mewngofnodi achosi\'r broblem hon.', 'saml_fail_authed' => 'Wedi methu mewngofnodi gan ddefnyddio :system, ni roddodd y system awdurdodiad llwyddiannus', diff --git a/lang/cy/notifications.php b/lang/cy/notifications.php index 5539ae9a9..1afd23f1d 100644 --- a/lang/cy/notifications.php +++ b/lang/cy/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/cy/settings.php b/lang/cy/settings.php index c5ca662c3..03e9bf462 100644 --- a/lang/cy/settings.php +++ b/lang/cy/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/da/entities.php b/lang/da/entities.php index 6453d92ce..88945019c 100644 --- a/lang/da/entities.php +++ b/lang/da/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Opdateret :timeLength', 'meta_updated_name' => 'Opdateret :timeLength af :user', 'meta_owned_name' => 'Ejet af :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Vælg emne', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Billeder', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Rediger bog :bookName', 'books_form_book_name' => 'Bognavn', 'books_save' => 'Gem bog', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Bogtilladelser', 'books_permissions_updated' => 'Bogtilladelser opdateret', 'books_empty_contents' => 'Ingen sider eller kapitler er blevet oprettet i denne bog.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Slet kladdeside', 'pages_delete_success' => 'Side slettet', 'pages_delete_draft_success' => 'Kladdeside slettet', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Er du sikker på, du vil slette denne side?', 'pages_delete_draft_confirm' => 'Er du sikker på at du vil slette denne kladdesidde?', 'pages_editing_named' => 'Redigerer :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/da/errors.php b/lang/da/errors.php index 5c64219c9..162a74763 100644 --- a/lang/da/errors.php +++ b/lang/da/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'LDAP PHP udvidelse er ikke installeret', 'ldap_cannot_connect' => 'Kan ikke forbinde til ldap server. Indledende forbindelse mislykkedes', 'saml_already_logged_in' => 'Allerede logget ind', - 'saml_user_not_registered' => 'Brugeren :name er ikke registreret, og automatisk registrering er slået fra', 'saml_no_email_address' => 'Kunne ikke finde en e-mail-adresse for denne bruger i de data, der leveres af det eksterne godkendelsessystem', 'saml_invalid_response_id' => 'Anmodningen fra det eksterne godkendelsessystem genkendes ikke af en proces, der er startet af denne applikation. Navigering tilbage efter et login kan forårsage dette problem.', 'saml_fail_authed' => 'Login ved hjælp af :system failed, systemet har ikke givet tilladelse', diff --git a/lang/da/notifications.php b/lang/da/notifications.php index 5539ae9a9..1afd23f1d 100644 --- a/lang/da/notifications.php +++ b/lang/da/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/da/settings.php b/lang/da/settings.php index 3cecd1b86..13ef813c6 100644 --- a/lang/da/settings.php +++ b/lang/da/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'Hebraisk', 'hr' => 'Hrvatski', diff --git a/lang/de/entities.php b/lang/de/entities.php index 2d223ef42..21a0d3bef 100644 --- a/lang/de/entities.php +++ b/lang/de/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Zuletzt aktualisiert: :timeLength', 'meta_updated_name' => 'Zuletzt aktualisiert: :timeLength von :user', 'meta_owned_name' => 'Im Besitz von :user', - 'meta_reference_page_count' => 'Verwiesen auf 1 Seite|Verwiesen auf :count Seiten', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Eintrag auswählen', 'entity_select_lack_permission' => 'Sie haben nicht die benötigte Berechtigung, um dieses Element auszuwählen', 'images' => 'Bilder', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Buch ":bookName" bearbeiten', 'books_form_book_name' => 'Name des Buches', 'books_save' => 'Buch speichern', + 'books_default_template' => 'Standard Seitentemplate', + 'books_default_template_explain' => 'Zuweisen einer Seitenvorlage, die als Standardinhalt für alle neuen Seiten in diesem Buch verwendet wird. Beachten Sie, dass dies nur dann verwendet wird, wenn der Ersteller einer Seite Zugriff auf die ausgewählte Template-Seite hat.', + 'books_default_template_select' => 'Template-Seite auswählen', 'books_permissions' => 'Buch-Berechtigungen', 'books_permissions_updated' => 'Buch-Berechtigungen aktualisiert', 'books_empty_contents' => 'Es sind noch keine Seiten oder Kapitel zu diesem Buch hinzugefügt worden.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Seitenentwurf löschen', 'pages_delete_success' => 'Seite gelöscht', 'pages_delete_draft_success' => 'Seitenentwurf gelöscht', + 'pages_delete_warning_template' => 'Diese Seite wird aktiv als Standardvorlage für Seiten in diesem Buch verwendet. Diese Bücher haben nach dem Löschen dieser Seite keine Standardvorlage mehr zugewiesen.', 'pages_delete_confirm' => 'Sind Sie sicher, dass Sie diese Seite löschen möchen?', 'pages_delete_draft_confirm' => 'Sind Sie sicher, dass Sie diesen Seitenentwurf löschen möchten?', 'pages_editing_named' => 'Seite ":pageName" bearbeiten', @@ -212,7 +216,7 @@ return [ 'pages_edit_draft' => 'Seitenentwurf bearbeiten', 'pages_editing_draft' => 'Seitenentwurf bearbeiten', 'pages_editing_page' => 'Seite bearbeiten', - 'pages_edit_draft_save_at' => 'Entwurf gespeichert um ', + 'pages_edit_draft_save_at' => 'Entwurf gesichert um ', 'pages_edit_delete_draft' => 'Entwurf löschen', 'pages_edit_delete_draft_confirm' => 'Sind Sie sicher, dass Sie Ihren Entwurf löschen möchten? Alle Ihre Änderungen seit dem letzten vollständigen Speichern gehen verloren und der Editor wird mit dem letzten Speicherzustand aktualisiert, der kein Entwurf ist.', 'pages_edit_discard_draft' => 'Entwurf verwerfen', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Verweise', 'references_none' => 'Es gibt keine nachverfolgten Referenzen zu diesem Element.', - 'references_to_desc' => 'Nachfolgend sind alle bekannten Seiten im System aufgeführt, die auf diesen Artikel verweisen.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Beobachten', diff --git a/lang/de/errors.php b/lang/de/errors.php index 639373f34..2fd34c00d 100644 --- a/lang/de/errors.php +++ b/lang/de/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'LDAP-PHP-Erweiterung ist nicht installiert.', 'ldap_cannot_connect' => 'Die Verbindung zum LDAP-Server ist fehlgeschlagen. Beim initialen Verbindungsaufbau trat ein Fehler auf.', 'saml_already_logged_in' => 'Sie sind bereits angemeldet', - 'saml_user_not_registered' => 'Es ist kein Benutzer mit ID :name registriert und die automatische Registrierung ist deaktiviert', 'saml_no_email_address' => 'Es konnte keine E-Mail-Adresse für diesen Benutzer in den vom externen Authentifizierungssystem zur Verfügung gestellten Daten gefunden werden', 'saml_invalid_response_id' => 'Die Anfrage vom externen Authentifizierungssystem wird von einem von dieser Anwendung gestarteten Prozess nicht erkannt. Das Zurückgehen nach einer Anmeldung könnte dieses Problem verursachen.', 'saml_fail_authed' => 'Anmeldung mit :system fehlgeschlagen, System konnte keine erfolgreiche Autorisierung bereitstellen', diff --git a/lang/de/notifications.php b/lang/de/notifications.php index 314f0bfe3..f3b86b502 100644 --- a/lang/de/notifications.php +++ b/lang/de/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'Um eine Flut von Benachrichtigungen zu vermeiden, werden Sie für eine gewisse Zeit keine Benachrichtigungen für weitere Bearbeitungen dieser Seite durch denselben Bearbeiter erhalten.', 'detail_page_name' => 'Name der Seite:', + 'detail_page_path' => 'Seitenpfad:', 'detail_commenter' => 'Kommentator:', 'detail_comment' => 'Kommentar:', 'detail_created_by' => 'Erstellt von:', diff --git a/lang/de/settings.php b/lang/de/settings.php index c97a1715c..8769a8253 100644 --- a/lang/de/settings.php +++ b/lang/de/settings.php @@ -297,6 +297,7 @@ Hinweis: Benutzer können ihre E-Mail-Adresse nach erfolgreicher Registrierung 'et' => 'Estnisch', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Französisch', 'he' => 'Hebräisch', 'hr' => 'Kroatisch', diff --git a/lang/de_informal/entities.php b/lang/de_informal/entities.php index 063d51967..2da655c1f 100644 --- a/lang/de_informal/entities.php +++ b/lang/de_informal/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Zuletzt aktualisiert: :timeLength', 'meta_updated_name' => 'Zuletzt aktualisiert: :timeLength von :user', 'meta_owned_name' => 'Im Besitz von :user', - 'meta_reference_page_count' => 'Verwiesen auf 1 Seite|Verwiesen auf :count Seiten', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Eintrag auswählen', 'entity_select_lack_permission' => 'Du hast nicht die benötigte Berechtigung, um dieses Element auszuwählen', 'images' => 'Bilder', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Buch ":bookName" bearbeiten', 'books_form_book_name' => 'Name des Buches', 'books_save' => 'Buch speichern', + 'books_default_template' => 'Standard Seitentemplate', + 'books_default_template_explain' => 'Zuweisen einer Seitenvorlage, die als Standardinhalt für alle neuen Seiten in diesem Buch verwendet wird. Beachte, dass dies nur dann verwendet wird, wenn der Ersteller einer Seite Zugriff auf die ausgewählte Template-Seite hat.', + 'books_default_template_select' => 'Template-Seite auswählen', 'books_permissions' => 'Buch-Berechtigungen', 'books_permissions_updated' => 'Buch-Berechtigungen aktualisiert', 'books_empty_contents' => 'Es sind noch keine Seiten oder Kapitel zu diesem Buch hinzugefügt worden.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Seitenentwurf löschen', 'pages_delete_success' => 'Seite gelöscht', 'pages_delete_draft_success' => 'Seitenentwurf gelöscht', + 'pages_delete_warning_template' => 'Diese Seite wird aktiv als Standardvorlage für Seiten in diesem Buch verwendet. Diese Bücher haben nach dem Löschen dieser Seite keine Standardvorlage mehr zugewiesen.', 'pages_delete_confirm' => 'Bist du sicher, dass du diese Seite löschen möchtest?', 'pages_delete_draft_confirm' => 'Bist du sicher, dass du diesen Seitenentwurf löschen möchtest?', 'pages_editing_named' => 'Seite ":pageName" bearbeiten', @@ -212,7 +216,7 @@ return [ 'pages_edit_draft' => 'Seitenentwurf bearbeiten', 'pages_editing_draft' => 'Seitenentwurf bearbeiten', 'pages_editing_page' => 'Seite bearbeiten', - 'pages_edit_draft_save_at' => 'Entwurf gespeichert um ', + 'pages_edit_draft_save_at' => 'Entwurf gesichert um ', 'pages_edit_delete_draft' => 'Entwurf löschen', 'pages_edit_delete_draft_confirm' => 'Bist du sicher, dass du deinen Entwurf löschen möchtest? Alle deine Änderungen seit dem letzten vollständigen Speichern gehen verloren und der Editor wird mit dem letzten Speicherzustand aktualisiert, der kein Entwurf ist.', 'pages_edit_discard_draft' => 'Entwurf verwerfen', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Verweise', 'references_none' => 'Es gibt keine nachverfolgten Referenzen zu diesem Element.', - 'references_to_desc' => 'Nachfolgend sind alle bekannten Seiten im System aufgeführt, die auf diesen Artikel verweisen.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Beobachten', diff --git a/lang/de_informal/errors.php b/lang/de_informal/errors.php index c91fede4f..ad7a36d23 100644 --- a/lang/de_informal/errors.php +++ b/lang/de_informal/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'LDAP-PHP-Erweiterung ist nicht installiert', 'ldap_cannot_connect' => 'Die Verbindung zum LDAP-Server ist fehlgeschlagen. Beim initialen Verbindungsaufbau trat ein Fehler auf', 'saml_already_logged_in' => 'Du bist bereits angemeldet', - 'saml_user_not_registered' => 'Kein Benutzer mit ID :name registriert und die automatische Registrierung ist deaktiviert', 'saml_no_email_address' => 'Es konnte keine E-Mail-Adresse für diesen Benutzer in den vom externen Authentifizierungssystem zur Verfügung gestellten Daten gefunden werden', 'saml_invalid_response_id' => 'Die Anfrage vom externen Authentifizierungssystem wird von einem von dieser Anwendung gestarteten Prozess nicht erkannt. Das Zurückblättern nach einem Login könnte dieses Problem verursachen.', 'saml_fail_authed' => 'Anmeldung mit :system fehlgeschlagen, System konnte keine erfolgreiche Autorisierung bereitstellen', diff --git a/lang/de_informal/notifications.php b/lang/de_informal/notifications.php index fc6204d50..0bf7739f4 100644 --- a/lang/de_informal/notifications.php +++ b/lang/de_informal/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'Um eine Flut von Benachrichtigungen zu vermeiden, wirst du für eine gewisse Zeit keine Benachrichtigungen für weitere Bearbeitungen dieser Seite durch denselben Bearbeiter erhalten.', 'detail_page_name' => 'Seitenname:', + 'detail_page_path' => 'Seitenpfad:', 'detail_commenter' => 'Kommentator:', 'detail_comment' => 'Kommentar:', 'detail_created_by' => 'Erstellt von:', diff --git a/lang/de_informal/settings.php b/lang/de_informal/settings.php index dc5721ac9..5cd898240 100644 --- a/lang/de_informal/settings.php +++ b/lang/de_informal/settings.php @@ -297,6 +297,7 @@ Hinweis: Benutzer können ihre E-Mail Adresse nach erfolgreicher Registrierung 'et' => 'Estnisch', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Französisch', 'he' => 'עברית', 'hr' => 'Kroatisch', diff --git a/lang/el/entities.php b/lang/el/entities.php index 031844662..8aabdda0a 100644 --- a/lang/el/entities.php +++ b/lang/el/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Ενημερώθηκε :timeLength', 'meta_updated_name' => 'Ενημερώθηκε :timeLength από :user', 'meta_owned_name' => 'Ανήκει στον :user', - 'meta_reference_page_count' => 'Αναφορά σε :count σελίδας|Αναφορά στο :count σελίδες', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Επιλογή Οντότητας', 'entity_select_lack_permission' => 'Δεν έχετε τα απαιτούμενα δικαιώματα για να επιλέξετε αυτό το στοιχείο', 'images' => 'Εικόνες', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Επεξεργασία Βιβλίου :bookname', 'books_form_book_name' => 'Όνομα Βιβλίου', 'books_save' => 'Αποθήκευση Βιβλίου', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Άδειες Βιβλίου', 'books_permissions_updated' => 'Τα Δικαιώματα Βιβλίου Ενημερώθηκαν', 'books_empty_contents' => 'Δεν έχουν δημιουργηθεί σελίδες ή κεφάλαια για αυτό το βιβλίο.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Διαγραφή Προσχέδιας Σελίδας', 'pages_delete_success' => 'Η σελίδα διαγράφηκε', 'pages_delete_draft_success' => 'Η προσχέδια (πρόχειρη) σελίδα διαγράφηκε', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτή τη σελίδα;', 'pages_delete_draft_confirm' => 'Θέλετε σίγουρα να διαγράψετε την προσχέδια σελίδα;', 'pages_editing_named' => 'Επεξεργασία Σελίδας :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Αναφορές', 'references_none' => 'Δεν υπάρχουν αναφορές παρακολούθησης σε αυτό το στοιχείο.', - 'references_to_desc' => 'Παρακάτω εμφανίζονται όλες οι γνωστές σελίδες του συστήματος που συνδέονται με αυτό το στοιχείο.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/el/errors.php b/lang/el/errors.php index e42d0a855..efb20b1b6 100644 --- a/lang/el/errors.php +++ b/lang/el/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'Η επέκταση LDAP PHP δεν εγκαταστάθηκε', 'ldap_cannot_connect' => 'Αδυναμία σύνδεσης στο διακομιστή ldap, η αρχική σύνδεση απέτυχε', 'saml_already_logged_in' => 'Ήδη συνδεδεμένος', - 'saml_user_not_registered' => 'Ο χρήστης :name δεν είναι εγγεγραμμένος και η αυτόματη εγγραφή είναι απενεργοποιημένη', 'saml_no_email_address' => 'Δεν ήταν δυνατή η εύρεση μιας διεύθυνσης ηλεκτρονικού ταχυδρομείου, για αυτόν τον χρήστη, στα δεδομένα που παρέχονται από το εξωτερικό σύστημα ελέγχου ταυτότητας', 'saml_invalid_response_id' => 'Το αίτημα από το εξωτερικό σύστημα ελέγχου ταυτότητας δεν αναγνωρίζεται από μια διαδικασία που ξεκίνησε από αυτή την εφαρμογή. Η πλοήγηση πίσω μετά από μια σύνδεση θα μπορούσε να προκαλέσει αυτό το ζήτημα.', 'saml_fail_authed' => 'Η σύνδεση με τη χρήση :system απέτυχε, το σύστημα δεν παρείχε επιτυχή εξουσιοδότηση', diff --git a/lang/el/notifications.php b/lang/el/notifications.php index 5539ae9a9..1afd23f1d 100644 --- a/lang/el/notifications.php +++ b/lang/el/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/el/settings.php b/lang/el/settings.php index f9c199fc3..0c3497e64 100644 --- a/lang/el/settings.php +++ b/lang/el/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/es/entities.php b/lang/es/entities.php index ff2b8b8d5..82cf3e692 100644 --- a/lang/es/entities.php +++ b/lang/es/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Actualizado :timeLength', 'meta_updated_name' => 'Actualizado :timeLength por :user', 'meta_owned_name' => 'Propiedad de :user', - 'meta_reference_page_count' => 'Referido en :count página | Referido en :count paginas', + 'meta_reference_count' => 'Referido en :count página | Referido en :count paginas', 'entity_select' => 'Seleccione entidad', 'entity_select_lack_permission' => 'No tiene los permisos necesarios para seleccionar este elemento', 'images' => 'Imágenes', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Editar Libro :bookName', 'books_form_book_name' => 'Nombre de libro', 'books_save' => 'Guardar libro', + 'books_default_template' => 'Plantilla de página por defecto', + 'books_default_template_explain' => 'Asigne una plantilla de página que se utilizará como contenido predeterminado para todas las páginas nuevas de este libro. Tenga en cuenta que esto sólo se utilizará si el creador de páginas tiene acceso a la página elegida.', + 'books_default_template_select' => 'Seleccione una página de plantilla', 'books_permissions' => 'Permisos del libro', 'books_permissions_updated' => 'Permisos del libro actualizados', 'books_empty_contents' => 'Ninguna página o capítulo ha sido creada para este libro.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Borrar borrador de página', 'pages_delete_success' => 'Página borrada', 'pages_delete_draft_success' => 'Borrador de página borrado', + 'pages_delete_warning_template' => 'Esta página está en uso como plantilla de página predeterminada del libro. Estos libros ya no tendrán una plantilla de página predeterminada asignada después de eliminar esta página.', 'pages_delete_confirm' => '¿Está seguro de borrar esta página?', 'pages_delete_draft_confirm' => '¿Está seguro de que desea borrar este borrador de página?', 'pages_editing_named' => 'Editando página :pageName', diff --git a/lang/es/errors.php b/lang/es/errors.php index f32aa1cec..9fb7afba4 100644 --- a/lang/es/errors.php +++ b/lang/es/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'La extensión LDAP PHP no se encuentra instalada', 'ldap_cannot_connect' => 'No se puede conectar con el servidor ldap, la conexión inicial ha fallado', 'saml_already_logged_in' => 'Ya estás conectado', - 'saml_user_not_registered' => 'El usuario :name no está registrado y el registro automático está deshabilitado', 'saml_no_email_address' => 'No se pudo encontrar una dirección de correo electrónico, para este usuario, en los datos proporcionados por el sistema de autenticación externo', 'saml_invalid_response_id' => 'La solicitud del sistema de autenticación externo no está reconocida por un proceso iniciado por esta aplicación. Navegar hacia atrás después de un inicio de sesión podría causar este problema.', 'saml_fail_authed' => 'El inicio de sesión con :system falló, el sistema no proporcionó una autorización correcta', diff --git a/lang/es/notifications.php b/lang/es/notifications.php index 4e76a5924..5ebc42129 100644 --- a/lang/es/notifications.php +++ b/lang/es/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'Para prevenir notificaciones en masa, durante un tiempo no se enviarán notificaciones para futuras ediciones de esta página por el mismo editor.', 'detail_page_name' => 'Nombre de página:', + 'detail_page_path' => 'Ruta de la página:', 'detail_commenter' => 'Autor del comentario:', 'detail_comment' => 'Comentario:', 'detail_created_by' => 'Creado por:', diff --git a/lang/es/settings.php b/lang/es/settings.php index 221a96e1c..0d0cf221e 100644 --- a/lang/es/settings.php +++ b/lang/es/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Francés', 'he' => 'עברית', 'hr' => 'Croata', diff --git a/lang/es_AR/entities.php b/lang/es_AR/entities.php index 32c1b24b4..3afb54bcd 100644 --- a/lang/es_AR/entities.php +++ b/lang/es_AR/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Actualizado el :timeLength', 'meta_updated_name' => 'Actualizado el :timeLength por :user', 'meta_owned_name' => 'Propiedad de :user', - 'meta_reference_page_count' => 'Referido en :count página | Referido en :count paginas', + 'meta_reference_count' => 'Referido en :count página | Referido en :count paginas', 'entity_select' => 'Seleccione entidad', 'entity_select_lack_permission' => 'No tiene los permisos necesarios para seleccionar este elemento', 'images' => 'Imágenes', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Editar Libro :bookName', 'books_form_book_name' => 'Nombre de libro', 'books_save' => 'Guardar libro', + 'books_default_template' => 'Plantilla de página por defecto', + 'books_default_template_explain' => 'Asigne una plantilla de página que se utilizará como contenido predeterminado para todas las páginas nuevas de este libro. Tenga en cuenta que esto sólo se utilizará si el creador de páginas tiene acceso a la página elegida.', + 'books_default_template_select' => 'Seleccione una página de plantilla', 'books_permissions' => 'permisos de libro', 'books_permissions_updated' => 'Permisos de libro actualizados', 'books_empty_contents' => 'Ninguna página o capítulo ha sido creada para este libro.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Borrar borrador de página', 'pages_delete_success' => 'Página borrada', 'pages_delete_draft_success' => 'Borrador de página borrado', + 'pages_delete_warning_template' => 'Esta página está en uso como plantilla de página predeterminada del libro. Estos libros ya no tendrán una plantilla de página predeterminada asignada después de eliminar esta página.', 'pages_delete_confirm' => '¿Está seguro de borrar esta página?', 'pages_delete_draft_confirm' => 'Está seguro de que desea borrar este borrador de página?', 'pages_editing_named' => 'Editando página :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Referencias', 'references_none' => 'No hay referencias a este elemento.', - 'references_to_desc' => 'A continuación se muestran todas las páginas en el sistema que tienen un enlace a este elemento.', + 'references_to_desc' => 'A continuación se muestran todas las páginas en el sistema que enlazan a este elemento.', // Watch Options 'watch' => 'Suscribirme', diff --git a/lang/es_AR/errors.php b/lang/es_AR/errors.php index d5e08220a..e784b5092 100644 --- a/lang/es_AR/errors.php +++ b/lang/es_AR/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'La extensión LDAP PHP no se encuentra instalada', 'ldap_cannot_connect' => 'No se puede conectar con el servidor ldap, la conexión inicial ha fallado', 'saml_already_logged_in' => 'Ya estás conectado', - 'saml_user_not_registered' => 'El usuario :name no está registrado y el registro automático está deshabilitado', 'saml_no_email_address' => 'No se pudo encontrar una dirección de correo electrónico, para este usuario, en los datos proporcionados por el sistema de autenticación externo', 'saml_invalid_response_id' => 'La solicitud del sistema de autenticación externo no está reconocida por un proceso iniciado por esta aplicación. Navegar hacia atrás después de un inicio de sesión podría causar este problema.', 'saml_fail_authed' => 'El inicio de sesión con :system falló, el sistema no proporcionó una autorización correcta', diff --git a/lang/es_AR/notifications.php b/lang/es_AR/notifications.php index 4e76a5924..5ebc42129 100644 --- a/lang/es_AR/notifications.php +++ b/lang/es_AR/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'Para prevenir notificaciones en masa, durante un tiempo no se enviarán notificaciones para futuras ediciones de esta página por el mismo editor.', 'detail_page_name' => 'Nombre de página:', + 'detail_page_path' => 'Ruta de la página:', 'detail_commenter' => 'Autor del comentario:', 'detail_comment' => 'Comentario:', 'detail_created_by' => 'Creado por:', diff --git a/lang/es_AR/settings.php b/lang/es_AR/settings.php index 50d2e7c70..7c1cba813 100644 --- a/lang/es_AR/settings.php +++ b/lang/es_AR/settings.php @@ -297,6 +297,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Francés', 'he' => 'עברית', 'hr' => 'Croata', diff --git a/lang/et/entities.php b/lang/et/entities.php index 8e6a84e10..cd9606c63 100644 --- a/lang/et/entities.php +++ b/lang/et/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Muudetud :timeLength', 'meta_updated_name' => 'Muudetud :timeLength kasutaja :user poolt', 'meta_owned_name' => 'Kuulub kasutajale :user', - 'meta_reference_page_count' => 'Viidatud :count lehel|Viidatud :count lehel', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Objekti valik', 'entity_select_lack_permission' => 'Sul pole õiguseid selle objekti valimiseks', 'images' => 'Pildid', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Muuda raamatut :bookName', 'books_form_book_name' => 'Raamatu pealkiri', 'books_save' => 'Salvesta raamat', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Raamatu õigused', 'books_permissions_updated' => 'Raamatu õigused muudetud', 'books_empty_contents' => 'Ühtegi lehte ega peatükki pole lisatud.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Kustuta mustand', 'pages_delete_success' => 'Leht kustutatud', 'pages_delete_draft_success' => 'Mustand kustutatud', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Kas oled kindel, et soovid selle lehe kustutada?', 'pages_delete_draft_confirm' => 'Kas oled kindel, et soovid selle mustandi kustutada?', 'pages_editing_named' => 'Lehe :pageName muutmine', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Viited', 'references_none' => 'Sellele objektile ei ole viiteid.', - 'references_to_desc' => 'Allpool on kõik teadaolevad lehed, mis sellele objektile viitavad.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Jälgi', diff --git a/lang/et/errors.php b/lang/et/errors.php index 3145a0b29..56e8c9cd6 100644 --- a/lang/et/errors.php +++ b/lang/et/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'PHP LDAP laiendus ei ole paigaldatud', 'ldap_cannot_connect' => 'Ühendus LDAP serveriga ebaõnnestus', 'saml_already_logged_in' => 'Juba sisse logitud', - 'saml_user_not_registered' => 'Kasutaja :name ei ole registreeritud ning automaatne registreerimine on keelatud', 'saml_no_email_address' => 'Selle kasutaja e-posti aadressi ei õnnestunud välisest autentimissüsteemist leida', 'saml_invalid_response_id' => 'Välisest autentimissüsteemist tulnud päringut ei algatatud sellest rakendusest. Seda viga võib põhjustada pärast sisselogimist tagasi liikumine.', 'saml_fail_authed' => 'Sisenemine :system kaudu ebaõnnestus, süsteem ei andnud volitust', diff --git a/lang/et/notifications.php b/lang/et/notifications.php index 6a2c2d8bf..0b5fc814c 100644 --- a/lang/et/notifications.php +++ b/lang/et/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'Et vältida liigseid teavitusi, ei saadeta sulle mõnda aega teavitusi selle lehe muutmiste kohta sama kasutaja poolt.', 'detail_page_name' => 'Lehe nimetus:', + 'detail_page_path' => 'Lehe asukoht:', 'detail_commenter' => 'Kommenteerija:', 'detail_comment' => 'Kommentaar:', 'detail_created_by' => 'Autor:', diff --git a/lang/et/preferences.php b/lang/et/preferences.php index ee3b9e5ea..9ac77a965 100644 --- a/lang/et/preferences.php +++ b/lang/et/preferences.php @@ -35,7 +35,7 @@ return [ 'auth_change_password_success' => 'Parool on muudetud!', 'profile' => 'Profiili detailid', - 'profile_desc' => 'Manage the details of your account which represents you to other users, in addition to details that are used for communication and system personalisation.', + 'profile_desc' => 'Halda oma konto andmeid, mis sind teistele kasutajatele esindab, lisaks andmetele, mida kasutatakse suhtluseks ja süsteemi kohaldamiseks.', 'profile_view_public' => 'Vaata avalikku profiili', 'profile_name_desc' => 'Seadista oma avalik nimi, mis on nähtav teistele kasutajatele sinu tehtud tegevuste ja sulle kuuluva sisu kaudu.', 'profile_email_desc' => 'Seda e-posti aadressi kasutatakse teavituste saatmiseks ning, sõltuvalt aktiivsest autentimismeetodist, süsteemile ligipääsemiseks.', diff --git a/lang/et/settings.php b/lang/et/settings.php index b4a5ca174..01822ed81 100644 --- a/lang/et/settings.php +++ b/lang/et/settings.php @@ -193,8 +193,8 @@ return [ 'users_send_invite_text' => 'Sa võid kasutajale saata e-postiga kutse, mis võimaldab neil ise parooli seada. Vastasel juhul määra parool ise.', 'users_send_invite_option' => 'Saada e-postiga kutse', 'users_external_auth_id' => 'Välise autentimise ID', - 'users_external_auth_id_desc' => 'When an external authentication system is in use (such as SAML2, OIDC or LDAP) this is the ID which links this BookStack user to the authentication system account. You can ignore this field if using the default email-based authentication.', - 'users_password_warning' => 'Only fill the below if you would like to change the password for this user.', + 'users_external_auth_id_desc' => 'Kui kasutusel on väline autentimissüsteem (nagu SAML2, OIDC või LDAP), siis see ID ühendab selle BookStack kasutaja autentimissüsteemi kasutajakontoga. Kui kasutad vaikimisi e-posti põhist autentimist, võid seda välja ignoreerida.', + 'users_password_warning' => 'Täida allolevad väljad ainult siis, kui soovid selle kasutaja parooli muuta.', 'users_system_public' => 'See kasutaja tähistab kõiki külalisi, kes su rakendust vaatavad. Selle kontoga ei saa sisse logida, see määratakse automaatselt.', 'users_delete' => 'Kustuta kasutaja', 'users_delete_named' => 'Kustuta kasutaja :userName', @@ -210,16 +210,16 @@ return [ 'users_preferred_language' => 'Eelistatud keel', 'users_preferred_language_desc' => 'See valik muudab rakenduse kasutajaliidese keelt. Kasutajate loodud sisu see ei mõjuta.', 'users_social_accounts' => 'Sotsiaalmeedia kontod', - 'users_social_accounts_desc' => 'View the status of the connected social accounts for this user. Social accounts can be used in addition to the primary authentication system for system access.', + 'users_social_accounts_desc' => 'Vaata selle kasutaja ühendatud sotsiaalmeedia kontode seisundit. Sotsiaalmeedia kontosid saab kasutada süsteemile ligipääsemiseks, lisaks primaarsele autentimissüsteemile.', 'users_social_accounts_info' => 'Siin saad seostada teised kontod, millega kiiremini ja lihtsamini sisse logida. Siit konto eemaldamine ei tühista varem lubatud ligipääsu. Ligipääsu saad tühistada ühendatud konto profiili seadetest.', 'users_social_connect' => 'Lisa konto', 'users_social_disconnect' => 'Eemalda konto', - 'users_social_status_connected' => 'Connected', - 'users_social_status_disconnected' => 'Disconnected', + 'users_social_status_connected' => 'Ühendatud', + 'users_social_status_disconnected' => 'Ühendus katkestatud', 'users_social_connected' => ':socialAccount konto lisati su profiilile.', 'users_social_disconnected' => ':socialAccount konto eemaldati su profiililt.', 'users_api_tokens' => 'API tunnused', - 'users_api_tokens_desc' => 'Create and manage the access tokens used to authenticate with the BookStack REST API. Permissions for the API are managed via the user that the token belongs to.', + 'users_api_tokens_desc' => 'Lisa ja halda BookStack REST API-ga autentimiseks mõeldud ligipääsutunnuseid. API kasutamise õigused on määratud ksautaja kaudu, kellele ligipääsutunnus kuulub.', 'users_api_tokens_none' => 'Sellel kasutajal pole API tunnuseid', 'users_api_tokens_create' => 'Lisa tunnus', 'users_api_tokens_expires' => 'Aegub', @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français (prantsuse keel)', 'he' => 'עברית (heebrea keel)', 'hr' => 'Hrvatski (horvaadi keel)', diff --git a/lang/eu/entities.php b/lang/eu/entities.php index bc31d372e..d3582283f 100644 --- a/lang/eu/entities.php +++ b/lang/eu/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Aldatua :timeLength', 'meta_updated_name' => ':timeLength aldatuta. Erabiltzailea :user', 'meta_owned_name' => ':user da jabea', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Aukeratutako entitatea', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Irudiak', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Editatu :bookName liburua', 'books_form_book_name' => 'Liburu izena', 'books_save' => 'Gorde Liburua', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Liburu baimenak', 'books_permissions_updated' => 'Liburu baimenak eguneratuta', 'books_empty_contents' => 'Ez da orri edo kapitulurik sortu liburu honentzat.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Delete Draft Page', 'pages_delete_success' => 'Orria ezabatua', 'pages_delete_draft_success' => 'Draft page deleted', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Ziur al zaude orri hau ezabatu nahi duzula?', 'pages_delete_draft_confirm' => 'Are you sure you want to delete this draft page?', 'pages_editing_named' => 'Editing Page :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/eu/errors.php b/lang/eu/errors.php index 1028d3802..2244e0142 100644 --- a/lang/eu/errors.php +++ b/lang/eu/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'PHP LDAP extentsioa ez dago instalatuta', 'ldap_cannot_connect' => 'Ezin izan da ldap zerbitzarira konektatu, hasierako konexioak huts egin du', 'saml_already_logged_in' => 'Saioa aurretik hasita dago', - 'saml_user_not_registered' => ':name erabiltzailea ez dago erregistratua eta erregistro automatikoa ezgaituta dago', 'saml_no_email_address' => 'Ezin izan dugu posta helbiderik aurkitu erabiltzaile honentzat, kanpoko autentifikazio zerbitzuak bidalitako datuetan', 'saml_invalid_response_id' => 'Kanpoko egiazkotasun-sistemaren eskaria ez du onartzen aplikazio honek abiarazitako prozesu batek. Loginean atzera egitea izan daiteke arrazoia.', 'saml_fail_authed' => 'Login using :system failed, system did not provide successful authorization', diff --git a/lang/eu/notifications.php b/lang/eu/notifications.php index 5539ae9a9..1afd23f1d 100644 --- a/lang/eu/notifications.php +++ b/lang/eu/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/eu/settings.php b/lang/eu/settings.php index a9aa41dba..0e6fa2444 100644 --- a/lang/eu/settings.php +++ b/lang/eu/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/fa/entities.php b/lang/fa/entities.php index 900a91b92..bbdbe5b6a 100644 --- a/lang/fa/entities.php +++ b/lang/fa/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'به روزرسانی شده :timeLength', 'meta_updated_name' => 'به روزرسانی شده :timeLength توسط :user', 'meta_owned_name' => 'متعلق به :user', - 'meta_reference_page_count' => 'در 1 صفحه به آن ارجاع داده شده|در :count صفحه به آن ارجاع داده شده', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'انتخاب موجودیت', 'entity_select_lack_permission' => 'شما مجوزهای لازم برای انتخاب این مورد را ندارید', 'images' => 'عکس‌ها', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'ویرایش کتاب:bookName', 'books_form_book_name' => 'نام کتاب', 'books_save' => 'ذخیره کتاب', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'مجوزهای کتاب', 'books_permissions_updated' => 'مجوزهای کتاب به روز شد', 'books_empty_contents' => 'هیچ صفحه یا فصلی برای این کتاب ایجاد نشده است.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'حذف صفحه پیش نویس', 'pages_delete_success' => 'صفحه حذف شد', 'pages_delete_draft_success' => 'صفحه پیش نویس حذف شد', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'آیا مطمئن هستید که می خواهید این صفحه را حذف کنید؟', 'pages_delete_draft_confirm' => 'آیا مطمئن هستید که می خواهید این صفحه پیش نویس را حذف کنید؟', 'pages_editing_named' => 'ویرایش صفحه :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'مراجع', 'references_none' => 'هیچ رفرنسی برای این قلم یافت نشد.', - 'references_to_desc' => 'در زیر تمام صفحات شناخته شده در سیستم که به این مورد پیوند دارند، نشان داده شده است.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'نظارت', diff --git a/lang/fa/errors.php b/lang/fa/errors.php index 2dc4b18e9..6632246da 100644 --- a/lang/fa/errors.php +++ b/lang/fa/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'افزونه PHP LDAP نصب نشده است', 'ldap_cannot_connect' => 'اتصال به سرور LDAP امکان پذیر نیست، اتصال اولیه برقرار نشد', 'saml_already_logged_in' => 'قبلا وارد سیستم شده اید', - 'saml_user_not_registered' => 'کاربر :name ثبت نشده است و ثبت نام خودکار غیرفعال است', 'saml_no_email_address' => 'آدرس داده ای برای این کاربر در داده های ارائه شده توسط سیستم احراز هویت خارجی یافت نشد', 'saml_invalid_response_id' => 'درخواست از سیستم احراز هویت خارجی توسط فرایندی که توسط این نرم افزار آغاز شده است شناخته نمی شود. بازگشت به سیستم پس از ورود به سیستم می تواند باعث این مسئله شود.', 'saml_fail_authed' => 'ورود به سیستم :system انجام نشد، سیستم مجوز موفقیت آمیز ارائه نکرد', diff --git a/lang/fa/notifications.php b/lang/fa/notifications.php index 573c75113..675eaa762 100644 --- a/lang/fa/notifications.php +++ b/lang/fa/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'برای جلوگیری از انبوه اعلان‌ها، برای مدتی اعلان‌ ویرایش‌هایی که توسط همان ویرایشگر در این صفحه انجام می‌شود، ارسال نخواهد شد.', 'detail_page_name' => 'نام صفحه:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'نظر دهنده:', 'detail_comment' => 'نظر:', 'detail_created_by' => 'ایجاد شده توسط:', diff --git a/lang/fa/settings.php b/lang/fa/settings.php index 1b3b04eb5..0659cde46 100644 --- a/lang/fa/settings.php +++ b/lang/fa/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/fi/activities.php b/lang/fi/activities.php index d5b55c03d..6bb267c70 100644 --- a/lang/fi/activities.php +++ b/lang/fi/activities.php @@ -6,119 +6,119 @@ return [ // Pages - 'page_create' => 'created page', - 'page_create_notification' => 'Page successfully created', - 'page_update' => 'updated page', - 'page_update_notification' => 'Page successfully updated', - 'page_delete' => 'deleted page', - 'page_delete_notification' => 'Page successfully deleted', - 'page_restore' => 'restored page', - 'page_restore_notification' => 'Page successfully restored', - 'page_move' => 'moved page', - 'page_move_notification' => 'Page successfully moved', + 'page_create' => 'loi sivun', + 'page_create_notification' => 'Sivu luotiin onnistuneesti', + 'page_update' => 'päivitti sivun', + 'page_update_notification' => 'Sivu päivitettiin onnistuneesti', + 'page_delete' => 'poisti sivun', + 'page_delete_notification' => 'Sivu poistettiin onnistuneesti', + 'page_restore' => 'palautti sivun', + 'page_restore_notification' => 'Sivu palautettiin onnistuneesti', + 'page_move' => 'siirsi sivun', + 'page_move_notification' => 'Sivu siirrettiin onnistuneesti', // Chapters - 'chapter_create' => 'created chapter', - 'chapter_create_notification' => 'Chapter successfully created', - 'chapter_update' => 'updated chapter', - 'chapter_update_notification' => 'Chapter successfully updated', - 'chapter_delete' => 'deleted chapter', - 'chapter_delete_notification' => 'Chapter successfully deleted', - 'chapter_move' => 'moved chapter', - 'chapter_move_notification' => 'Chapter successfully moved', + 'chapter_create' => 'loi luvun', + 'chapter_create_notification' => 'Luku luotiin onnistuneesti', + 'chapter_update' => 'päivitti luvun', + 'chapter_update_notification' => 'Luku päivitettiin onnistuneesti', + 'chapter_delete' => 'poisti luvun', + 'chapter_delete_notification' => 'Sivu poistettiin onnistuneesti', + 'chapter_move' => 'siirsi luvun', + 'chapter_move_notification' => 'Sivu siirrettiin onnistuneesti', // Books - 'book_create' => 'created book', - 'book_create_notification' => 'Book successfully created', - 'book_create_from_chapter' => 'converted chapter to book', - 'book_create_from_chapter_notification' => 'Chapter successfully converted to a book', - 'book_update' => 'updated book', - 'book_update_notification' => 'Book successfully updated', - 'book_delete' => 'deleted book', - 'book_delete_notification' => 'Book successfully deleted', - 'book_sort' => 'sorted book', - 'book_sort_notification' => 'Book successfully re-sorted', + 'book_create' => 'loi kirjan', + 'book_create_notification' => 'Kirja luotiin onnistuneesti', + 'book_create_from_chapter' => 'muunsi luvun kirjaksi', + 'book_create_from_chapter_notification' => 'Luku muunnettiin onnistuneesti kirjaksi', + 'book_update' => 'päivitti kirjan', + 'book_update_notification' => 'Kirja päivitettiin onnistuneesti', + 'book_delete' => 'poisti kirjan', + 'book_delete_notification' => 'Kirja poistettiin onnistuneesti', + 'book_sort' => 'järjesti kirjan', + 'book_sort_notification' => 'Kirja järjestettiin uudelleen onnistuneesti', // Bookshelves - 'bookshelf_create' => 'created shelf', - 'bookshelf_create_notification' => 'Shelf successfully created', - 'bookshelf_create_from_book' => 'converted book to shelf', - 'bookshelf_create_from_book_notification' => 'Book successfully converted to a shelf', - 'bookshelf_update' => 'updated shelf', - 'bookshelf_update_notification' => 'Shelf successfully updated', - 'bookshelf_delete' => 'deleted shelf', - 'bookshelf_delete_notification' => 'Shelf successfully deleted', + 'bookshelf_create' => 'loi hyllyn', + 'bookshelf_create_notification' => 'Hylly luotiin onnistuneesti', + 'bookshelf_create_from_book' => 'muunsi kirjan hyllyksi', + 'bookshelf_create_from_book_notification' => 'Kirja muunnettiin onnistuneesti hyllyksi', + 'bookshelf_update' => 'päivitti hyllyn', + 'bookshelf_update_notification' => 'Hylly päivitettiin onnistuneesti', + 'bookshelf_delete' => 'poisti hyllyn', + 'bookshelf_delete_notification' => 'Hylly poistettiin onnistuneesti', // Revisions - 'revision_restore' => 'restored revision', - 'revision_delete' => 'deleted revision', - 'revision_delete_notification' => 'Revision successfully deleted', + 'revision_restore' => 'palautti version', + 'revision_delete' => 'poisti version', + 'revision_delete_notification' => 'Versio poistettiin onnistuneesti', // Favourites - 'favourite_add_notification' => '":name" has been added to your favourites', - 'favourite_remove_notification' => '":name" has been removed from your favourites', + 'favourite_add_notification' => '":name" on lisätty suosikkeihisi', + 'favourite_remove_notification' => '":name" on poistettu suosikeistasi', // Watching - 'watch_update_level_notification' => 'Watch preferences successfully updated', + 'watch_update_level_notification' => 'Seurannan asetukset päivitetty onnistuneesti', // Auth - 'auth_login' => 'logged in', - 'auth_register' => 'registered as new user', - 'auth_password_reset_request' => 'requested user password reset', - 'auth_password_reset_update' => 'reset user password', - 'mfa_setup_method' => 'configured MFA method', - 'mfa_setup_method_notification' => 'Multi-factor method successfully configured', - 'mfa_remove_method' => 'removed MFA method', - 'mfa_remove_method_notification' => 'Multi-factor method successfully removed', + 'auth_login' => 'kirjautui sisään', + 'auth_register' => 'rekisteröityi uudeksi käyttäjäksi', + 'auth_password_reset_request' => 'pyysi käyttäjän salasanan palautusta', + 'auth_password_reset_update' => 'palautti käyttäjän salasana', + 'mfa_setup_method' => 'määritti monivaiheisen tunnistaumisen menetelmän', + 'mfa_setup_method_notification' => 'Monivaiheisen tunnistautumisen menetelmän määrittäminen onnistui', + 'mfa_remove_method' => 'poisti monivaiheisen tunnistautumisen menetelmän', + 'mfa_remove_method_notification' => 'Monivaiheisen tunnistautumisen menetelmä poistettiin onnistuneesti', // Settings - 'settings_update' => 'updated settings', - 'settings_update_notification' => 'Settings successfully updated', - 'maintenance_action_run' => 'ran maintenance action', + 'settings_update' => 'päivitti asetukset', + 'settings_update_notification' => 'Asetukset päivitettiin onnistuneesti', + 'maintenance_action_run' => 'suoritti huoltotoimenpiteen', // Webhooks - 'webhook_create' => 'created webhook', - 'webhook_create_notification' => 'Webhook successfully created', - 'webhook_update' => 'updated webhook', - 'webhook_update_notification' => 'Webhook successfully updated', - 'webhook_delete' => 'deleted webhook', - 'webhook_delete_notification' => 'Webhook successfully deleted', + 'webhook_create' => 'loi toimintokutsun', + 'webhook_create_notification' => 'Toimintokutsu luotiin onnistuneesti', + 'webhook_update' => 'päivitti toimintokutsun', + 'webhook_update_notification' => 'Toimintokutsu päivitettiin onnistuneesti', + 'webhook_delete' => 'poisti toimintokutsun', + 'webhook_delete_notification' => 'Toimintokutsu poistettiin onnistuneesti', // Users - 'user_create' => 'created user', - 'user_create_notification' => 'User successfully created', - 'user_update' => 'updated user', - 'user_update_notification' => 'User successfully updated', - 'user_delete' => 'deleted user', - 'user_delete_notification' => 'User successfully removed', + 'user_create' => 'loi käyttäjän', + 'user_create_notification' => 'Käyttäjä luotiin onnistuneesti', + 'user_update' => 'päivitti käyttäjän', + 'user_update_notification' => 'Käyttäjä päivitettiin onnistuneesti', + 'user_delete' => 'poisti käyttäjän', + 'user_delete_notification' => 'Käyttäjä poistettiin onnistuneesti', // API Tokens - 'api_token_create' => 'created api token', - 'api_token_create_notification' => 'API token successfully created', - 'api_token_update' => 'updated api token', - 'api_token_update_notification' => 'API token successfully updated', - 'api_token_delete' => 'deleted api token', - 'api_token_delete_notification' => 'API token successfully deleted', + 'api_token_create' => 'loi API-tunnisteen', + 'api_token_create_notification' => 'API-tunniste luotiin onnistuneesti', + 'api_token_update' => 'päivitti API-tunnisteen', + 'api_token_update_notification' => 'API-tunniste päivitettiin onnistuneesti', + 'api_token_delete' => 'poisti API-tunnisteen', + 'api_token_delete_notification' => 'API-tunniste poistettiin onnistuneesti', // Roles - 'role_create' => 'created role', - 'role_create_notification' => 'Role successfully created', - 'role_update' => 'updated role', - 'role_update_notification' => 'Role successfully updated', - 'role_delete' => 'deleted role', - 'role_delete_notification' => 'Role successfully deleted', + 'role_create' => 'loi roolin', + 'role_create_notification' => 'Rooli luotiin onnistuneesti', + 'role_update' => 'päivitti roolin', + 'role_update_notification' => 'Rooli päivitettiin onnistuneesti', + 'role_delete' => 'poisti roolin', + 'role_delete_notification' => 'Rooli poistettiin onnistuneesti', // Recycle Bin - 'recycle_bin_empty' => 'emptied recycle bin', - 'recycle_bin_restore' => 'restored from recycle bin', - 'recycle_bin_destroy' => 'removed from recycle bin', + 'recycle_bin_empty' => 'tyhjensi roskakorin', + 'recycle_bin_restore' => 'palautti roskakorista', + 'recycle_bin_destroy' => 'poisti roskakorista', // Comments - 'commented_on' => 'commented on', - 'comment_create' => 'added comment', - 'comment_update' => 'updated comment', - 'comment_delete' => 'deleted comment', + 'commented_on' => 'kommentoi', + 'comment_create' => 'lisäsi kommentin', + 'comment_update' => 'päivitti kommentin', + 'comment_delete' => 'poisti kommentin', // Other - 'permissions_update' => 'updated permissions', + 'permissions_update' => 'päivitti käyttöoikeudet', ]; diff --git a/lang/fi/auth.php b/lang/fi/auth.php index dc4b242a0..40aca0695 100644 --- a/lang/fi/auth.php +++ b/lang/fi/auth.php @@ -6,112 +6,112 @@ */ return [ - 'failed' => 'These credentials do not match our records.', - 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + 'failed' => 'Annettuja käyttäjätietoja ei löydy.', + 'throttle' => 'Liikaa kirjautumisyrityksiä. Yritä uudelleen :seconds sekunnin päästä.', // Login & Register - 'sign_up' => 'Sign up', - 'log_in' => 'Log in', - 'log_in_with' => 'Login with :socialDriver', - 'sign_up_with' => 'Sign up with :socialDriver', - 'logout' => 'Logout', + 'sign_up' => 'Rekisteröidy', + 'log_in' => 'Kirjaudu sisään', + 'log_in_with' => 'Kirjaudu sisään palvelulla :socialDriver', + 'sign_up_with' => 'Rekisteröidy palvelulla :socialDriver', + 'logout' => 'Kirjaudu ulos', - 'name' => 'Name', - 'username' => 'Username', - 'email' => 'Email', - 'password' => 'Password', - 'password_confirm' => 'Confirm Password', - 'password_hint' => 'Must be at least 8 characters', - 'forgot_password' => 'Forgot Password?', - 'remember_me' => 'Remember Me', - 'ldap_email_hint' => 'Please enter an email to use for this account.', - 'create_account' => 'Create Account', - 'already_have_account' => 'Already have an account?', - 'dont_have_account' => 'Don\'t have an account?', - 'social_login' => 'Social Login', - 'social_registration' => 'Social Registration', - 'social_registration_text' => 'Register and sign in using another service.', + 'name' => 'Nimi', + 'username' => 'Käyttäjätunnus', + 'email' => 'Sähköposti', + 'password' => 'Salasana', + 'password_confirm' => 'Vahvista salasana', + 'password_hint' => 'Tulee olla vähintään 8 merkkiä', + 'forgot_password' => 'Unohditko salasanan?', + 'remember_me' => 'Muista minut', + 'ldap_email_hint' => 'Ole hyvä ja anna käyttäjätilin sähköpostiosoite.', + 'create_account' => 'Luo käyttäjätili', + 'already_have_account' => 'Onko sinulla jo käyttäjätili?', + 'dont_have_account' => 'Eikö sinulla ole käyttäjätiliä?', + 'social_login' => 'Kirjaudu sosiaalisen median käyttäjätilillä', + 'social_registration' => 'Rekisteröidy sosiaalisen median käyttäjätilillä', + 'social_registration_text' => 'Rekisteröidy ja kirjaudu sisään käyttämällä toista palvelua.', - 'register_thanks' => 'Thanks for registering!', - 'register_confirm' => 'Please check your email and click the confirmation button to access :appName.', - 'registrations_disabled' => 'Registrations are currently disabled', - 'registration_email_domain_invalid' => 'That email domain does not have access to this application', - 'register_success' => 'Thanks for signing up! You are now registered and signed in.', + 'register_thanks' => 'Kiitos rekisteröitymisestä!', + 'register_confirm' => 'Tarkista sähköpostisi ja paina vahvistuspainiketta päästäksesi sovellukseen :appName.', + 'registrations_disabled' => 'Rekisteröityminen on tällä hetkellä pois käytöstä', + 'registration_email_domain_invalid' => 'Tämän sähköpostiosoitteen verkkotunnuksella ei ole pääsyä tähän sovellukseen', + 'register_success' => 'Kiitos liittymisestä! Olet nyt rekisteröitynyt ja kirjautunut sisään.', // Login auto-initiation - 'auto_init_starting' => 'Attempting Login', - 'auto_init_starting_desc' => 'We\'re contacting your authentication system to start the login process. If there\'s no progress after 5 seconds you can try clicking the link below.', - 'auto_init_start_link' => 'Proceed with authentication', + 'auto_init_starting' => 'Kirjautumisyritys', + 'auto_init_starting_desc' => 'Otamme yhteyttä tunnistautumisjärjestelmääsi aloittaaksemme kirjautumisprosessin. Jos 5 sekunnin jälkeen ei tapahdu mitään, voit yrittää klikata alla olevaa linkkiä.', + 'auto_init_start_link' => 'Jatka tunnistautumisen avulla', // Password Reset - 'reset_password' => 'Reset Password', - 'reset_password_send_instructions' => 'Enter your email below and you will be sent an email with a password reset link.', - 'reset_password_send_button' => 'Send Reset Link', - 'reset_password_sent' => 'A password reset link will be sent to :email if that email address is found in the system.', - 'reset_password_success' => 'Your password has been successfully reset.', - 'email_reset_subject' => 'Reset your :appName password', - 'email_reset_text' => 'You are receiving this email because we received a password reset request for your account.', - 'email_reset_not_requested' => 'If you did not request a password reset, no further action is required.', + 'reset_password' => 'Palauta salasana', + 'reset_password_send_instructions' => 'Syötä sähköpostiosoitteesi alla olevaan kenttään, niin sinulle lähetetään sähköpostiviesti, jossa on salasanan palautuslinkki.', + 'reset_password_send_button' => 'Lähetä palautuslinkki', + 'reset_password_sent' => 'Salasanan palautuslinkki lähetetään osoitteeseen :email, jos kyseinen sähköpostiosoite löytyy järjestelmästä.', + 'reset_password_success' => 'Salasanasi on onnistuneesti palautettu.', + 'email_reset_subject' => 'Palauta salasanasi sivustolle :appName', + 'email_reset_text' => 'Saat tämän sähköpostiviestin, koska saimme käyttäjätiliäsi koskevan salasanan palautuspyynnön.', + 'email_reset_not_requested' => 'Jos et ole pyytänyt salasanan palauttamista, mitään toimenpiteitä ei tarvita.', // Email Confirmation - 'email_confirm_subject' => 'Confirm your email on :appName', - 'email_confirm_greeting' => 'Thanks for joining :appName!', - 'email_confirm_text' => 'Please confirm your email address by clicking the button below:', - 'email_confirm_action' => 'Confirm Email', - 'email_confirm_send_error' => 'Email confirmation required but the system could not send the email. Contact the admin to ensure email is set up correctly.', - 'email_confirm_success' => 'Your email has been confirmed! You should now be able to login using this email address.', - 'email_confirm_resent' => 'Confirmation email resent, Please check your inbox.', - 'email_confirm_thanks' => 'Thanks for confirming!', - 'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.', + 'email_confirm_subject' => 'Vahvista sähköpostisi sovelluksessa :appName', + 'email_confirm_greeting' => 'Kiitos liittymisestä sovellukseen :appName!', + 'email_confirm_text' => 'Vahvista sähköpostiosoitteesi klikkaamalla alla olevaa painiketta:', + 'email_confirm_action' => 'Vahvista sähköpostiosoite', + 'email_confirm_send_error' => 'Sähköpostivahvistusta vaaditaan, mutta järjestelmä ei pystynyt lähettämään sähköpostia. Ota yhteyttä ylläpitäjään varmistaaksesi, että sähköpostiasetukset on määritetty oikein.', + 'email_confirm_success' => 'Sähköpostisi on vahvistettu! Sinun pitäisi nyt pystyä kirjautumaan sisään tällä sähköpostiosoitteella.', + 'email_confirm_resent' => 'Vahvistussähköposti on lähetetty uudelleen, tarkista saapuneet sähköpostisi.', + 'email_confirm_thanks' => 'Kiitos vahvistuksesta!', + 'email_confirm_thanks_desc' => 'Odota hetki, kun vahvistuksesi käsitellään. Jos sinua ei ohjata uudelleen 3 sekunnin kuluttua, paina alla olevaa "Jatka"-linkkiä.', - 'email_not_confirmed' => 'Email Address Not Confirmed', - 'email_not_confirmed_text' => 'Your email address has not yet been confirmed.', - 'email_not_confirmed_click_link' => 'Please click the link in the email that was sent shortly after you registered.', - 'email_not_confirmed_resend' => 'If you cannot find the email you can re-send the confirmation email by submitting the form below.', - 'email_not_confirmed_resend_button' => 'Resend Confirmation Email', + 'email_not_confirmed' => 'Sähköpostiosoitetta ei ole vahvistettu', + 'email_not_confirmed_text' => 'Sähköpostiosoitettasi ei ole vielä vahvistettu.', + 'email_not_confirmed_click_link' => 'Klikkaa rekisteröitymisen jälkeen saapuneessa sähköpostissa olevaa vahvistuslinkkiä.', + 'email_not_confirmed_resend' => 'Jos et löydä sähköpostia, voit lähettää sen uudelleen alla olevalla lomakkeella.', + 'email_not_confirmed_resend_button' => 'Lähetä vahvistusviesti uudelleen', // User Invite - 'user_invite_email_subject' => 'You have been invited to join :appName!', - 'user_invite_email_greeting' => 'An account has been created for you on :appName.', - 'user_invite_email_text' => 'Click the button below to set an account password and gain access:', - 'user_invite_email_action' => 'Set Account Password', - 'user_invite_page_welcome' => 'Welcome to :appName!', - 'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.', - 'user_invite_page_confirm_button' => 'Confirm Password', - 'user_invite_success_login' => 'Password set, you should now be able to login using your set password to access :appName!', + 'user_invite_email_subject' => 'Sinut on kutsuttu liittymään sivustoon :appName!', + 'user_invite_email_greeting' => 'Sinulle on luotu käyttäjätili sivustolla :appName.', + 'user_invite_email_text' => 'Klikkaa alla olevaa painiketta asettaaksesi tilin salasanan ja saadaksesi pääsyn:', + 'user_invite_email_action' => 'Aseta käyttäjätilin salasana', + 'user_invite_page_welcome' => 'Tervetuloa sivustolle :appName!', + 'user_invite_page_text' => 'Viimeistelläksesi käyttäjätilisi ja saadaksesi pääsyn sinun on asetettava salasana, jolla kirjaudut jatkossa sivustolle :appName.', + 'user_invite_page_confirm_button' => 'Vahvista salasana', + 'user_invite_success_login' => 'Salasana asetettu, sinun pitäisi nyt pystyä kirjautumaan sivustolle :appName käyttämällä antamaasi salasanaa!', // Multi-factor Authentication - 'mfa_setup' => 'Setup Multi-Factor Authentication', - 'mfa_setup_desc' => 'Setup multi-factor authentication as an extra layer of security for your user account.', - 'mfa_setup_configured' => 'Already configured', - 'mfa_setup_reconfigure' => 'Reconfigure', - 'mfa_setup_remove_confirmation' => 'Are you sure you want to remove this multi-factor authentication method?', - 'mfa_setup_action' => 'Setup', - 'mfa_backup_codes_usage_limit_warning' => 'You have less than 5 backup codes remaining, Please generate and store a new set before you run out of codes to prevent being locked out of your account.', - 'mfa_option_totp_title' => 'Mobile App', - 'mfa_option_totp_desc' => 'To use multi-factor authentication you\'ll need a mobile application that supports TOTP such as Google Authenticator, Authy or Microsoft Authenticator.', - 'mfa_option_backup_codes_title' => 'Backup Codes', - 'mfa_option_backup_codes_desc' => 'Securely store a set of one-time-use backup codes which you can enter to verify your identity.', - 'mfa_gen_confirm_and_enable' => 'Confirm and Enable', - 'mfa_gen_backup_codes_title' => 'Backup Codes Setup', - 'mfa_gen_backup_codes_desc' => 'Store the below list of codes in a safe place. When accessing the system you\'ll be able to use one of the codes as a second authentication mechanism.', - 'mfa_gen_backup_codes_download' => 'Download Codes', - 'mfa_gen_backup_codes_usage_warning' => 'Each code can only be used once', - 'mfa_gen_totp_title' => 'Mobile App Setup', - 'mfa_gen_totp_desc' => 'To use multi-factor authentication you\'ll need a mobile application that supports TOTP such as Google Authenticator, Authy or Microsoft Authenticator.', - 'mfa_gen_totp_scan' => 'Scan the QR code below using your preferred authentication app to get started.', - 'mfa_gen_totp_verify_setup' => 'Verify Setup', - 'mfa_gen_totp_verify_setup_desc' => 'Verify that all is working by entering a code, generated within your authentication app, in the input box below:', - 'mfa_gen_totp_provide_code_here' => 'Provide your app generated code here', - 'mfa_verify_access' => 'Verify Access', - 'mfa_verify_access_desc' => 'Your user account requires you to confirm your identity via an additional level of verification before you\'re granted access. Verify using one of your configured methods to continue.', - 'mfa_verify_no_methods' => 'No Methods Configured', - 'mfa_verify_no_methods_desc' => 'No multi-factor authentication methods could be found for your account. You\'ll need to set up at least one method before you gain access.', - 'mfa_verify_use_totp' => 'Verify using a mobile app', - 'mfa_verify_use_backup_codes' => 'Verify using a backup code', - 'mfa_verify_backup_code' => 'Backup Code', - 'mfa_verify_backup_code_desc' => 'Enter one of your remaining backup codes below:', - 'mfa_verify_backup_code_enter_here' => 'Enter backup code here', - 'mfa_verify_totp_desc' => 'Enter the code, generated using your mobile app, below:', - 'mfa_setup_login_notification' => 'Multi-factor method configured, Please now login again using the configured method.', + 'mfa_setup' => 'Määritä monivaiheinen tunnistautuminen', + 'mfa_setup_desc' => 'Määritä monivaiheinen tunnistautuminen käyttäjätilisi turvallisuuden parantamiseksi.', + 'mfa_setup_configured' => 'Määritetty', + 'mfa_setup_reconfigure' => 'Uudelleenmäärittele', + 'mfa_setup_remove_confirmation' => 'Oletko varma, että haluat poistaa tämän monivaiheisen tunnistautumisen menetelmän?', + 'mfa_setup_action' => 'Asetukset', + 'mfa_backup_codes_usage_limit_warning' => 'Sinulla on alle 5 varmistuskoodia jäljellä. Luo ja tallenna uusi sarja ennen kuin koodit loppuvat, jotta käyttäjätilisi ei lukitu.', + 'mfa_option_totp_title' => 'Mobiilisovellus', + 'mfa_option_totp_desc' => 'Jos haluat käyttää monivaiheista tunnistautumista, tarvitset mobiilisovelluksen, joka tukee TOTP:tä, kuten Google Authenticator, Authy tai Microsoft Authenticator.', + 'mfa_option_backup_codes_title' => 'Varmistuskoodit', + 'mfa_option_backup_codes_desc' => 'Tallenna turvallisesti joukko kertakäyttöisiä varmistuskoodeja, jotka voit syöttää henkilöllisyytesi varmentamiseksi.', + 'mfa_gen_confirm_and_enable' => 'Vahvista ja ota käyttöön', + 'mfa_gen_backup_codes_title' => 'Varmistuskoodien asetukset', + 'mfa_gen_backup_codes_desc' => 'Säilytä alla oleva luettelo koodeista turvallisessa paikassa. Kun käytät järjestelmää, voit käyttää yhtä koodeista toisena tunnistautumistapana.', + 'mfa_gen_backup_codes_download' => 'Lataa koodit', + 'mfa_gen_backup_codes_usage_warning' => 'Jokainen koodi voidaan käyttää vain kerran', + 'mfa_gen_totp_title' => 'Mobiilisovelluksen asetukset', + 'mfa_gen_totp_desc' => 'Jos haluat käyttää monivaiheista tunnistautumista, tarvitset mobiilisovelluksen, joka tukee TOTP: tä, kuten Google Authenticator, Authy tai Microsoft Authenticator.', + 'mfa_gen_totp_scan' => 'Skannaa alla oleva QR-koodi haluamallasi todennussovelluksella päästäksesi alkuun.', + 'mfa_gen_totp_verify_setup' => 'Vahvista asetukset', + 'mfa_gen_totp_verify_setup_desc' => 'Vahvista, että kaikki toimii syöttämällä tunnistautumissovelluksessa luotu koodi alla olevaan kenttään:', + 'mfa_gen_totp_provide_code_here' => 'Anna sovelluksen luoma koodi', + 'mfa_verify_access' => 'Vahvista pääsy', + 'mfa_verify_access_desc' => 'Käyttäjätilisi vaatii kirjautumiseen monivaiheisen tunnistautumisen. Vahvista kirjautuminen jollakin määrittelemälläsi menetelmällä jatkaaksesi.', + 'mfa_verify_no_methods' => 'Ei määriteltyjä Menetelmiä', + 'mfa_verify_no_methods_desc' => 'Käyttäjätilillesi ei löytynyt monivaiheisen tunnistautumisen menetelmiä. Kirjautumiseen vaaditaan vähintään yksi menetelmä.', + 'mfa_verify_use_totp' => 'Vahvista käyttämällä mobiilisovellusta', + 'mfa_verify_use_backup_codes' => 'Vahvista käyttämällä varmistuskoodia', + 'mfa_verify_backup_code' => 'Varmistuskoodi', + 'mfa_verify_backup_code_desc' => 'Syötä yksi jäljellä olevista varmistukoodeistasi:', + 'mfa_verify_backup_code_enter_here' => 'Syötä varmistuskoodi', + 'mfa_verify_totp_desc' => 'Anna mobiilisovelluksella luotu koodi alle:', + 'mfa_setup_login_notification' => 'Monivaiheisen tunnistautumisen menetelmä määritetty. Kirjaudu nyt uudelleen käyttämällä määritettyä menetelmää.', ]; diff --git a/lang/fi/common.php b/lang/fi/common.php index 27037babe..76c5cc41f 100644 --- a/lang/fi/common.php +++ b/lang/fi/common.php @@ -5,106 +5,106 @@ return [ // Buttons - 'cancel' => 'Cancel', - 'close' => 'Close', - 'confirm' => 'Confirm', - 'back' => 'Back', - 'save' => 'Save', - 'continue' => 'Continue', - 'select' => 'Select', - 'toggle_all' => 'Toggle All', - 'more' => 'More', + 'cancel' => 'Peruuta', + 'close' => 'Sulje', + 'confirm' => 'Vahvista', + 'back' => 'Takaisin', + 'save' => 'Tallenna', + 'continue' => 'Jatka', + 'select' => 'Valitse', + 'toggle_all' => 'Vaihda kaikki', + 'more' => 'Lisää', // Form Labels - 'name' => 'Name', - 'description' => 'Description', - 'role' => 'Role', - 'cover_image' => 'Cover image', - 'cover_image_description' => 'This image should be approx 440x250px.', + 'name' => 'Nimi', + 'description' => 'Kuvaus', + 'role' => 'Rooli', + 'cover_image' => 'Kansikuva', + 'cover_image_description' => 'Kuvan tulee olla noin 440x250px.', // Actions - 'actions' => 'Actions', - 'view' => 'View', - 'view_all' => 'View All', - 'new' => 'New', - 'create' => 'Create', - 'update' => 'Update', - 'edit' => 'Edit', - 'sort' => 'Sort', - 'move' => 'Move', - 'copy' => 'Copy', - 'reply' => 'Reply', - 'delete' => 'Delete', - 'delete_confirm' => 'Confirm Deletion', - 'search' => 'Search', - 'search_clear' => 'Clear Search', - 'reset' => 'Reset', - 'remove' => 'Remove', - 'add' => 'Add', - 'configure' => 'Configure', - 'manage' => 'Manage', - 'fullscreen' => 'Fullscreen', - 'favourite' => 'Favourite', - 'unfavourite' => 'Unfavourite', - 'next' => 'Next', - 'previous' => 'Previous', - 'filter_active' => 'Active Filter:', - 'filter_clear' => 'Clear Filter', - 'download' => 'Download', - 'open_in_tab' => 'Open in Tab', - 'open' => 'Open', + 'actions' => 'Toiminnot', + 'view' => 'Näytä', + 'view_all' => 'Näytä kaikki', + 'new' => 'Uusi', + 'create' => 'Luo', + 'update' => 'Päivitä', + 'edit' => 'Muokkaa', + 'sort' => 'Järjestä', + 'move' => 'Siirrä', + 'copy' => 'Kopioi', + 'reply' => 'Vastaa', + 'delete' => 'Poista', + 'delete_confirm' => 'Vahvista poistaminen', + 'search' => 'Hae', + 'search_clear' => 'Tyhjennä haku', + 'reset' => 'Palauta', + 'remove' => 'Poista', + 'add' => 'Lisää', + 'configure' => 'Määritä', + 'manage' => 'Hallinnoi', + 'fullscreen' => 'Koko näyttö', + 'favourite' => 'Suosikki', + 'unfavourite' => 'Poista suosikki', + 'next' => 'Seuraava', + 'previous' => 'Edellinen', + 'filter_active' => 'Aktiivinen suodatin:', + 'filter_clear' => 'Tyhjennä suodatin', + 'download' => 'Lataa', + 'open_in_tab' => 'Avaa välilehdessä', + 'open' => 'Avaa', // Sort Options - 'sort_options' => 'Sort Options', - 'sort_direction_toggle' => 'Sort Direction Toggle', - 'sort_ascending' => 'Sort Ascending', - 'sort_descending' => 'Sort Descending', - 'sort_name' => 'Name', - 'sort_default' => 'Default', - 'sort_created_at' => 'Created Date', - 'sort_updated_at' => 'Updated Date', + 'sort_options' => 'Järjestyksen asetukset', + 'sort_direction_toggle' => 'Järjestyssuunnan vaihto', + 'sort_ascending' => 'Järjestä nousevasti', + 'sort_descending' => 'Järjestä laskevasti', + 'sort_name' => 'Nimi', + 'sort_default' => 'Oletus', + 'sort_created_at' => 'Luontipäiväys', + 'sort_updated_at' => 'Päivityksen päiväys', // Misc - 'deleted_user' => 'Deleted User', - 'no_activity' => 'No activity to show', - 'no_items' => 'No items available', - 'back_to_top' => 'Back to top', - 'skip_to_main_content' => 'Skip to main content', - 'toggle_details' => 'Toggle Details', - 'toggle_thumbnails' => 'Toggle Thumbnails', - 'details' => 'Details', - 'grid_view' => 'Grid View', - 'list_view' => 'List View', - 'default' => 'Default', - 'breadcrumb' => 'Breadcrumb', - 'status' => 'Status', - 'status_active' => 'Active', - 'status_inactive' => 'Inactive', - 'never' => 'Never', - 'none' => 'None', + 'deleted_user' => 'Poistettu käyttäjä', + 'no_activity' => 'Ei näytettävää toimintaa', + 'no_items' => 'Ei kohteita saatavilla', + 'back_to_top' => 'Palaa alkuun', + 'skip_to_main_content' => 'Siirry pääsisältöön', + 'toggle_details' => 'Näytä tiedot', + 'toggle_thumbnails' => 'Näytä pienoiskuvat', + 'details' => 'Tiedot', + 'grid_view' => 'Ruudukkonäkymä', + 'list_view' => 'Luettelonäkymä', + 'default' => 'Oletus', + 'breadcrumb' => 'Navigointipolku', + 'status' => 'Tila', + 'status_active' => 'Aktiivinen', + 'status_inactive' => 'Ei aktiivinen', + 'never' => 'Ei koskaan', + 'none' => 'Ei mitään', // Header - 'homepage' => 'Homepage', - 'header_menu_expand' => 'Expand Header Menu', - 'profile_menu' => 'Profile Menu', - 'view_profile' => 'View Profile', - 'edit_profile' => 'Edit Profile', - 'dark_mode' => 'Dark Mode', - 'light_mode' => 'Light Mode', - 'global_search' => 'Global Search', + 'homepage' => 'Kotisivu', + 'header_menu_expand' => 'Laajenna päävalikko', + 'profile_menu' => 'Profiilivalikko', + 'view_profile' => 'Näytä profiili', + 'edit_profile' => 'Muokkaa profiilia', + 'dark_mode' => 'Tumma tila', + 'light_mode' => 'Vaalea tila', + 'global_search' => 'Yleishaku', // Layout tabs 'tab_info' => 'Info', - 'tab_info_label' => 'Tab: Show Secondary Information', - 'tab_content' => 'Content', - 'tab_content_label' => 'Tab: Show Primary Content', + 'tab_info_label' => 'Välilehti: Näytä toissijaiset tiedot', + 'tab_content' => 'Sisältö', + 'tab_content_label' => 'Välilehti: Näytä ensisijainen sisältö', // Email Content - 'email_action_help' => 'If you’re having trouble clicking the ":actionText" button, copy and paste the URL below into your web browser:', - 'email_rights' => 'All rights reserved', + 'email_action_help' => 'Jos sinulla on ongelmia ":actionText"-painikkeen klikkaamisessa, kopioi ja liitä alla oleva URL-osoite selaimeesi:', + 'email_rights' => 'Kaikki oikeudet pidätetään', // Footer Link Options // Not directly used but available for convenience to users. - 'privacy_policy' => 'Privacy Policy', - 'terms_of_service' => 'Terms of Service', + 'privacy_policy' => 'Tietosuojaseloste', + 'terms_of_service' => 'Palvelun käyttöehdot', ]; diff --git a/lang/fi/components.php b/lang/fi/components.php index c33b1d0b7..570318158 100644 --- a/lang/fi/components.php +++ b/lang/fi/components.php @@ -5,42 +5,42 @@ return [ // Image Manager - 'image_select' => 'Image Select', - 'image_list' => 'Image List', - 'image_details' => 'Image Details', - 'image_upload' => 'Upload Image', - 'image_intro' => 'Here you can select and manage images that have been previously uploaded to the system.', - 'image_intro_upload' => 'Upload a new image by dragging an image file into this window, or by using the "Upload Image" button above.', - 'image_all' => 'All', - 'image_all_title' => 'View all images', - 'image_book_title' => 'View images uploaded to this book', - 'image_page_title' => 'View images uploaded to this page', - 'image_search_hint' => 'Search by image name', - 'image_uploaded' => 'Uploaded :uploadedDate', - 'image_uploaded_by' => 'Uploaded by :userName', - 'image_uploaded_to' => 'Uploaded to :pageLink', - 'image_updated' => 'Updated :updateDate', - 'image_load_more' => 'Load More', - 'image_image_name' => 'Image Name', - 'image_delete_used' => 'This image is used in the pages below.', - 'image_delete_confirm_text' => 'Are you sure you want to delete this image?', - 'image_select_image' => 'Select Image', - 'image_dropzone' => 'Drop images or click here to upload', - 'image_dropzone_drop' => 'Drop images here to upload', - 'images_deleted' => 'Images Deleted', - 'image_preview' => 'Image Preview', - 'image_upload_success' => 'Image uploaded successfully', - 'image_update_success' => 'Image details successfully updated', - 'image_delete_success' => 'Image successfully deleted', - 'image_replace' => 'Replace Image', - 'image_replace_success' => 'Image file successfully updated', - 'image_rebuild_thumbs' => 'Regenerate Size Variations', - 'image_rebuild_thumbs_success' => 'Image size variations successfully rebuilt!', + 'image_select' => 'Kuvan valinta', + 'image_list' => 'Kuvalista', + 'image_details' => 'Kuvan tiedot', + 'image_upload' => 'Lataa kuva', + 'image_intro' => 'Täällä voit valita ja hallita kuvia, jotka on aiemmin ladattu järjestelmään.', + 'image_intro_upload' => 'Lataa uusi kuva vetämällä kuvatiedosto tähän ikkunaan tai käyttämällä yllä olevaa "Lataa kuva" -painiketta.', + 'image_all' => 'Kaikki', + 'image_all_title' => 'Näytä kaikki kuvat', + 'image_book_title' => 'Näytä tähän kirjaan ladatut kuvat', + 'image_page_title' => 'Näytä tähän sivuun ladatut kuvat', + 'image_search_hint' => 'Hae kuvan nimellä', + 'image_uploaded' => 'Ladattu :uploadedDate', + 'image_uploaded_by' => 'Lataaja :userName', + 'image_uploaded_to' => 'Ladattu sivulle :pageLink', + 'image_updated' => 'Päivitetty :updateDate', + 'image_load_more' => 'Lataa lisää', + 'image_image_name' => 'Kuvan nimi', + 'image_delete_used' => 'Tätä kuvaa käytetään alla mainituilla sivuilla.', + 'image_delete_confirm_text' => 'Haluatko varmasti poistaa tämän kuvan?', + 'image_select_image' => 'Valitse kuva', + 'image_dropzone' => 'Pudota kuvat tai lataa ne klikkaamalla tästä', + 'image_dropzone_drop' => 'Pudota kuvat tähän ladattavaksi', + 'images_deleted' => 'Kuvat poistettu', + 'image_preview' => 'Kuvan esikatselu', + 'image_upload_success' => 'Kuva ladattiin onnistuneesti', + 'image_update_success' => 'Kuvan tiedot päivitettiin onnistuneesti', + 'image_delete_success' => 'Kuva poistettiin onnistuneesti', + 'image_replace' => 'Vaihda kuva', + 'image_replace_success' => 'Kuvatiedosto päivitettiin onnistuneesti', + 'image_rebuild_thumbs' => 'Luo kokovaihtoehdot uudelleen', + 'image_rebuild_thumbs_success' => 'Kuvan kokovaihtoehdot luotiin onnistuneesti uudelleen!', // Code Editor - 'code_editor' => 'Edit Code', - 'code_language' => 'Code Language', - 'code_content' => 'Code Content', - 'code_session_history' => 'Session History', - 'code_save' => 'Save Code', + 'code_editor' => 'Muokkaa koodia', + 'code_language' => 'Koodin kieli', + 'code_content' => 'Koodin sisältö', + 'code_session_history' => 'Istuntohistoria', + 'code_save' => 'Tallenna koodi', ]; diff --git a/lang/fi/editor.php b/lang/fi/editor.php index 670c1c5e1..7954aa603 100644 --- a/lang/fi/editor.php +++ b/lang/fi/editor.php @@ -7,168 +7,168 @@ */ return [ // General editor terms - 'general' => 'General', - 'advanced' => 'Advanced', - 'none' => 'None', - 'cancel' => 'Cancel', - 'save' => 'Save', - 'close' => 'Close', - 'undo' => 'Undo', - 'redo' => 'Redo', - 'left' => 'Left', - 'center' => 'Center', - 'right' => 'Right', - 'top' => 'Top', - 'middle' => 'Middle', - 'bottom' => 'Bottom', - 'width' => 'Width', - 'height' => 'Height', - 'More' => 'More', - 'select' => 'Select...', + 'general' => 'Yleinen', + 'advanced' => 'Lisäasetukset', + 'none' => 'Ei mitään', + 'cancel' => 'Peruuta', + 'save' => 'Tallenna', + 'close' => 'Sulje', + 'undo' => 'Kumoa', + 'redo' => 'Tee uudelleen', + 'left' => 'Vasen', + 'center' => 'Keskellä', + 'right' => 'Oikea', + 'top' => 'Ylhäällä', + 'middle' => 'Keskellä', + 'bottom' => 'Alhaalla', + 'width' => 'Leveys', + 'height' => 'Korkeus', + 'More' => 'Enemmän', + 'select' => 'Valitse...', // Toolbar - 'formats' => 'Formats', - 'header_large' => 'Large Header', - 'header_medium' => 'Medium Header', - 'header_small' => 'Small Header', - 'header_tiny' => 'Tiny Header', - 'paragraph' => 'Paragraph', - 'blockquote' => 'Blockquote', - 'inline_code' => 'Inline code', - 'callouts' => 'Callouts', - 'callout_information' => 'Information', - 'callout_success' => 'Success', - 'callout_warning' => 'Warning', - 'callout_danger' => 'Danger', - 'bold' => 'Bold', - 'italic' => 'Italic', - 'underline' => 'Underline', - 'strikethrough' => 'Strikethrough', - 'superscript' => 'Superscript', - 'subscript' => 'Subscript', - 'text_color' => 'Text color', - 'custom_color' => 'Custom color', - 'remove_color' => 'Remove color', - 'background_color' => 'Background color', - 'align_left' => 'Align left', - 'align_center' => 'Align center', - 'align_right' => 'Align right', - 'align_justify' => 'Justify', - 'list_bullet' => 'Bullet list', - 'list_numbered' => 'Numbered list', - 'list_task' => 'Task list', - 'indent_increase' => 'Increase indent', - 'indent_decrease' => 'Decrease indent', - 'table' => 'Table', - 'insert_image' => 'Insert image', - 'insert_image_title' => 'Insert/Edit Image', - 'insert_link' => 'Insert/edit link', - 'insert_link_title' => 'Insert/Edit Link', - 'insert_horizontal_line' => 'Insert horizontal line', - 'insert_code_block' => 'Insert code block', - 'edit_code_block' => 'Edit code block', - 'insert_drawing' => 'Insert/edit drawing', - 'drawing_manager' => 'Drawing manager', - 'insert_media' => 'Insert/edit media', - 'insert_media_title' => 'Insert/Edit Media', - 'clear_formatting' => 'Clear formatting', - 'source_code' => 'Source code', - 'source_code_title' => 'Source Code', - 'fullscreen' => 'Fullscreen', - 'image_options' => 'Image options', + 'formats' => 'Formaatit', + 'header_large' => 'Iso otsikko', + 'header_medium' => 'Keskikokoinen otsikko', + 'header_small' => 'Pieni otsikko', + 'header_tiny' => 'Hyvin pieni otsikko', + 'paragraph' => 'Kappale', + 'blockquote' => 'Sitaatti', + 'inline_code' => 'Koodi', + 'callouts' => 'Huomautukset', + 'callout_information' => 'Tietoja', + 'callout_success' => 'Onnistuminen', + 'callout_warning' => 'Varoitus', + 'callout_danger' => 'Vaara', + 'bold' => 'Lihavointi', + 'italic' => 'Kursivointi', + 'underline' => 'Alleviivaus', + 'strikethrough' => 'Yliviivaus', + 'superscript' => 'Yläindeksi', + 'subscript' => 'Alaindeksi', + 'text_color' => 'Tekstin väri', + 'custom_color' => 'Mukautettu väri', + 'remove_color' => 'Poista väri', + 'background_color' => 'Taustaväri', + 'align_left' => 'Tasaa vasemmalle', + 'align_center' => 'Tasaa keskelle', + 'align_right' => 'Tasaa oikealle', + 'align_justify' => 'Tasaa molemmat reunat', + 'list_bullet' => 'Lajittelematon lista', + 'list_numbered' => 'Numeroitu lista', + 'list_task' => 'Tehtävälista', + 'indent_increase' => 'Lisää sisennystä', + 'indent_decrease' => 'Vähennä sisennystä', + 'table' => 'Taulukko', + 'insert_image' => 'Lisää kuva', + 'insert_image_title' => 'Lisää/muokkaa kuvaa', + 'insert_link' => 'Lisää/muokkaa linkkiä', + 'insert_link_title' => 'Lisää/muokkaa linkkiä', + 'insert_horizontal_line' => 'Lisää vaakaviiva', + 'insert_code_block' => 'Lisää koodilohko', + 'edit_code_block' => 'Muokkaa koodilohkoa', + 'insert_drawing' => 'Lisää/muokkaa piirrosta', + 'drawing_manager' => 'Piirroksen hallinta', + 'insert_media' => 'Lisää/muokkaa mediaa', + 'insert_media_title' => 'Lisää/muokkaa mediaa', + 'clear_formatting' => 'Poista muotoilu', + 'source_code' => 'Lähdekoodi', + 'source_code_title' => 'Lähdekoodi', + 'fullscreen' => 'Koko näyttö', + 'image_options' => 'Kuvan asetukset', // Tables - 'table_properties' => 'Table properties', - 'table_properties_title' => 'Table Properties', - 'delete_table' => 'Delete table', - 'insert_row_before' => 'Insert row before', - 'insert_row_after' => 'Insert row after', - 'delete_row' => 'Delete row', - 'insert_column_before' => 'Insert column before', - 'insert_column_after' => 'Insert column after', - 'delete_column' => 'Delete column', - 'table_cell' => 'Cell', - 'table_row' => 'Row', - 'table_column' => 'Column', - 'cell_properties' => 'Cell properties', - 'cell_properties_title' => 'Cell Properties', - 'cell_type' => 'Cell type', - 'cell_type_cell' => 'Cell', - 'cell_scope' => 'Scope', - 'cell_type_header' => 'Header cell', - 'merge_cells' => 'Merge cells', - 'split_cell' => 'Split cell', - 'table_row_group' => 'Row Group', - 'table_column_group' => 'Column Group', - 'horizontal_align' => 'Horizontal align', - 'vertical_align' => 'Vertical align', - 'border_width' => 'Border width', - 'border_style' => 'Border style', - 'border_color' => 'Border color', - 'row_properties' => 'Row properties', - 'row_properties_title' => 'Row Properties', - 'cut_row' => 'Cut row', - 'copy_row' => 'Copy row', - 'paste_row_before' => 'Paste row before', - 'paste_row_after' => 'Paste row after', - 'row_type' => 'Row type', - 'row_type_header' => 'Header', - 'row_type_body' => 'Body', - 'row_type_footer' => 'Footer', - 'alignment' => 'Alignment', - 'cut_column' => 'Cut column', - 'copy_column' => 'Copy column', - 'paste_column_before' => 'Paste column before', - 'paste_column_after' => 'Paste column after', - 'cell_padding' => 'Cell padding', - 'cell_spacing' => 'Cell spacing', - 'caption' => 'Caption', - 'show_caption' => 'Show caption', - 'constrain' => 'Constrain proportions', - 'cell_border_solid' => 'Solid', - 'cell_border_dotted' => 'Dotted', - 'cell_border_dashed' => 'Dashed', - 'cell_border_double' => 'Double', - 'cell_border_groove' => 'Groove', - 'cell_border_ridge' => 'Ridge', - 'cell_border_inset' => 'Inset', - 'cell_border_outset' => 'Outset', - 'cell_border_none' => 'None', - 'cell_border_hidden' => 'Hidden', + 'table_properties' => 'Taulukon ominaisuudet', + 'table_properties_title' => 'Taulukon ominaisuudet', + 'delete_table' => 'Poista taulukko', + 'insert_row_before' => 'Lisää rivi ennen', + 'insert_row_after' => 'Lisää rivi jälkeen', + 'delete_row' => 'Poista rivi', + 'insert_column_before' => 'Liitä sarake ennen', + 'insert_column_after' => 'Lisää sarake jälkeen', + 'delete_column' => 'Poista sarake', + 'table_cell' => 'Solu', + 'table_row' => 'Rivi', + 'table_column' => 'Sarake', + 'cell_properties' => 'Solun ominaisuudet', + 'cell_properties_title' => 'Solun ominaisuudet', + 'cell_type' => 'Solun tyyppi', + 'cell_type_cell' => 'Solu', + 'cell_scope' => 'Laajuus', + 'cell_type_header' => 'Otsikkosolu', + 'merge_cells' => 'Yhdistä solut', + 'split_cell' => 'Jaa solu', + 'table_row_group' => 'Riviryhmä', + 'table_column_group' => 'Sarakeryhmä', + 'horizontal_align' => 'Vaaka-asettelu', + 'vertical_align' => 'Pystyasettelu', + 'border_width' => 'Reunuksen leveys', + 'border_style' => 'Reunuksen tyyli', + 'border_color' => 'Reunuksen väri', + 'row_properties' => 'Rivin ominaisuudet', + 'row_properties_title' => 'Rivin ominaisuudet', + 'cut_row' => 'Leikkaa rivi', + 'copy_row' => 'Kopioi rivi', + 'paste_row_before' => 'Liitä rivi ennen', + 'paste_row_after' => 'Liitä rivi jälkeen', + 'row_type' => 'Rivin tyyppi', + 'row_type_header' => 'Ylätunniste', + 'row_type_body' => 'Sisältö', + 'row_type_footer' => 'Alatunniste', + 'alignment' => 'Tasaus', + 'cut_column' => 'Leikkaa sarake', + 'copy_column' => 'Kopioi sarake', + 'paste_column_before' => 'Liitä sarake ennen', + 'paste_column_after' => 'Liitä sarake jälkeen', + 'cell_padding' => 'Solun reunus', + 'cell_spacing' => 'Solun välistys', + 'caption' => 'Otsikko', + 'show_caption' => 'Näytä otsikko', + 'constrain' => 'Rajaa mittasuhteet', + 'cell_border_solid' => 'Kiinteä', + 'cell_border_dotted' => 'Pisteviiva', + 'cell_border_dashed' => 'Katkoviiva', + 'cell_border_double' => 'Kaksinkertainen viiva', + 'cell_border_groove' => 'Ura', + 'cell_border_ridge' => 'Harjanne', + 'cell_border_inset' => 'Sisenevä', + 'cell_border_outset' => 'Ulkoneva', + 'cell_border_none' => 'Ei mitään', + 'cell_border_hidden' => 'Piilotettu', // Images, links, details/summary & embed - 'source' => 'Source', - 'alt_desc' => 'Alternative description', - 'embed' => 'Embed', - 'paste_embed' => 'Paste your embed code below:', - 'url' => 'URL', - 'text_to_display' => 'Text to display', - 'title' => 'Title', - 'open_link' => 'Open link', - 'open_link_in' => 'Open link in...', - 'open_link_current' => 'Current window', - 'open_link_new' => 'New window', - 'remove_link' => 'Remove link', - 'insert_collapsible' => 'Insert collapsible block', - 'collapsible_unwrap' => 'Unwrap', - 'edit_label' => 'Edit label', - 'toggle_open_closed' => 'Toggle open/closed', - 'collapsible_edit' => 'Edit collapsible block', - 'toggle_label' => 'Toggle label', + 'source' => 'Lähde', + 'alt_desc' => 'Vaihtoehtoinen kuvaus', + 'embed' => 'Upota', + 'paste_embed' => 'Liitä upotuskoodisi alle:', + 'url' => 'URL-osoite', + 'text_to_display' => 'Näytettävä teksti', + 'title' => 'Otsikko', + 'open_link' => 'Avaa linkki', + 'open_link_in' => 'Avaa linkki...', + 'open_link_current' => 'Nykyinen ikkuna', + 'open_link_new' => 'Uusi ikkuna', + 'remove_link' => 'Poista linkki', + 'insert_collapsible' => 'Lisää kokoontaitettava lohko', + 'collapsible_unwrap' => 'Poista ympäröivä lohko', + 'edit_label' => 'Muokkaa nimikettä', + 'toggle_open_closed' => 'Auki/kiinni', + 'collapsible_edit' => 'Muokkaa kokoontaitettavaa lohkoa', + 'toggle_label' => 'Näytä nimike', // About view - 'about' => 'About the editor', - 'about_title' => 'About the WYSIWYG Editor', - 'editor_license' => 'Editor License & Copyright', - 'editor_tiny_license' => 'This editor is built using :tinyLink which is provided under the MIT license.', - 'editor_tiny_license_link' => 'The copyright and license details of TinyMCE can be found here.', - 'save_continue' => 'Save Page & Continue', - 'callouts_cycle' => '(Keep pressing to toggle through types)', - 'link_selector' => 'Link to content', - 'shortcuts' => 'Shortcuts', - 'shortcut' => 'Shortcut', - 'shortcuts_intro' => 'The following shortcuts are available in the editor:', + 'about' => 'Tietoja editorista', + 'about_title' => 'Tietoja WYSIWYG-editorista', + 'editor_license' => 'Editorin lisenssi ja tekijänoikeus', + 'editor_tiny_license' => 'Tämä editori on rakennettu käyttäen sovellusta :tinyLink, joka on MIT-lisenssin alainen.', + 'editor_tiny_license_link' => 'TinyMCE-editorin tekijänoikeus- ja lisenssitiedot löytyvät täältä.', + 'save_continue' => 'Tallenna sivu ja jatka', + 'callouts_cycle' => '(Pidä painettuna valitaksesi tyyppien välillä)', + 'link_selector' => 'Linkki sisältöön', + 'shortcuts' => 'Pikanäppäimet', + 'shortcut' => 'Pikanäppäin', + 'shortcuts_intro' => 'Editorissa on saatavilla seuraavat pikanäppäimet:', 'windows_linux' => '(Windows/Linux)', 'mac' => '(Mac)', - 'description' => 'Description', + 'description' => 'Kuvaus', ]; diff --git a/lang/fi/entities.php b/lang/fi/entities.php index cfb5aae1a..dc43388a3 100644 --- a/lang/fi/entities.php +++ b/lang/fi/entities.php @@ -6,428 +6,432 @@ return [ // Shared - 'recently_created' => 'Recently Created', - 'recently_created_pages' => 'Recently Created Pages', - 'recently_updated_pages' => 'Recently Updated Pages', - 'recently_created_chapters' => 'Recently Created Chapters', - 'recently_created_books' => 'Recently Created Books', - 'recently_created_shelves' => 'Recently Created Shelves', - 'recently_update' => 'Recently Updated', - 'recently_viewed' => 'Recently Viewed', - 'recent_activity' => 'Recent Activity', - 'create_now' => 'Create one now', - 'revisions' => 'Revisions', - 'meta_revision' => 'Revision #:revisionCount', - 'meta_created' => 'Created :timeLength', - 'meta_created_name' => 'Created :timeLength by :user', - 'meta_updated' => 'Updated :timeLength', - 'meta_updated_name' => 'Updated :timeLength by :user', - 'meta_owned_name' => 'Owned by :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', - 'entity_select' => 'Entity Select', - 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', - 'images' => 'Images', - 'my_recent_drafts' => 'My Recent Drafts', - 'my_recently_viewed' => 'My Recently Viewed', - 'my_most_viewed_favourites' => 'My Most Viewed Favourites', - 'my_favourites' => 'My Favourites', - 'no_pages_viewed' => 'You have not viewed any pages', - 'no_pages_recently_created' => 'No pages have been recently created', - 'no_pages_recently_updated' => 'No pages have been recently updated', - 'export' => 'Export', - 'export_html' => 'Contained Web File', - 'export_pdf' => 'PDF File', - 'export_text' => 'Plain Text File', - 'export_md' => 'Markdown File', + 'recently_created' => 'Viimeksi luodut', + 'recently_created_pages' => 'Viimeksi luodut sivut', + 'recently_updated_pages' => 'Viimeksi päivitetyt sivut', + 'recently_created_chapters' => 'Viimeksi luodut luvut', + 'recently_created_books' => 'Viimeksi luodut kirjat', + 'recently_created_shelves' => 'Viimeksi luodut hyllyt', + 'recently_update' => 'Viimeksi päivitetyt', + 'recently_viewed' => 'Viimeksi katsotut', + 'recent_activity' => 'Viimeaikainen toiminta', + 'create_now' => 'Luo uusi', + 'revisions' => 'Versiot', + 'meta_revision' => 'Versio #:revisionCount', + 'meta_created' => 'Luotu :timeLength', + 'meta_created_name' => 'Luotu :timeLength käyttäjän :user toimesta', + 'meta_updated' => 'Päivitetty :timeLength', + 'meta_updated_name' => 'Päivitetty :timeLength käyttäjän :user toimesta', + 'meta_owned_name' => 'Omistaja :user', + 'meta_reference_count' => 'Viittaa :count kohteeseen|Viittaa :count kohteeseen', + 'entity_select' => 'Kohteen valinta', + 'entity_select_lack_permission' => 'Sinulla ei ole tarvittavia oikeuksia tämän kohteen valitsemiseen', + 'images' => 'Kuvat', + 'my_recent_drafts' => 'Viimeisimmät luonnokseni', + 'my_recently_viewed' => 'Omat viimeksi katsotut', + 'my_most_viewed_favourites' => 'Omat katsotuimmat suosikit', + 'my_favourites' => 'Omat suosikit', + 'no_pages_viewed' => 'Et ole katsonut yhtään sivua', + 'no_pages_recently_created' => 'Yhtään sivua ei ole luotu äskettäin', + 'no_pages_recently_updated' => 'Yhtään sivua ei ole päivitetty äskettäin', + 'export' => 'Vie', + 'export_html' => 'HTML-tiedosto', + 'export_pdf' => 'PDF-tiedosto', + 'export_text' => 'Tekstitiedosto', + 'export_md' => 'Markdown-tiedosto', // Permissions and restrictions - 'permissions' => 'Permissions', - 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', - 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', - 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', - 'permissions_save' => 'Save Permissions', - 'permissions_owner' => 'Owner', - 'permissions_role_everyone_else' => 'Everyone Else', - 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', - 'permissions_role_override' => 'Override permissions for role', - 'permissions_inherit_defaults' => 'Inherit defaults', + 'permissions' => 'Käyttöoikeudet', + 'permissions_desc' => 'Määritä tässä käyttöoikeudet ohittaaksesi käyttäjäroolien antamat oletusoikeudet.', + 'permissions_book_cascade' => 'Kirjoille määritetyt käyttöoikeudet siirtyvät automaattisesti lukuihin ja -sivuihin, ellei niille ole määritelty omia käyttöoikeuksia.', + 'permissions_chapter_cascade' => 'Luvuille määritetyt käyttöoikeudet siirtyvät automaattisesti sivuille, ellei niille ole määritelty omia käyttöoikeuksia.', + 'permissions_save' => 'Tallenna käyttöoikeudet', + 'permissions_owner' => 'Omistaja', + 'permissions_role_everyone_else' => 'Kaikki muut', + 'permissions_role_everyone_else_desc' => 'Aseta käyttöoikeudet kaikille rooleille, joita ei ole erikseen ohitettu.', + 'permissions_role_override' => 'Ohita roolin käyttöoikeudet', + 'permissions_inherit_defaults' => 'Peritään oletusarvot', // Search - 'search_results' => 'Search Results', - 'search_total_results_found' => ':count result found|:count total results found', - 'search_clear' => 'Clear Search', - 'search_no_pages' => 'No pages matched this search', - 'search_for_term' => 'Search for :term', - 'search_more' => 'More Results', - 'search_advanced' => 'Advanced Search', - 'search_terms' => 'Search Terms', - 'search_content_type' => 'Content Type', - 'search_exact_matches' => 'Exact Matches', - 'search_tags' => 'Tag Searches', - 'search_options' => 'Options', - 'search_viewed_by_me' => 'Viewed by me', - 'search_not_viewed_by_me' => 'Not viewed by me', - 'search_permissions_set' => 'Permissions set', - 'search_created_by_me' => 'Created by me', - 'search_updated_by_me' => 'Updated by me', - 'search_owned_by_me' => 'Owned by me', - 'search_date_options' => 'Date Options', - 'search_updated_before' => 'Updated before', - 'search_updated_after' => 'Updated after', - 'search_created_before' => 'Created before', - 'search_created_after' => 'Created after', - 'search_set_date' => 'Set Date', - 'search_update' => 'Update Search', + 'search_results' => 'Hakutulokset', + 'search_total_results_found' => 'löytyi :count osuma|löytyi :count osumaa', + 'search_clear' => 'Tyhjennä haku', + 'search_no_pages' => 'Haulla ei löytynyt yhtään sivua', + 'search_for_term' => 'Hae sanaa :term', + 'search_more' => 'Lisää tuloksia', + 'search_advanced' => 'Tarkennettu haku', + 'search_terms' => 'Hakusanat', + 'search_content_type' => 'Sisältötyyppi', + 'search_exact_matches' => 'Täsmälliset vastineet', + 'search_tags' => 'Tunnisteen haut', + 'search_options' => 'Valinnat', + 'search_viewed_by_me' => 'Olen katsonut', + 'search_not_viewed_by_me' => 'En ole katsonut', + 'search_permissions_set' => 'Käyttöoikeudet asetettu', + 'search_created_by_me' => 'Minun luomani', + 'search_updated_by_me' => 'Minun päivittämäni', + 'search_owned_by_me' => 'Minun omistamani', + 'search_date_options' => 'Päiväyksen valinnat', + 'search_updated_before' => 'Päivitetty ennen', + 'search_updated_after' => 'Päivitetty jälkeen', + 'search_created_before' => 'Luotu ennen', + 'search_created_after' => 'Luotu jälkeen', + 'search_set_date' => 'Aseta päiväys', + 'search_update' => 'Päivitä haku', // Shelves - 'shelf' => 'Shelf', - 'shelves' => 'Shelves', - 'x_shelves' => ':count Shelf|:count Shelves', - 'shelves_empty' => 'No shelves have been created', - 'shelves_create' => 'Create New Shelf', - 'shelves_popular' => 'Popular Shelves', - 'shelves_new' => 'New Shelves', - 'shelves_new_action' => 'New Shelf', - 'shelves_popular_empty' => 'The most popular shelves will appear here.', - 'shelves_new_empty' => 'The most recently created shelves will appear here.', - 'shelves_save' => 'Save Shelf', - 'shelves_books' => 'Books on this shelf', - 'shelves_add_books' => 'Add books to this shelf', - 'shelves_drag_books' => 'Drag books below to add them to this shelf', - 'shelves_empty_contents' => 'This shelf has no books assigned to it', - 'shelves_edit_and_assign' => 'Edit shelf to assign books', - 'shelves_edit_named' => 'Edit Shelf :name', - 'shelves_edit' => 'Edit Shelf', - 'shelves_delete' => 'Delete Shelf', - 'shelves_delete_named' => 'Delete Shelf :name', - 'shelves_delete_explain' => "This will delete the shelf with the name ':name'. Contained books will not be deleted.", - 'shelves_delete_confirmation' => 'Are you sure you want to delete this shelf?', - 'shelves_permissions' => 'Shelf Permissions', - 'shelves_permissions_updated' => 'Shelf Permissions Updated', - 'shelves_permissions_active' => 'Shelf Permissions Active', - 'shelves_permissions_cascade_warning' => 'Permissions on shelves do not automatically cascade to contained books. This is because a book can exist on multiple shelves. Permissions can however be copied down to child books using the option found below.', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', - 'shelves_copy_permissions_to_books' => 'Copy Permissions to Books', - 'shelves_copy_permissions' => 'Copy Permissions', - 'shelves_copy_permissions_explain' => 'This will apply the current permission settings of this shelf to all books contained within. Before activating, ensure any changes to the permissions of this shelf have been saved.', - 'shelves_copy_permission_success' => 'Shelf permissions copied to :count books', + 'shelf' => 'Hylly', + 'shelves' => 'Hyllyt', + 'x_shelves' => ':count hylly|:count hyllyä', + 'shelves_empty' => 'Hyllyjä ei ole luotu', + 'shelves_create' => 'Luo uusi hylly', + 'shelves_popular' => 'Suositut hyllyt', + 'shelves_new' => 'Uudet hyllyt', + 'shelves_new_action' => 'Uusi hylly', + 'shelves_popular_empty' => 'Suosituimmat hyllyt näkyvät tässä.', + 'shelves_new_empty' => 'Viimeksi luodut hyllyt näkyvät tässä.', + 'shelves_save' => 'Tallenna hylly', + 'shelves_books' => 'Tässä hyllyssä olevat kirjat', + 'shelves_add_books' => 'Lisää kirjoja tähän hyllyyn', + 'shelves_drag_books' => 'Vedä alla olevia kirjoja lisätäksesi ne tähän hyllyyn', + 'shelves_empty_contents' => 'Tälle hyllylle ei ole lisätty kirjoja', + 'shelves_edit_and_assign' => 'Muokkaa hyllyä lisätäksesi kirjoja', + 'shelves_edit_named' => 'Muokkaa hyllyä :name', + 'shelves_edit' => 'Muokkaa hyllyä', + 'shelves_delete' => 'Poista hylly', + 'shelves_delete_named' => 'Poista hylly :name', + 'shelves_delete_explain' => "Tämä poistaa hyllyn, jonka nimi on ':nimi'. Sisältyviä kirjoja ei poisteta.", + 'shelves_delete_confirmation' => 'Haluatko varmasti poistaa tämän hyllyn?', + 'shelves_permissions' => 'Hyllyn käyttöoikeudet', + 'shelves_permissions_updated' => 'Hyllyn käyttöoikeudet päivitetty', + 'shelves_permissions_active' => 'Hyllyn käyttöoikeudet käytössä', + 'shelves_permissions_cascade_warning' => 'Hyllyjen käyttöoikeudet eivät siirry automaattisesti kirjoihin. Tämä johtuu siitä, että kirja voi olla useammassa hyllyssä. Käyttöoikeudet voidaan kuitenkin kopioida kaikkiin hyllyn kirjoihin käyttämällä alla olevaa valintaa.', + 'shelves_permissions_create' => 'Hyllyjen luontioikeuksia käytetään vain kopioidessa oikeuksia hyllyn kirjoihin alla olevan toiminnon avulla. Ne eivät vaikuta mahdollisuuteen luoda kirjoja.', + 'shelves_copy_permissions_to_books' => 'Kopioi käyttöoikeudet kirjoihin', + 'shelves_copy_permissions' => 'Kopioi käyttöoikeudet', + 'shelves_copy_permissions_explain' => 'Tämä valinta siirtää hyllyn nykyiset käyttöoikeusasetukset kaikkiin hyllyssä oleviin kirjoihin. Varmista ennen aktivointia, että kaikki tämän hyllyn käyttöoikeuksiin tehdyt muutokset on tallennettu.', + 'shelves_copy_permission_success' => 'Hyllyn käyttöoikeudet kopioitu :count kirjaan', // Books - 'book' => 'Book', - 'books' => 'Books', - 'x_books' => ':count Book|:count Books', - 'books_empty' => 'No books have been created', - 'books_popular' => 'Popular Books', - 'books_recent' => 'Recent Books', - 'books_new' => 'New Books', - 'books_new_action' => 'New Book', - 'books_popular_empty' => 'The most popular books will appear here.', - 'books_new_empty' => 'The most recently created books will appear here.', - 'books_create' => 'Create New Book', - 'books_delete' => 'Delete Book', - 'books_delete_named' => 'Delete Book :bookName', - 'books_delete_explain' => 'This will delete the book with the name \':bookName\'. All pages and chapters will be removed.', - 'books_delete_confirmation' => 'Are you sure you want to delete this book?', - 'books_edit' => 'Edit Book', - 'books_edit_named' => 'Edit Book :bookName', - 'books_form_book_name' => 'Book Name', - 'books_save' => 'Save Book', - 'books_permissions' => 'Book Permissions', - 'books_permissions_updated' => 'Book Permissions Updated', - 'books_empty_contents' => 'No pages or chapters have been created for this book.', - 'books_empty_create_page' => 'Create a new page', - 'books_empty_sort_current_book' => 'Sort the current book', - 'books_empty_add_chapter' => 'Add a chapter', - 'books_permissions_active' => 'Book Permissions Active', - 'books_search_this' => 'Search this book', - 'books_navigation' => 'Book Navigation', - 'books_sort' => 'Sort Book Contents', - 'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books.', - 'books_sort_named' => 'Sort Book :bookName', - 'books_sort_name' => 'Sort by Name', - 'books_sort_created' => 'Sort by Created Date', - 'books_sort_updated' => 'Sort by Updated Date', - 'books_sort_chapters_first' => 'Chapters First', - 'books_sort_chapters_last' => 'Chapters Last', - 'books_sort_show_other' => 'Show Other Books', - 'books_sort_save' => 'Save New Order', - 'books_sort_show_other_desc' => 'Add other books here to include them in the sort operation, and allow easy cross-book reorganisation.', - 'books_sort_move_up' => 'Move Up', - 'books_sort_move_down' => 'Move Down', - 'books_sort_move_prev_book' => 'Move to Previous Book', - 'books_sort_move_next_book' => 'Move to Next Book', - 'books_sort_move_prev_chapter' => 'Move Into Previous Chapter', - 'books_sort_move_next_chapter' => 'Move Into Next Chapter', - 'books_sort_move_book_start' => 'Move to Start of Book', - 'books_sort_move_book_end' => 'Move to End of Book', - 'books_sort_move_before_chapter' => 'Move to Before Chapter', - 'books_sort_move_after_chapter' => 'Move to After Chapter', - 'books_copy' => 'Copy Book', - 'books_copy_success' => 'Book successfully copied', + 'book' => 'Kirja', + 'books' => 'Kirjat', + 'x_books' => ':count kirja|:count kirjaa', + 'books_empty' => 'Kirjoja ei ole luotu', + 'books_popular' => 'Suositut kirjat', + 'books_recent' => 'Viimeisimmät kirjat', + 'books_new' => 'Uudet kirjat', + 'books_new_action' => 'Uusi kirja', + 'books_popular_empty' => 'Suosituimmat kirjat näkyvät tässä.', + 'books_new_empty' => 'Viimeksi luodut kirjat näkyvät tässä.', + 'books_create' => 'Luo uusi kirja', + 'books_delete' => 'Poista kirja', + 'books_delete_named' => 'Poista kirja :bookName', + 'books_delete_explain' => 'Tämä poistaa kirjan, jonka nimi on \':bookName\'. Kaikki sivut ja luvut poistetaan.', + 'books_delete_confirmation' => 'Haluatko varmasti poistaa tämän kirjan?', + 'books_edit' => 'Muokkaa kirjaa', + 'books_edit_named' => 'Muokkaa kirjaa :bookName', + 'books_form_book_name' => 'Kirjan nimi', + 'books_save' => 'Tallenna kirja', + 'books_default_template' => 'Oletusmallipohja', + 'books_default_template_explain' => 'Määritä mallipohja, jota käytetään oletuksena kaikille tämän kirjan uusille sivuille. Muista, että mallipohjaa käytetään vain, jos sivun luojalla on katseluoikeudet valittuun mallipohjaan.', + 'books_default_template_select' => 'Valitse mallipohja', + 'books_permissions' => 'Kirjan käyttöoikeudet', + 'books_permissions_updated' => 'Kirjan käyttöoikeudet päivitetty', + 'books_empty_contents' => 'Tähän kirjaan ei ole luotu sivuja tai lukuja.', + 'books_empty_create_page' => 'Luo uusi sivu', + 'books_empty_sort_current_book' => 'Järjestä nykyistä kirjaa', + 'books_empty_add_chapter' => 'Lisää luku', + 'books_permissions_active' => 'Kirjan käyttöoikeudet käytössä', + 'books_search_this' => 'Hae tästä kirjasta', + 'books_navigation' => 'Kirjan navigaatio', + 'books_sort' => 'Järjestä kirjan sisältö', + 'books_sort_desc' => 'Siirrä lukuja ja sivuja kirjan sisällä järjestelläksesi sen sisältöä uudelleen. Voit lisätä muita kirjoja, jolloin lukujen ja sivujen siirtäminen kirjojen välillä on helppoa.', + 'books_sort_named' => 'Järjestä kirja :bookName', + 'books_sort_name' => 'Järjestä nimen mukaan', + 'books_sort_created' => 'Järjestä luontipäiväyksen mukaan', + 'books_sort_updated' => 'Järjestä päivityksen päiväyksen mukaan', + 'books_sort_chapters_first' => 'Luvut ensin', + 'books_sort_chapters_last' => 'Luvut viimeisenä', + 'books_sort_show_other' => 'Näytä muita kirjoja', + 'books_sort_save' => 'Tallenna uusi järjestys', + 'books_sort_show_other_desc' => 'Voit lisätä tähän muita kirjoja järjestämistä varten ja mahdollistaa sujuvan kirjojen välisen uudelleenjärjestelyn.', + 'books_sort_move_up' => 'Siirrä ylös', + 'books_sort_move_down' => 'Siirrä alas', + 'books_sort_move_prev_book' => 'Siirrä edelliseen kirjaan', + 'books_sort_move_next_book' => 'Siirrä seuraavaan kirjaan', + 'books_sort_move_prev_chapter' => 'Siirrä edelliseen lukuun', + 'books_sort_move_next_chapter' => 'Siirrä seuraavaan lukuun', + 'books_sort_move_book_start' => 'Siirrä kirjan alkuun', + 'books_sort_move_book_end' => 'Siirrä kirjan loppuun', + 'books_sort_move_before_chapter' => 'Siirrä luvun edelle', + 'books_sort_move_after_chapter' => 'Siirrä luvun jälkeen', + 'books_copy' => 'Kopioi kirja', + 'books_copy_success' => 'Kirja kopioitiin onnistuneesti', // Chapters - 'chapter' => 'Chapter', - 'chapters' => 'Chapters', - 'x_chapters' => ':count Chapter|:count Chapters', - 'chapters_popular' => 'Popular Chapters', - 'chapters_new' => 'New Chapter', - 'chapters_create' => 'Create New Chapter', - 'chapters_delete' => 'Delete Chapter', - 'chapters_delete_named' => 'Delete Chapter :chapterName', - 'chapters_delete_explain' => 'This will delete the chapter with the name \':chapterName\'. All pages that exist within this chapter will also be deleted.', - 'chapters_delete_confirm' => 'Are you sure you want to delete this chapter?', - 'chapters_edit' => 'Edit Chapter', - 'chapters_edit_named' => 'Edit Chapter :chapterName', - 'chapters_save' => 'Save Chapter', - 'chapters_move' => 'Move Chapter', - 'chapters_move_named' => 'Move Chapter :chapterName', - 'chapters_copy' => 'Copy Chapter', - 'chapters_copy_success' => 'Chapter successfully copied', - 'chapters_permissions' => 'Chapter Permissions', - 'chapters_empty' => 'No pages are currently in this chapter.', - 'chapters_permissions_active' => 'Chapter Permissions Active', - 'chapters_permissions_success' => 'Chapter Permissions Updated', - 'chapters_search_this' => 'Search this chapter', - 'chapter_sort_book' => 'Sort Book', + 'chapter' => 'Luku', + 'chapters' => 'Luvut', + 'x_chapters' => ':count luku|:count lukua', + 'chapters_popular' => 'Suositut luvut', + 'chapters_new' => 'Uusi luku', + 'chapters_create' => 'Luo uusi luku', + 'chapters_delete' => 'Poista luku', + 'chapters_delete_named' => 'Poista luku :chapterName', + 'chapters_delete_explain' => 'Tämä poistaa luvun nimeltä \':chapterName\'. Myös kaikki luvun sisällä olevat sivut poistetaan.', + 'chapters_delete_confirm' => 'Haluatko varmasti poistaa tämän luvun?', + 'chapters_edit' => 'Muokkaa lukua', + 'chapters_edit_named' => 'Muokkaa lukua :chapterName', + 'chapters_save' => 'Tallenna luku', + 'chapters_move' => 'Siirrä luku', + 'chapters_move_named' => 'Siirrä lukua :chapterName', + 'chapters_copy' => 'Kopioi luku', + 'chapters_copy_success' => 'Luku kopioitiin onnistuneesti', + 'chapters_permissions' => 'Luvun käyttöoikeudet', + 'chapters_empty' => 'Tässä luvussa ei ole tällä hetkellä sivuja.', + 'chapters_permissions_active' => 'Luvun käyttöoikeudet käytössä', + 'chapters_permissions_success' => 'Luvun käyttöoikeudet päivitetty', + 'chapters_search_this' => 'Hae tästä luvusta', + 'chapter_sort_book' => 'Järjestä kirja', // Pages - 'page' => 'Page', - 'pages' => 'Pages', - 'x_pages' => ':count Page|:count Pages', - 'pages_popular' => 'Popular Pages', - 'pages_new' => 'New Page', - 'pages_attachments' => 'Attachments', - 'pages_navigation' => 'Page Navigation', - 'pages_delete' => 'Delete Page', - 'pages_delete_named' => 'Delete Page :pageName', - 'pages_delete_draft_named' => 'Delete Draft Page :pageName', - 'pages_delete_draft' => 'Delete Draft Page', - 'pages_delete_success' => 'Page deleted', - 'pages_delete_draft_success' => 'Draft page deleted', - 'pages_delete_confirm' => 'Are you sure you want to delete this page?', - 'pages_delete_draft_confirm' => 'Are you sure you want to delete this draft page?', - 'pages_editing_named' => 'Editing Page :pageName', - 'pages_edit_draft_options' => 'Draft Options', - 'pages_edit_save_draft' => 'Save Draft', - 'pages_edit_draft' => 'Edit Page Draft', - 'pages_editing_draft' => 'Editing Draft', - 'pages_editing_page' => 'Editing Page', - 'pages_edit_draft_save_at' => 'Draft saved at ', - 'pages_edit_delete_draft' => 'Delete Draft', - 'pages_edit_delete_draft_confirm' => 'Are you sure you want to delete your draft page changes? All of your changes, since the last full save, will be lost and the editor will be updated with the latest page non-draft save state.', - 'pages_edit_discard_draft' => 'Discard Draft', - 'pages_edit_switch_to_markdown' => 'Switch to Markdown Editor', - 'pages_edit_switch_to_markdown_clean' => '(Clean Content)', - 'pages_edit_switch_to_markdown_stable' => '(Stable Content)', - 'pages_edit_switch_to_wysiwyg' => 'Switch to WYSIWYG Editor', - 'pages_edit_set_changelog' => 'Set Changelog', - 'pages_edit_enter_changelog_desc' => 'Enter a brief description of the changes you\'ve made', - 'pages_edit_enter_changelog' => 'Enter Changelog', - 'pages_editor_switch_title' => 'Switch Editor', - 'pages_editor_switch_are_you_sure' => 'Are you sure you want to change the editor for this page?', - 'pages_editor_switch_consider_following' => 'Consider the following when changing editors:', - 'pages_editor_switch_consideration_a' => 'Once saved, the new editor option will be used by any future editors, including those that may not be able to change editor type themselves.', - 'pages_editor_switch_consideration_b' => 'This can potentially lead to a loss of detail and syntax in certain circumstances.', - 'pages_editor_switch_consideration_c' => 'Tag or changelog changes, made since last save, won\'t persist across this change.', - 'pages_save' => 'Save Page', - 'pages_title' => 'Page Title', - 'pages_name' => 'Page Name', - 'pages_md_editor' => 'Editor', - 'pages_md_preview' => 'Preview', - 'pages_md_insert_image' => 'Insert Image', - 'pages_md_insert_link' => 'Insert Entity Link', - 'pages_md_insert_drawing' => 'Insert Drawing', - 'pages_md_show_preview' => 'Show preview', - 'pages_md_sync_scroll' => 'Sync preview scroll', - 'pages_drawing_unsaved' => 'Unsaved Drawing Found', - 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', - 'pages_not_in_chapter' => 'Page is not in a chapter', - 'pages_move' => 'Move Page', - 'pages_copy' => 'Copy Page', - 'pages_copy_desination' => 'Copy Destination', - 'pages_copy_success' => 'Page successfully copied', - 'pages_permissions' => 'Page Permissions', - 'pages_permissions_success' => 'Page permissions updated', - 'pages_revision' => 'Revision', - 'pages_revisions' => 'Page Revisions', - 'pages_revisions_desc' => 'Listed below are all the past revisions of this page. You can look back upon, compare, and restore old page versions if permissions allow. The full history of the page may not be fully reflected here since, depending on system configuration, old revisions could be auto-deleted.', - 'pages_revisions_named' => 'Page Revisions for :pageName', - 'pages_revision_named' => 'Page Revision for :pageName', - 'pages_revision_restored_from' => 'Restored from #:id; :summary', - 'pages_revisions_created_by' => 'Created By', - 'pages_revisions_date' => 'Revision Date', + 'page' => 'Sivu', + 'pages' => 'Sivut', + 'x_pages' => ':count sivu|:count sivua', + 'pages_popular' => 'Suositut sivut', + 'pages_new' => 'Uusi sivu', + 'pages_attachments' => 'Liitteet', + 'pages_navigation' => 'Sivun navigaatio', + 'pages_delete' => 'Poista sivu', + 'pages_delete_named' => 'Poista sivu :pageName', + 'pages_delete_draft_named' => 'Poista luonnos :pageName', + 'pages_delete_draft' => 'Poista luonnos', + 'pages_delete_success' => 'Sivu poistettu', + 'pages_delete_draft_success' => 'Luonnos poistettu', + 'pages_delete_warning_template' => 'Tätä sivua käytetään oletusmallipohjana. Näillä kirjoilla ei ole enää oletusmallipohjaa, jos tämä sivu poistetaan.', + 'pages_delete_confirm' => 'Oletko varma, että haluat poistaa tämän sivun?', + 'pages_delete_draft_confirm' => 'Haluatko varmasti poistaa tämän luonnoksen?', + 'pages_editing_named' => 'Muokataan sivua :pageName', + 'pages_edit_draft_options' => 'Luonnoksen asetukset', + 'pages_edit_save_draft' => 'Tallenna luonnos', + 'pages_edit_draft' => 'Muokkaa luonnosta', + 'pages_editing_draft' => 'Muokataan luonnosta', + 'pages_editing_page' => 'Muokataan sivua', + 'pages_edit_draft_save_at' => 'Luonnos tallennettu ', + 'pages_edit_delete_draft' => 'Poista luonnos', + 'pages_edit_delete_draft_confirm' => 'Oletko varma, että haluat poistaa luonnoksen muutokset? Kaikki muutokset viimeisimmästä tallennuksesta lähtien häviävät, ja editori päivitetään viimeisimpään tallennettuun sivun versioon.', + 'pages_edit_discard_draft' => 'Hylkää luonnos', + 'pages_edit_switch_to_markdown' => 'Vaihda Markdown-editoriin', + 'pages_edit_switch_to_markdown_clean' => '(Puhdas sisältö)', + 'pages_edit_switch_to_markdown_stable' => '(Vakaa sisältö)', + 'pages_edit_switch_to_wysiwyg' => 'Vaihda WYSIWYG-editoriin', + 'pages_edit_set_changelog' => 'Aseta muutosloki', + 'pages_edit_enter_changelog_desc' => 'Kirjoita lyhyt kuvaus tekemistäsi muutoksista', + 'pages_edit_enter_changelog' => 'Syötä muutosloki', + 'pages_editor_switch_title' => 'Vaihda editoria', + 'pages_editor_switch_are_you_sure' => 'Haluatko varmasti vaihtaa tämän sivun editorin?', + 'pages_editor_switch_consider_following' => 'Ota huomioon seuraavat asiat, kun vaihdat editoria:', + 'pages_editor_switch_consideration_a' => 'Tallennuksen jälkeen kaikki tulevat muokkaajat käyttävät uutta editorivaihtoehtoa, myös käyttäjät, jotka eivät ehkä pysty itse vaihtamaan editoria.', + 'pages_editor_switch_consideration_b' => 'Tämä voi joissain tapauksissa johtaa yksityiskohtien ja muotoilujen häviämiseen.', + 'pages_editor_switch_consideration_c' => 'Viimeisimmän tallennuksen jälkeen tehdyt tunniste- tai muutoslokimuutokset eivät säily.', + 'pages_save' => 'Tallenna sivu', + 'pages_title' => 'Sivun otsikko', + 'pages_name' => 'Sivun nimi', + 'pages_md_editor' => 'Editori', + 'pages_md_preview' => 'Esikatselu', + 'pages_md_insert_image' => 'Lisää kuva', + 'pages_md_insert_link' => 'Lisää linkki', + 'pages_md_insert_drawing' => 'Lisää piirustus', + 'pages_md_show_preview' => 'Näytä esikatselu', + 'pages_md_sync_scroll' => 'Vieritä esikatselua koodin vierityksen mukaan', + 'pages_drawing_unsaved' => 'Tallentamaton piirustus löytyi', + 'pages_drawing_unsaved_confirm' => 'Järjestelmä löysi tallentamattoman piirustuksen. Haluatko palauttaa piirustuksen ja jatkaa sen muokkaamista?', + 'pages_not_in_chapter' => 'Sivu ei kuulu mihinkään lukuun', + 'pages_move' => 'Siirrä sivu', + 'pages_copy' => 'Kopioi sivu', + 'pages_copy_desination' => 'Kopioinnin kohde', + 'pages_copy_success' => 'Sivu kopioitiin onnistuneesti', + 'pages_permissions' => 'Sivun käyttöoikeudet', + 'pages_permissions_success' => 'Sivun käyttöoikeudet päivitetty', + 'pages_revision' => 'Versio', + 'pages_revisions' => 'Sivun versiot', + 'pages_revisions_desc' => 'Alla on kaikki tämän sivun aiemmat versiot. Voit tarkastella, vertailla ja palauttaa vanhoja versioita, jos käyttöoikeudet sallivat. Sivun koko historia ei välttämättä näy kokonaan, sillä järjestelmän asetuksista riippuen vanhat versiot saatetaan poistaa automaattisesti.', + 'pages_revisions_named' => 'Sivun :pageName versiot', + 'pages_revision_named' => 'Sivun :pageName versio', + 'pages_revision_restored_from' => 'Palautettu versiosta #:id; :summary', + 'pages_revisions_created_by' => 'Luonut', + 'pages_revisions_date' => 'Version päiväys', 'pages_revisions_number' => '#', - 'pages_revisions_sort_number' => 'Revision Number', - 'pages_revisions_numbered' => 'Revision #:id', - 'pages_revisions_numbered_changes' => 'Revision #:id Changes', - 'pages_revisions_editor' => 'Editor Type', - 'pages_revisions_changelog' => 'Changelog', - 'pages_revisions_changes' => 'Changes', - 'pages_revisions_current' => 'Current Version', - 'pages_revisions_preview' => 'Preview', - 'pages_revisions_restore' => 'Restore', - 'pages_revisions_none' => 'This page has no revisions', - 'pages_copy_link' => 'Copy Link', - 'pages_edit_content_link' => 'Jump to section in editor', - 'pages_pointer_enter_mode' => 'Enter section select mode', - 'pages_pointer_label' => 'Page Section Options', - 'pages_pointer_permalink' => 'Page Section Permalink', - 'pages_pointer_include_tag' => 'Page Section Include Tag', - 'pages_pointer_toggle_link' => 'Permalink mode, Press to show include tag', - 'pages_pointer_toggle_include' => 'Include tag mode, Press to show permalink', - 'pages_permissions_active' => 'Page Permissions Active', - 'pages_initial_revision' => 'Initial publish', - 'pages_references_update_revision' => 'System auto-update of internal links', - 'pages_initial_name' => 'New Page', - 'pages_editing_draft_notification' => 'You are currently editing a draft that was last saved :timeDiff.', - 'pages_draft_edited_notification' => 'This page has been updated by since that time. It is recommended that you discard this draft.', - 'pages_draft_page_changed_since_creation' => 'This page has been updated since this draft was created. It is recommended that you discard this draft or take care not to overwrite any page changes.', + 'pages_revisions_sort_number' => 'Versionumero', + 'pages_revisions_numbered' => 'Versio #:id', + 'pages_revisions_numbered_changes' => 'Version #:id muutokset', + 'pages_revisions_editor' => 'Editorin tyyppi', + 'pages_revisions_changelog' => 'Muutoshistoria', + 'pages_revisions_changes' => 'Muutokset', + 'pages_revisions_current' => 'Nykyinen versio', + 'pages_revisions_preview' => 'Esikatselu', + 'pages_revisions_restore' => 'Palauta', + 'pages_revisions_none' => 'Tällä sivulla ei ole versioita', + 'pages_copy_link' => 'Kopioi linkki', + 'pages_edit_content_link' => 'Siirry osioon editorissa', + 'pages_pointer_enter_mode' => 'Siirry osion valintatilaan', + 'pages_pointer_label' => 'Sivun osion valinnat', + 'pages_pointer_permalink' => 'Sivun osion pysyvä linkki', + 'pages_pointer_include_tag' => 'Sivun osion viitetunniste', + 'pages_pointer_toggle_link' => 'Pysyvä linkki, valitse viitetunniste painamalla', + 'pages_pointer_toggle_include' => 'Viitetunniste, valitse pysyvä linkki painamalla', + 'pages_permissions_active' => 'Sivun käyttöoikeudet käytössä', + 'pages_initial_revision' => 'Alkuperäinen julkaisu', + 'pages_references_update_revision' => 'Sisäisten linkkien automaattinen päivitys', + 'pages_initial_name' => 'Uusi sivu', + 'pages_editing_draft_notification' => 'Muokkaat luonnosta, joka on viimeksi tallennettu :timeDiff.', + 'pages_draft_edited_notification' => 'Tätä sivua on päivitetty myöhemmin. Tämä luonnos suositellaan poistettavaksi.', + 'pages_draft_page_changed_since_creation' => 'Sivua on päivitetty tämän luonnoksen luomisen jälkeen. On suositeltavaa, että poistat tämän luonnoksen tai huolehdit siitä, ettet korvaa uusia sivun muutoksia.', 'pages_draft_edit_active' => [ - 'start_a' => ':count users have started editing this page', - 'start_b' => ':userName has started editing this page', - 'time_a' => 'since the page was last updated', - 'time_b' => 'in the last :minCount minutes', - 'message' => ':start :time. Take care not to overwrite each other\'s updates!', + 'start_a' => ':count käyttäjää muokkaa tätä sivua', + 'start_b' => ':userName muokkaa tätä sivua', + 'time_a' => 'sivun viimeisimmän päivityksen jälkeen', + 'time_b' => 'viimeisen :minCount minuutin aikana', + 'message' => ':start :time. Huolehdi siitä, että ette ylikirjoita toistenne päivityksiä!', ], - 'pages_draft_discarded' => 'Draft discarded! The editor has been updated with the current page content', - 'pages_draft_deleted' => 'Draft deleted! The editor has been updated with the current page content', - 'pages_specific' => 'Specific Page', - 'pages_is_template' => 'Page Template', + 'pages_draft_discarded' => 'Luonnos on hylätty! Editoriin on päivitetty sivun nykyinen sisältö', + 'pages_draft_deleted' => 'Luonnos on poistettu! Editoriin on päivitetty sivun nykyinen sisältö', + 'pages_specific' => 'Tietty sivu', + 'pages_is_template' => 'Mallipohja', // Editor Sidebar - 'toggle_sidebar' => 'Toggle Sidebar', - 'page_tags' => 'Page Tags', - 'chapter_tags' => 'Chapter Tags', - 'book_tags' => 'Book Tags', - 'shelf_tags' => 'Shelf Tags', - 'tag' => 'Tag', - 'tags' => 'Tags', - 'tags_index_desc' => 'Tags can be applied to content within the system to apply a flexible form of categorization. Tags can have both a key and value, with the value being optional. Once applied, content can then be queried using the tag name and value.', - 'tag_name' => 'Tag Name', - 'tag_value' => 'Tag Value (Optional)', - 'tags_explain' => "Add some tags to better categorise your content. \n You can assign a value to a tag for more in-depth organisation.", - 'tags_add' => 'Add another tag', - 'tags_remove' => 'Remove this tag', - 'tags_usages' => 'Total tag usages', - 'tags_assigned_pages' => 'Assigned to Pages', - 'tags_assigned_chapters' => 'Assigned to Chapters', - 'tags_assigned_books' => 'Assigned to Books', - 'tags_assigned_shelves' => 'Assigned to Shelves', - 'tags_x_unique_values' => ':count unique values', - 'tags_all_values' => 'All values', - 'tags_view_tags' => 'View Tags', - 'tags_view_existing_tags' => 'View existing tags', - 'tags_list_empty_hint' => 'Tags can be assigned via the page editor sidebar or while editing the details of a book, chapter or shelf.', - 'attachments' => 'Attachments', - 'attachments_explain' => 'Upload some files or attach some links to display on your page. These are visible in the page sidebar.', - 'attachments_explain_instant_save' => 'Changes here are saved instantly.', - 'attachments_upload' => 'Upload File', - 'attachments_link' => 'Attach Link', - 'attachments_upload_drop' => 'Alternatively you can drag and drop a file here to upload it as an attachment.', - 'attachments_set_link' => 'Set Link', - 'attachments_delete' => 'Are you sure you want to delete this attachment?', - 'attachments_dropzone' => 'Drop files here to upload', - 'attachments_no_files' => 'No files have been uploaded', - 'attachments_explain_link' => 'You can attach a link if you\'d prefer not to upload a file. This can be a link to another page or a link to a file in the cloud.', - 'attachments_link_name' => 'Link Name', - 'attachment_link' => 'Attachment link', - 'attachments_link_url' => 'Link to file', - 'attachments_link_url_hint' => 'Url of site or file', - 'attach' => 'Attach', - 'attachments_insert_link' => 'Add Attachment Link to Page', - 'attachments_edit_file' => 'Edit File', - 'attachments_edit_file_name' => 'File Name', - 'attachments_edit_drop_upload' => 'Drop files or click here to upload and overwrite', - 'attachments_order_updated' => 'Attachment order updated', - 'attachments_updated_success' => 'Attachment details updated', - 'attachments_deleted' => 'Attachment deleted', - 'attachments_file_uploaded' => 'File successfully uploaded', - 'attachments_file_updated' => 'File successfully updated', - 'attachments_link_attached' => 'Link successfully attached to page', - 'templates' => 'Templates', - 'templates_set_as_template' => 'Page is a template', - 'templates_explain_set_as_template' => 'You can set this page as a template so its contents be utilized when creating other pages. Other users will be able to use this template if they have view permissions for this page.', - 'templates_replace_content' => 'Replace page content', - 'templates_append_content' => 'Append to page content', - 'templates_prepend_content' => 'Prepend to page content', + 'toggle_sidebar' => 'Näytä/piilota sivupalkki', + 'page_tags' => 'Sivun tunnisteet', + 'chapter_tags' => 'Lukujen tunnisteet', + 'book_tags' => 'Kirjojen tunnisteet', + 'shelf_tags' => 'Hyllyjen tunnisteet', + 'tag' => 'Tunniste', + 'tags' => 'Tunnisteet', + 'tags_index_desc' => 'Järjestelmässä oleva sisältöä voidaan luokitella joustavasti tunnisteiden avulla. Tunnisteilla voi olla sekä avain että arvo. Arvo on valinnainen. Tunnisteella merkittyjä sisältöjä voidaan hakea käyttämällä tunnisteen nimeä ja arvoa.', + 'tag_name' => 'Tunnisteen nimi', + 'tag_value' => 'Tunnisteen arvo (valinnainen)', + 'tags_explain' => "Lisää tunnisteita sisällön luokittelua varten. \n Tunnisteiden arvojen avulla luokittelua voi edelleen tarkentaa.", + 'tags_add' => 'Lisää uusi tunniste', + 'tags_remove' => 'Poista tämä tunniste', + 'tags_usages' => 'Tunnisteen käyttökerrat', + 'tags_assigned_pages' => 'Lisätty sivuille', + 'tags_assigned_chapters' => 'Lisätty lukuihin', + 'tags_assigned_books' => 'Lisätty kirjoihin', + 'tags_assigned_shelves' => 'Lisätty hyllyihin', + 'tags_x_unique_values' => ':count yksilöllistä arvoa', + 'tags_all_values' => 'Kaikki arvot', + 'tags_view_tags' => 'Näytä tunnisteita', + 'tags_view_existing_tags' => 'Näytä käytetyt tunnisteet', + 'tags_list_empty_hint' => 'Tunnisteet voidaan määrittää editorin sivupalkissa tai kirjan, luvun tai hyllyn tietoja muokattaessa.', + 'attachments' => 'Liitteet', + 'attachments_explain' => 'Lataa tiedostoja tai liitä linkkejä, jotka näytetään sivulla. Nämä näkyvät sivun sivupalkissa.', + 'attachments_explain_instant_save' => 'Tässä tehdyt muutokset tallentuvat välittömästi.', + 'attachments_upload' => 'Lataa tiedosto', + 'attachments_link' => 'Liitä linkki', + 'attachments_upload_drop' => 'Vaihtoehtoisesti voit raahata ja pudottaa tiedoston tähän ladataksesi sen liitetiedostoksi.', + 'attachments_set_link' => 'Aseta linkki', + 'attachments_delete' => 'Haluatko varmasti poistaa tämän liitteen?', + 'attachments_dropzone' => 'Pudota siirrettävät tiedostot tähän', + 'attachments_no_files' => 'Yhtään tiedostoa ei ole ladattu', + 'attachments_explain_link' => 'Voit antaa linkin, jos et halua ladata tiedostoa. Se voi olla linkki toiselle sivulle tai linkki pilvipalvelussa olevaan tiedostoon.', + 'attachments_link_name' => 'Linkin nimi', + 'attachment_link' => 'Liitteen linkki', + 'attachments_link_url' => 'Linkki tiedostoon', + 'attachments_link_url_hint' => 'Sivuston tai tiedoston osoite', + 'attach' => 'Liitä', + 'attachments_insert_link' => 'Lisää liitteen linkki sivulle', + 'attachments_edit_file' => 'Muokkaa tiedostoa', + 'attachments_edit_file_name' => 'Tiedoston nimi', + 'attachments_edit_drop_upload' => 'Pudota tiedostoja tai klikkaa tästä ladataksesi ja korvataksesi', + 'attachments_order_updated' => 'Liitteiden järjestys päivitetty', + 'attachments_updated_success' => 'Liitteen tiedot päivitetty', + 'attachments_deleted' => 'Liite poistettu', + 'attachments_file_uploaded' => 'Tiedosto ladattiin onnistuneesti', + 'attachments_file_updated' => 'Tiedosto päivitettiin onnistuneesti', + 'attachments_link_attached' => 'Linkki liitettiin onnistuneesti sivulle', + 'templates' => 'Mallipohjat', + 'templates_set_as_template' => 'Sivu on mallipohja', + 'templates_explain_set_as_template' => 'Voit tehdä tästä sivusta mallipohjan, jolloin sen sisältöä voidaan käyttää muiden sivujen luomisessa. Muut käyttäjät voivat käyttää tätä mallipohjaa, jos heillä on pääsyoikeus sivuun.', + 'templates_replace_content' => 'Korvaa sivun sisältö', + 'templates_append_content' => 'Liitä sivun sisällön jatkoksi', + 'templates_prepend_content' => 'Liitä sivun sisällön alkuun', // Profile View - 'profile_user_for_x' => 'User for :time', - 'profile_created_content' => 'Created Content', - 'profile_not_created_pages' => ':userName has not created any pages', - 'profile_not_created_chapters' => ':userName has not created any chapters', - 'profile_not_created_books' => ':userName has not created any books', - 'profile_not_created_shelves' => ':userName has not created any shelves', + 'profile_user_for_x' => 'Ollut käyttäjä :time', + 'profile_created_content' => 'Luotu sisältö', + 'profile_not_created_pages' => ':userName ei ole luonut sivuja', + 'profile_not_created_chapters' => ':userName ei ole luonut lukuja', + 'profile_not_created_books' => ':userName ei ole luonut kirjoja', + 'profile_not_created_shelves' => ':userName ei ole luonut hyllyjä', // Comments - 'comment' => 'Comment', - 'comments' => 'Comments', - 'comment_add' => 'Add Comment', - 'comment_placeholder' => 'Leave a comment here', - 'comment_count' => '{0} No Comments|{1} 1 Comment|[2,*] :count Comments', - 'comment_save' => 'Save Comment', - 'comment_new' => 'New Comment', - 'comment_created' => 'commented :createDiff', - 'comment_updated' => 'Updated :updateDiff by :username', - 'comment_updated_indicator' => 'Updated', - 'comment_deleted_success' => 'Comment deleted', - 'comment_created_success' => 'Comment added', - 'comment_updated_success' => 'Comment updated', - 'comment_delete_confirm' => 'Are you sure you want to delete this comment?', - 'comment_in_reply_to' => 'In reply to :commentId', - 'comment_editor_explain' => 'Here are the comments that have been left on this page. Comments can be added & managed when viewing the saved page.', + 'comment' => 'Kommentti', + 'comments' => 'Kommentit', + 'comment_add' => 'Lisää kommentti', + 'comment_placeholder' => 'Jätä kommentti tähän', + 'comment_count' => '{0} Ei kommentteja|{1} 1 kommentti|[2,*] :count kommenttia', + 'comment_save' => 'Tallenna kommentti', + 'comment_new' => 'Uusi kommentti', + 'comment_created' => 'kommentoi :createDiff', + 'comment_updated' => 'Päivitetty :updateDiff :username toimesta', + 'comment_updated_indicator' => 'Päivitetty', + 'comment_deleted_success' => 'Kommentti poistettu', + 'comment_created_success' => 'Kommentti lisätty', + 'comment_updated_success' => 'Kommentti päivitetty', + 'comment_delete_confirm' => 'Haluatko varmasti poistaa tämän kommentin?', + 'comment_in_reply_to' => 'Vastaus kommenttiin :commentId', + 'comment_editor_explain' => 'Tässä ovat sivulle jätetyt komentit. Kommentteja voi lisätä ja hallita, kun tarkastelet tallennettua sivua.', // Revision - 'revision_delete_confirm' => 'Are you sure you want to delete this revision?', - 'revision_restore_confirm' => 'Are you sure you want to restore this revision? The current page contents will be replaced.', - 'revision_cannot_delete_latest' => 'Cannot delete the latest revision.', + 'revision_delete_confirm' => 'Haluatko varmasti poistaa tämän version?', + 'revision_restore_confirm' => 'Oletko varma, että haluat palauttaa tämän version? Sivun nykyinen sisältö korvataan.', + 'revision_cannot_delete_latest' => 'Uusinta versiota ei voi poistaa.', // Copy view - 'copy_consider' => 'Please consider the below when copying content.', - 'copy_consider_permissions' => 'Custom permission settings will not be copied.', - 'copy_consider_owner' => 'You will become the owner of all copied content.', - 'copy_consider_images' => 'Page image files will not be duplicated & the original images will retain their relation to the page they were originally uploaded to.', - 'copy_consider_attachments' => 'Page attachments will not be copied.', - 'copy_consider_access' => 'A change of location, owner or permissions may result in this content being accessible to those previously without access.', + 'copy_consider' => 'Ota huomioon seuraavat asiat sisältöä kopioidessasi.', + 'copy_consider_permissions' => 'Mukautettuja käyttöoikeusasetuksia ei kopioida.', + 'copy_consider_owner' => 'Sinusta tulee kaiken kopioidun sisällön omistaja.', + 'copy_consider_images' => 'Sivun kuvatiedostoja ei kopioida, ja alkuperäiset kuvat säilyttävät suhteensa siihen sivuun, jolle ne alun perin ladattiin.', + 'copy_consider_attachments' => 'Sivun liitteitä ei kopioida.', + 'copy_consider_access' => 'Sijainnin, omistajan tai käyttöoikeuksien vaihtuminen voi johtaa siihen, että tämä sisältö on sellaisten käyttäjien saatavilla, joilla ei ole aiemmin ollut siihen pääsyä.', // Conversions - 'convert_to_shelf' => 'Convert to Shelf', - 'convert_to_shelf_contents_desc' => 'You can convert this book to a new shelf with the same contents. Chapters contained within this book will be converted to new books. If this book contains any pages, that are not in a chapter, this book will be renamed and contain such pages, and this book will become part of the new shelf.', - 'convert_to_shelf_permissions_desc' => 'Any permissions set on this book will be copied to the new shelf and to all new child books that don\'t have their own permissions enforced. Note that permissions on shelves do not auto-cascade to content within, as they do for books.', - 'convert_book' => 'Convert Book', - 'convert_book_confirm' => 'Are you sure you want to convert this book?', - 'convert_undo_warning' => 'This cannot be as easily undone.', - 'convert_to_book' => 'Convert to Book', - 'convert_to_book_desc' => 'You can convert this chapter to a new book with the same contents. Any permissions set on this chapter will be copied to the new book but any inherited permissions, from the parent book, will not be copied which could lead to a change of access control.', - 'convert_chapter' => 'Convert Chapter', - 'convert_chapter_confirm' => 'Are you sure you want to convert this chapter?', + 'convert_to_shelf' => 'Muunna hyllyksi', + 'convert_to_shelf_contents_desc' => 'Voit muuntaa tämän kirjan uudeksi hyllyksi, jossa on sama sisältö. Kirjan sisältämät luvut muunnetaan uusiksi kirjoiksi. Jos kirja sisältää sivuja, jotka eivät kuulu mihinkään lukuun, kirja nimetään uudelleen ja siitä tulee osa uutta hyllyä, joka sisältää kyseiset sivut.', + 'convert_to_shelf_permissions_desc' => 'Kaikki tälle kirjalle asetetut käyttöoikeudet kopioidaan uuteen hyllyyn ja kaikkiin uusiin hyllyn kirjoihin, joilla ei ole omia käyttöoikeuksia. Huomaa, että hyllyjen käyttöoikeudet eivät siirry automaattisesti hyllyssä olevaan sisältöön, kuten kirjoissa.', + 'convert_book' => 'Muunna kirja', + 'convert_book_confirm' => 'Haluatko varmasti muuntaa tämän kirjan?', + 'convert_undo_warning' => 'Tätä ei voi yhtä helposti perua.', + 'convert_to_book' => 'Muunna kirjaksi', + 'convert_to_book_desc' => 'Voit muuntaa tämän luvun uudeksi kirjaksi, jonka sisältö on sama. Kaikki tälle luvulle asetetut käyttöoikeudet kopioidaan uuteen kirjaan, mutta alkuperäisestä kirjasta perittyjä käyttöoikeuksia ei kopioida, mikä voi johtaa käyttöoikeuksien muuttumiseen.', + 'convert_chapter' => 'Muunna luku', + 'convert_chapter_confirm' => 'Haluatko varmasti muuntaa tämän luvun?', // References - 'references' => 'References', - 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references' => 'Viitteet', + 'references_none' => 'Tähän kohteeseen ei ole viittauksia.', + 'references_to_desc' => 'Lista kohteeseen viittaavasta sisällöstä.', // Watch Options - 'watch' => 'Watch', - 'watch_title_default' => 'Default Preferences', - 'watch_desc_default' => 'Revert watching to just your default notification preferences.', - 'watch_title_ignore' => 'Ignore', - 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', - 'watch_title_new' => 'New Pages', - 'watch_desc_new' => 'Notify when any new page is created within this item.', - 'watch_title_updates' => 'All Page Updates', - 'watch_desc_updates' => 'Notify upon all new pages and page changes.', - 'watch_desc_updates_page' => 'Notify upon all page changes.', - 'watch_title_comments' => 'All Page Updates & Comments', - 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', - 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', - 'watch_change_default' => 'Change default notification preferences', - 'watch_detail_ignore' => 'Ignoring notifications', - 'watch_detail_new' => 'Watching for new pages', - 'watch_detail_updates' => 'Watching new pages and updates', - 'watch_detail_comments' => 'Watching new pages, updates & comments', - 'watch_detail_parent_book' => 'Watching via parent book', - 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', - 'watch_detail_parent_chapter' => 'Watching via parent chapter', - 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', + 'watch' => 'Seuraa', + 'watch_title_default' => 'Oletusasetukset', + 'watch_desc_default' => 'Palauta seuranta omiin oletusasetuksiin.', + 'watch_title_ignore' => 'Ohita', + 'watch_desc_ignore' => 'Ohita kaikki ilmoitukset, myös käyttäjäasetuksissa määritellyt ilmoitukset.', + 'watch_title_new' => 'Uudet sivut', + 'watch_desc_new' => 'Ilmoita, kun tähän kohteeseen luodaan uusi sivu.', + 'watch_title_updates' => 'Kaikki sivupäivitykset', + 'watch_desc_updates' => 'Ilmoita kaikista uusista sivuista ja sivujen muutoksista.', + 'watch_desc_updates_page' => 'Ilmoita kaikista sivujen muutoksista.', + 'watch_title_comments' => 'Kaikki sivupäivitykset ja kommentit', + 'watch_desc_comments' => 'Ilmoita kaikista uusista sivuista, sivujen muutoksista ja uusista kommenteista.', + 'watch_desc_comments_page' => 'Ilmoita sivujen muutoksista ja uusista kommenteista.', + 'watch_change_default' => 'Muuta oletusilmoitusasetuksia', + 'watch_detail_ignore' => 'Ilmoitusten ohittaminen', + 'watch_detail_new' => 'Uusien sivujen seuraaminen', + 'watch_detail_updates' => 'Uusien sivujen ja päivitysten seuraaminen', + 'watch_detail_comments' => 'Uusien sivujen, päivitysten ja kommenttien seuraaminen', + 'watch_detail_parent_book' => 'Seuraaminen kirjan perusteella', + 'watch_detail_parent_book_ignore' => 'Huomioimatta jättäminen kirjan perusteella', + 'watch_detail_parent_chapter' => 'Seuraaminen luvun perusteella', + 'watch_detail_parent_chapter_ignore' => 'Huomioimatta jättäminen luvun perusteella', ]; diff --git a/lang/fi/errors.php b/lang/fi/errors.php index 8813cf90a..082cac242 100644 --- a/lang/fi/errors.php +++ b/lang/fi/errors.php @@ -5,116 +5,116 @@ return [ // Permissions - 'permission' => 'You do not have permission to access the requested page.', - 'permissionJson' => 'You do not have permission to perform the requested action.', + 'permission' => 'Sinulla ei ole pääsyoikeutta pyydettyyn sivuun.', + 'permissionJson' => 'Sinulla ei ole oikeutta suorittaa pyydettyä toimintoa.', // Auth - 'error_user_exists_different_creds' => 'A user with the email :email already exists but with different credentials.', - 'email_already_confirmed' => 'Email has already been confirmed, Try logging in.', - 'email_confirmation_invalid' => 'This confirmation token is not valid or has already been used, Please try registering again.', - 'email_confirmation_expired' => 'The confirmation token has expired, A new confirmation email has been sent.', - 'email_confirmation_awaiting' => 'The email address for the account in use needs to be confirmed', - 'ldap_fail_anonymous' => 'LDAP access failed using anonymous bind', - 'ldap_fail_authed' => 'LDAP access failed using given dn & password details', - 'ldap_extension_not_installed' => 'LDAP PHP extension not installed', - 'ldap_cannot_connect' => 'Cannot connect to ldap server, Initial connection failed', - 'saml_already_logged_in' => 'Already logged in', - 'saml_user_not_registered' => 'The user :name is not registered and automatic registration is disabled', - 'saml_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system', - 'saml_invalid_response_id' => 'The request from the external authentication system is not recognised by a process started by this application. Navigating back after a login could cause this issue.', - 'saml_fail_authed' => 'Login using :system failed, system did not provide successful authorization', - 'oidc_already_logged_in' => 'Already logged in', - 'oidc_user_not_registered' => 'The user :name is not registered and automatic registration is disabled', - 'oidc_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system', - 'oidc_fail_authed' => 'Login using :system failed, system did not provide successful authorization', - 'social_no_action_defined' => 'No action defined', - 'social_login_bad_response' => "Error received during :socialAccount login: \n:error", - 'social_account_in_use' => 'This :socialAccount account is already in use, Try logging in via the :socialAccount option.', - 'social_account_email_in_use' => 'The email :email is already in use. If you already have an account you can connect your :socialAccount account from your profile settings.', - 'social_account_existing' => 'This :socialAccount is already attached to your profile.', - 'social_account_already_used_existing' => 'This :socialAccount account is already used by another user.', - 'social_account_not_used' => 'This :socialAccount account is not linked to any users. Please attach it in your profile settings. ', - 'social_account_register_instructions' => 'If you do not yet have an account, You can register an account using the :socialAccount option.', - 'social_driver_not_found' => 'Social driver not found', - 'social_driver_not_configured' => 'Your :socialAccount social settings are not configured correctly.', - 'invite_token_expired' => 'This invitation link has expired. You can instead try to reset your account password.', + 'error_user_exists_different_creds' => 'Sähköpostiosoite :email on jo käytössä toisessa käyttäjätunnuksessa.', + 'email_already_confirmed' => 'Sähköposti on jo vahvistettu, yritä kirjautua sisään.', + 'email_confirmation_invalid' => 'Tämä vahvistuslinkki ei ole voimassa tai sitä on jo käytetty, yritä rekisteröityä uudelleen.', + 'email_confirmation_expired' => 'Vahvistuslinkki on vanhentunut, uusi vahvistussähköposti on lähetetty.', + 'email_confirmation_awaiting' => 'Tämän tilin sähköpostiosoite pitää vahvistaa', + 'ldap_fail_anonymous' => 'Anonyymi LDAP-todennus epäonnistui', + 'ldap_fail_authed' => 'LDAP-todennus epäonnistui annetulla nimellä ja salasanalla', + 'ldap_extension_not_installed' => 'PHP:n LDAP-laajennusta ei ole asennettu', + 'ldap_cannot_connect' => 'Yhteyttä LDAP-palvelimeen ei voida muodostaa, alustava yhteys epäonnistui', + 'saml_already_logged_in' => 'Olet jo kirjautunut sisään', + 'saml_no_email_address' => 'Tämän käyttäjän sähköpostiosoitetta ei löytynyt ulkoisesta todennuspalvelusta', + 'saml_invalid_response_id' => 'Tämän sovelluksen käynnistämä prosessi ei tunnista ulkoisen todennusjärjestelmän pyyntöä. +Sovellus ei tunnista ulkoisen todennuspalvelun pyyntöä. Ongelman voi aiheuttaa siirtyminen selaimessa takaisin edelliseen näkymään kirjautumisen jälkeen.', + 'saml_fail_authed' => 'Sisäänkirjautuminen :system käyttäen epäonnistui, järjestelmä ei antanut valtuutusta', + 'oidc_already_logged_in' => 'Olet jo kirjautunut sisään', + 'oidc_user_not_registered' => 'Käyttäjää :nimi ei ole rekisteröity ja automaattinen rekisteröinti on pois käytöstä', + 'oidc_no_email_address' => 'Ulkoisen todennuspalvelun antamista tiedoista ei löytynyt tämän käyttäjän sähköpostiosoitetta', + 'oidc_fail_authed' => 'Sisäänkirjautuminen :system käyttäen epäonnistui, järjestelmä ei antanut valtuutusta', + 'social_no_action_defined' => 'Ei määriteltyä toimenpidettä', + 'social_login_bad_response' => "Virhe :socialAccount-kirjautumisen aikana: \n:error", + 'social_account_in_use' => 'Tämä :socialAccount-tili on jo käytössä, yritä kirjautua sisään :socialAccount-vaihtoehdon kautta.', + 'social_account_email_in_use' => 'Sähköposti :email on jo käytössä. Jos sinulla on jo sivustolla käyttäjätili, voit yhdistää :socialAccount-tilisi profiiliasetuksista.', + 'social_account_existing' => 'Tämä :socialAccount-tili on jo liitetty profiiliisi.', + 'social_account_already_used_existing' => 'Tämä :socialAccount-tili on jo toisen käyttäjän käytössä.', + 'social_account_not_used' => 'Tätä :socialAccount-tiliä ei ole liitetty mihinkään käyttäjään. Voit liittää sen profiiliasetuksistasi. ', + 'social_account_register_instructions' => 'Jos sinulla ei vielä ole käyttäjätiliä, voit rekisteröidä tilin käyttämällä :socialAccount-vaihtoehtoa.', + 'social_driver_not_found' => 'Sosiaalisen median tilin ajuria ei löytynyt', + 'social_driver_not_configured' => ':socialAccount-tilin asetuksia ei ole määritetty oikein.', + 'invite_token_expired' => 'Tämä kutsulinkki on vanhentunut. Voit sen sijaan yrittää palauttaa tilisi salasanan.', // System - 'path_not_writable' => 'File path :filePath could not be uploaded to. Ensure it is writable to the server.', - 'cannot_get_image_from_url' => 'Cannot get image from :url', - 'cannot_create_thumbs' => 'The server cannot create thumbnails. Please check you have the GD PHP extension installed.', - 'server_upload_limit' => 'The server does not allow uploads of this size. Please try a smaller file size.', - 'server_post_limit' => 'The server cannot receive the provided amount of data. Try again with less data or a smaller file.', - 'uploaded' => 'The server does not allow uploads of this size. Please try a smaller file size.', + 'path_not_writable' => 'Tiedostopolkuun :filePath ei voitu ladata tiedostoa. Tarkista polun kirjoitusoikeudet.', + 'cannot_get_image_from_url' => 'Kuvan hakeminen osoitteesta :url ei onnistu', + 'cannot_create_thumbs' => 'Palvelin ei voi luoda pikkukuvia. Tarkista, että PHP:n GD-laajennus on asennettu.', + 'server_upload_limit' => 'Palvelin ei salli näin suuria tiedostoja. Kokeile pienempää tiedostokokoa.', + 'server_post_limit' => 'Palvelin ei pysty vastaanottamaan annettua tietomäärää. Yritä uudelleen pienemmällä tiedostolla.', + 'uploaded' => 'Palvelin ei salli näin suuria tiedostoja. Kokeile pienempää tiedostokokoa.', // Drawing & Images - 'image_upload_error' => 'An error occurred uploading the image', - 'image_upload_type_error' => 'The image type being uploaded is invalid', - 'image_upload_replace_type' => 'Image file replacements must be of the same type', - 'image_upload_memory_limit' => 'Failed to handle image upload and/or create thumbnails due to system resource limits.', - 'image_thumbnail_memory_limit' => 'Failed to create image size variations due to system resource limits.', - 'image_gallery_thumbnail_memory_limit' => 'Failed to create gallery thumbnails due to system resource limits.', - 'drawing_data_not_found' => 'Drawing data could not be loaded. The drawing file might no longer exist or you may not have permission to access it.', + 'image_upload_error' => 'Kuvan lataamisessa tapahtui virhe', + 'image_upload_type_error' => 'Ladattavan kuvan tyyppi on virheellinen', + 'image_upload_replace_type' => 'Korvaavan kuvatiedoston tulee olla samaa tyyppiä kuin alkuperäinen kuva', + 'image_upload_memory_limit' => 'Kuvan lataaminen ja/tai pikkukuvien luominen epäonnistui järjestelmän resurssirajoitusten vuoksi.', + 'image_thumbnail_memory_limit' => 'Kuvan kokovaihtoehtojen luominen epäonnistui järjestelmän resurssirajoitusten vuoksi.', + 'image_gallery_thumbnail_memory_limit' => 'Gallerian pikkukuvien luominen epäonnistui järjestelmän resurssirajoitusten vuoksi.', + 'drawing_data_not_found' => 'Piirustuksen tietoja ei voitu ladata. Piirustustiedostoa ei ehkä ole enää olemassa tai sinulla ei ole oikeutta käyttää sitä.', // Attachments - 'attachment_not_found' => 'Attachment not found', - 'attachment_upload_error' => 'An error occurred uploading the attachment file', + 'attachment_not_found' => 'Liitettä ei löytynyt', + 'attachment_upload_error' => 'Liitteen lataamisessa tapahtui virhe', // Pages - 'page_draft_autosave_fail' => 'Failed to save draft. Ensure you have internet connection before saving this page', - 'page_draft_delete_fail' => 'Failed to delete page draft and fetch current page saved content', - 'page_custom_home_deletion' => 'Cannot delete a page while it is set as a homepage', + 'page_draft_autosave_fail' => 'Luonnoksen tallentaminen epäonnistui. Varmista, että sinulla on toimiva internetyhteys ennen sivun tallentamista', + 'page_draft_delete_fail' => 'Luonnoksen poistaminen ja sivun tallennetun sisällön noutaminen epäonnistui', + 'page_custom_home_deletion' => 'Sivua ei voi poistaa, koska se on asetettu etusivuksi', // Entities - 'entity_not_found' => 'Entity not found', - 'bookshelf_not_found' => 'Shelf not found', - 'book_not_found' => 'Book not found', - 'page_not_found' => 'Page not found', - 'chapter_not_found' => 'Chapter not found', - 'selected_book_not_found' => 'The selected book was not found', - 'selected_book_chapter_not_found' => 'The selected Book or Chapter was not found', - 'guests_cannot_save_drafts' => 'Guests cannot save drafts', + 'entity_not_found' => 'Kohdetta ei löydy', + 'bookshelf_not_found' => 'Hyllyä ei löytynyt', + 'book_not_found' => 'Kirjaa ei löytynyt', + 'page_not_found' => 'Sivua ei löytynyt', + 'chapter_not_found' => 'Lukua ei löytynyt', + 'selected_book_not_found' => 'Valittua kirjaa ei löytynyt', + 'selected_book_chapter_not_found' => 'Valittua kirjaa tai lukua ei löytynyt', + 'guests_cannot_save_drafts' => 'Vieraat eivät voi tallentaa luonnoksia', // Users - 'users_cannot_delete_only_admin' => 'You cannot delete the only admin', - 'users_cannot_delete_guest' => 'You cannot delete the guest user', + 'users_cannot_delete_only_admin' => 'Ainoaa ylläpitäjää ei voi poistaa', + 'users_cannot_delete_guest' => 'Vieraskäyttäjää ei voi poistaa', // Roles - 'role_cannot_be_edited' => 'This role cannot be edited', - 'role_system_cannot_be_deleted' => 'This role is a system role and cannot be deleted', - 'role_registration_default_cannot_delete' => 'This role cannot be deleted while set as the default registration role', - 'role_cannot_remove_only_admin' => 'This user is the only user assigned to the administrator role. Assign the administrator role to another user before attempting to remove it here.', + 'role_cannot_be_edited' => 'Tätä roolia ei voi muokata', + 'role_system_cannot_be_deleted' => 'Tämä rooli on järjestelmärooli, eikä sitä voi poistaa', + 'role_registration_default_cannot_delete' => 'Tätä roolia ei voi poistaa, kun se on asetettu oletusrooliksi uusille rekisteröityville käyttäjille', + 'role_cannot_remove_only_admin' => 'Tämä käyttäjä on ainoa käyttäjä, jolle on määritetty ylläpitäjän rooli. Määritä ylläpitäjän rooli toiselle käyttäjälle, ennen kuin yrität poistaa tämän käyttäjän.', // Comments - 'comment_list' => 'An error occurred while fetching the comments.', - 'cannot_add_comment_to_draft' => 'You cannot add comments to a draft.', - 'comment_add' => 'An error occurred while adding / updating the comment.', - 'comment_delete' => 'An error occurred while deleting the comment.', - 'empty_comment' => 'Cannot add an empty comment.', + 'comment_list' => 'Kommenttien noutamisessa tapahtui virhe.', + 'cannot_add_comment_to_draft' => 'Luonnokseen ei voi lisätä kommentteja.', + 'comment_add' => 'Kommentin lisäämisessä tai päivittämisessä tapahtui virhe.', + 'comment_delete' => 'Kommentin poistamisessa tapahtui virhe.', + 'empty_comment' => 'Tyhjää kommenttia ei voi lisätä.', // Error pages - '404_page_not_found' => 'Page Not Found', - 'sorry_page_not_found' => 'Sorry, The page you were looking for could not be found.', - 'sorry_page_not_found_permission_warning' => 'If you expected this page to exist, you might not have permission to view it.', - 'image_not_found' => 'Image Not Found', - 'image_not_found_subtitle' => 'Sorry, The image file you were looking for could not be found.', - 'image_not_found_details' => 'If you expected this image to exist it might have been deleted.', - 'return_home' => 'Return to home', - 'error_occurred' => 'An Error Occurred', - 'app_down' => ':appName is down right now', - 'back_soon' => 'It will be back up soon.', + '404_page_not_found' => 'Sivua ei löydy', + 'sorry_page_not_found' => 'Valitettavasti etsimääsi sivua ei löytynyt.', + 'sorry_page_not_found_permission_warning' => 'Jos oletit, että tämä sivu on olemassa, sinulla ei ehkä ole lupaa tarkastella sitä.', + 'image_not_found' => 'Kuvaa ei löytynyt', + 'image_not_found_subtitle' => 'Valitettavasti etsimääsi kuvatiedostoa ei löytynyt.', + 'image_not_found_details' => 'Jos oletit, että tämä kuva on olemassa, se on ehkä poistettu.', + 'return_home' => 'Palaa etusivulle', + 'error_occurred' => 'Tapahtui virhe', + 'app_down' => ':appName on kaatunut', + 'back_soon' => 'Se palautetaan pian.', // API errors - 'api_no_authorization_found' => 'No authorization token found on the request', - 'api_bad_authorization_format' => 'An authorization token was found on the request but the format appeared incorrect', - 'api_user_token_not_found' => 'No matching API token was found for the provided authorization token', - 'api_incorrect_token_secret' => 'The secret provided for the given used API token is incorrect', - 'api_user_no_api_permission' => 'The owner of the used API token does not have permission to make API calls', - 'api_user_token_expired' => 'The authorization token used has expired', + 'api_no_authorization_found' => 'Pyynnöstä ei löytynyt valtuutuskoodia', + 'api_bad_authorization_format' => 'Pyynnöstä löytyi valtuutuskoodi, mutta sen muoto oli virheellinen', + 'api_user_token_not_found' => 'Annetulle valtuutuskoodille ei löytynyt vastaavaa API-tunnistetta', + 'api_incorrect_token_secret' => 'API-tunnisteelle annettu salainen avain on virheellinen', + 'api_user_no_api_permission' => 'Käytetyn API-tunnisteen omistajalla ei ole oikeutta tehdä API-kutsuja', + 'api_user_token_expired' => 'Käytetty valtuutuskoodi on vanhentunut', // Settings & Maintenance - 'maintenance_test_email_failure' => 'Error thrown when sending a test email:', + 'maintenance_test_email_failure' => 'Virhe testisähköpostia lähetettäessä:', // HTTP errors - 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', + 'http_ssr_url_no_match' => 'URL-osoite ei vastaa määritettyjä sallittuja SSR-isäntiä', ]; diff --git a/lang/fi/notifications.php b/lang/fi/notifications.php index 5539ae9a9..a737ad7d9 100644 --- a/lang/fi/notifications.php +++ b/lang/fi/notifications.php @@ -4,23 +4,24 @@ */ return [ - 'new_comment_subject' => 'New comment on page: :pageName', - 'new_comment_intro' => 'A user has commented on a page in :appName:', - 'new_page_subject' => 'New page: :pageName', - 'new_page_intro' => 'A new page has been created in :appName:', - 'updated_page_subject' => 'Updated page: :pageName', - 'updated_page_intro' => 'A page has been updated in :appName:', - 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'new_comment_subject' => 'Uusi kommentti sivulla: :pageName', + 'new_comment_intro' => 'Käyttäjä on kommentoinut sivua sivustolla :appName:', + 'new_page_subject' => 'Uusi sivu: :pageName', + 'new_page_intro' => 'Uusi sivu on luotu sivustolla :appName:', + 'updated_page_subject' => 'Päivitetty sivu: :pageName', + 'updated_page_intro' => 'Sivu on päivitetty sivustolla :appName:', + 'updated_page_debounce' => 'Useiden ilmoitusten välttämiseksi sinulle ei toistaiseksi lähetetä ilmoituksia saman toimittajan tekemistä uusista muokkauksista tälle sivulle.', - 'detail_page_name' => 'Page Name:', - 'detail_commenter' => 'Commenter:', - 'detail_comment' => 'Comment:', - 'detail_created_by' => 'Created By:', - 'detail_updated_by' => 'Updated By:', + 'detail_page_name' => 'Sivun nimi:', + 'detail_page_path' => 'Sivun polku:', + 'detail_commenter' => 'Kommentoija:', + 'detail_comment' => 'Kommentti:', + 'detail_created_by' => 'Luonut', + 'detail_updated_by' => 'Päivittänyt', - 'action_view_comment' => 'View Comment', - 'action_view_page' => 'View Page', + 'action_view_comment' => 'Näytä kommentti', + 'action_view_page' => 'Näytä sivu', - 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', - 'footer_reason_link' => 'your notification preferences', + 'footer_reason' => 'Tämä ilmoitus lähetettiin sinulle, koska :link kattaa tämän tyyppisen toiminnan tälle kohteelle.', + 'footer_reason_link' => 'omat ilmoitusasetukset', ]; diff --git a/lang/fi/pagination.php b/lang/fi/pagination.php index 85bd12fc3..ebd8085c2 100644 --- a/lang/fi/pagination.php +++ b/lang/fi/pagination.php @@ -6,7 +6,7 @@ */ return [ - 'previous' => '« Previous', - 'next' => 'Next »', + 'previous' => '« Edellinen', + 'next' => 'Seuraava »', ]; diff --git a/lang/fi/passwords.php b/lang/fi/passwords.php index b408f3c2f..86e4e189d 100644 --- a/lang/fi/passwords.php +++ b/lang/fi/passwords.php @@ -6,10 +6,10 @@ */ return [ - 'password' => 'Passwords must be at least eight characters and match the confirmation.', - 'user' => "We can't find a user with that e-mail address.", - 'token' => 'The password reset token is invalid for this email address.', - 'sent' => 'We have e-mailed your password reset link!', - 'reset' => 'Your password has been reset!', + 'password' => 'Salasanan on oltava vähintään kahdeksan merkkiä pitkä ja täsmättävä vahvistuksen kanssa.', + 'user' => "Emme löydä käyttäjää, jolla on kyseinen sähköpostiosoite.", + 'token' => 'Salasanan palautuslinkki ei täsmää sähköpostin kanssa.', + 'sent' => 'Olemme lähettäneet salasanasi palautuslinkin sähköpostitse!', + 'reset' => 'Salasanasi on palautettu!', ]; diff --git a/lang/fi/preferences.php b/lang/fi/preferences.php index 2b88f9671..25cd76bf7 100644 --- a/lang/fi/preferences.php +++ b/lang/fi/preferences.php @@ -5,47 +5,47 @@ */ return [ - 'my_account' => 'My Account', + 'my_account' => 'Oma tili', - 'shortcuts' => 'Shortcuts', - 'shortcuts_interface' => 'UI Shortcut Preferences', - 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', - 'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.', - 'shortcuts_toggle_label' => 'Keyboard shortcuts enabled', - 'shortcuts_section_navigation' => 'Navigation', - 'shortcuts_section_actions' => 'Common Actions', - 'shortcuts_save' => 'Save Shortcuts', - 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', - 'shortcuts_update_success' => 'Shortcut preferences have been updated!', - 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + 'shortcuts' => 'Pikanäppäimet', + 'shortcuts_interface' => 'Käyttöliittymän pikanäppäinten asetukset', + 'shortcuts_toggle_desc' => 'Tästä voit ottaa käyttöön tai poistaa käytöstä järjestelmän käyttöliittymän pikanäppäimet, joita käytetään navigointiin ja toimintoihin.', + 'shortcuts_customize_desc' => 'Voit mukauttaa alla olevia pikanäppäimiä. Paina haluamaasi näppäinyhdistelmää sen jälkeen, kun olet valinnut pikanäppäimen syöttökentän.', + 'shortcuts_toggle_label' => 'Pikanäppäimet käytössä', + 'shortcuts_section_navigation' => 'Navigointi', + 'shortcuts_section_actions' => 'Yleiset toiminnot', + 'shortcuts_save' => 'Tallenna pikanäppäimet', + 'shortcuts_overlay_desc' => 'Huomautus: kun pikanäppäimet ovat käytössä, voit painaa "?"-näppäintä, joka korostaa näytöllä näkyviin toimintoihin liitetyt pikanäppäimet.', + 'shortcuts_update_success' => 'Pikanäppäinten asetukset on päivitetty!', + 'shortcuts_overview_desc' => 'Hallitse pikanäppäimiä, joilla voit navigoida järjestelmän käyttöliittymässä.', - 'notifications' => 'Notification Preferences', - 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', - 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', - 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', - 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', - 'notifications_save' => 'Save Preferences', - 'notifications_update_success' => 'Notification preferences have been updated!', - 'notifications_watched' => 'Watched & Ignored Items', - 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + 'notifications' => 'Ilmoitusasetukset', + 'notifications_desc' => 'Hallitse järjestelmän toimintoihin liittyviä sähköposti-ilmoituksia.', + 'notifications_opt_own_page_changes' => 'Ilmoita omistamilleni sivuille tehdyistä muutoksista', + 'notifications_opt_own_page_comments' => 'Ilmoita omistamilleni sivuille tehdyistä kommenteista', + 'notifications_opt_comment_replies' => 'Ilmoita vastauksista kommentteihini', + 'notifications_save' => 'Tallenna asetukset', + 'notifications_update_success' => 'Ilmoitusasetukset on päivitetty!', + 'notifications_watched' => 'Seuratut ja huomiotta jätetyt kohteet', + 'notifications_watched_desc' => 'Alla oleviin kohteisiin sovelletaan muokautettuja seuranta-asetuksia. Voit päivittää kohteita koskevat asetukset avaamalla kohde ja valitsemalla seuranta-asetukset sivupalkista.', - 'auth' => 'Access & Security', - 'auth_change_password' => 'Change Password', - 'auth_change_password_desc' => 'Change the password you use to log-in to the application. This must be at least 8 characters long.', - 'auth_change_password_success' => 'Password has been updated!', + 'auth' => 'Pääsy ja turvallisuus', + 'auth_change_password' => 'Vaihda salasana', + 'auth_change_password_desc' => 'Vaihda kirjautumiseen käytettävä salasana. Salasanan on oltava vähintään 8 merkkiä pitkä.', + 'auth_change_password_success' => 'Salasana on päivitetty!', - 'profile' => 'Profile Details', - 'profile_desc' => 'Manage the details of your account which represents you to other users, in addition to details that are used for communication and system personalisation.', - 'profile_view_public' => 'View Public Profile', - 'profile_name_desc' => 'Configure your display name which will be visible to other users in the system through the activity you perform, and content you own.', - 'profile_email_desc' => 'This email will be used for notifications and, depending on active system authentication, system access.', - 'profile_email_no_permission' => 'Unfortunately you don\'t have permission to change your email address. If you want to change this, you\'d need to ask an administrator to change this for you.', - 'profile_avatar_desc' => 'Select an image which will be used to represent yourself to others in the system. Ideally this image should be square and about 256px in width and height.', - 'profile_admin_options' => 'Administrator Options', - 'profile_admin_options_desc' => 'Additional administrator-level options, like those to manage role assignments, can be found for your user account in the "Settings > Users" area of the application.', + 'profile' => 'Profiilitiedot', + 'profile_desc' => 'Hallitse muille käyttäjille näkyviä käyttäjätilisi tietoja, sekä tietoja, joita käytetään viestintään ja järjestelmän personointiin.', + 'profile_view_public' => 'Näytä julkinen profiili', + 'profile_name_desc' => 'Määritä näyttönimesi, joka näkyy muille käyttäjille järjestelmässä suorittamiesi toimintojen ja omistamasi sisällön yhteydessä.', + 'profile_email_desc' => 'Tätä sähköpostia käytetään ilmoituksiin ja käytössä olevasta tunnistautumistavasta riippuen järjestelmään pääsyyn.', + 'profile_email_no_permission' => 'Valitettavasti sinulla ei ole lupaa muuttaa sähköpostiosoitettasi. Jos haluat muuttaa sen, sinun on pyydettävä ylläpitäjää muuttamaan se puolestasi.', + 'profile_avatar_desc' => 'Valitse kuva, joka näkyy järjestelmän muille käyttäjille. Kuvan tulisi olla neliön muotoinen ja leveydeltään ja korkeudeltaan noin 256 pikseliä.', + 'profile_admin_options' => 'Ylläpitäjän asetukset', + 'profile_admin_options_desc' => 'Ylläpitäjän lisäasetukset, kuten roolien osoittamiseen liittyvät valinnat, löytyvät käyttäjätiliäsi varten järjestelmän kohdasta "Asetukset > Käyttäjät".', - 'delete_account' => 'Delete Account', - 'delete_my_account' => 'Delete My Account', - 'delete_my_account_desc' => 'This will fully delete your user account from the system. You will not be able to recover this account or revert this action. Content you\'ve created, such as created pages and uploaded images, will remain.', - 'delete_my_account_warning' => 'Are you sure you want to delete your account?', + 'delete_account' => 'Poista käyttäjätili', + 'delete_my_account' => 'Poista oma käyttäjätili', + 'delete_my_account_desc' => 'Tämä poistaa käyttäjätilisi kokonaan järjestelmästä. Et voi palauttaa tiliäsi tai peruuttaa tätä toimenpidettä. Luomasi sisältö, kuten luodut sivut ja ladatut kuvat, säilyvät.', + 'delete_my_account_warning' => 'Haluatko varmasti poistaa käyttäjätilisi?', ]; diff --git a/lang/fi/settings.php b/lang/fi/settings.php index c5ca662c3..03517b6e5 100644 --- a/lang/fi/settings.php +++ b/lang/fi/settings.php @@ -7,274 +7,274 @@ return [ // Common Messages - 'settings' => 'Settings', - 'settings_save' => 'Save Settings', - 'system_version' => 'System Version', - 'categories' => 'Categories', + 'settings' => 'Asetukset', + 'settings_save' => 'Tallenna asetukset', + 'system_version' => 'Järjestelmän versio', + 'categories' => 'Kategoriat', // App Settings - 'app_customization' => 'Customization', - 'app_features_security' => 'Features & Security', - 'app_name' => 'Application Name', - 'app_name_desc' => 'This name is shown in the header and in any system-sent emails.', - 'app_name_header' => 'Show name in header', - 'app_public_access' => 'Public Access', - 'app_public_access_desc' => 'Enabling this option will allow visitors, that are not logged-in, to access content in your BookStack instance.', - 'app_public_access_desc_guest' => 'Access for public visitors can be controlled through the "Guest" user.', - 'app_public_access_toggle' => 'Allow public access', - 'app_public_viewing' => 'Allow public viewing?', - 'app_secure_images' => 'Higher Security Image Uploads', - 'app_secure_images_toggle' => 'Enable higher security image uploads', - 'app_secure_images_desc' => 'For performance reasons, all images are public. This option adds a random, hard-to-guess string in front of image urls. Ensure directory indexes are not enabled to prevent easy access.', - 'app_default_editor' => 'Default Page Editor', - 'app_default_editor_desc' => 'Select which editor will be used by default when editing new pages. This can be overridden at a page level where permissions allow.', - 'app_custom_html' => 'Custom HTML Head Content', - 'app_custom_html_desc' => 'Any content added here will be inserted into the bottom of the section of every page. This is handy for overriding styles or adding analytics code.', - 'app_custom_html_disabled_notice' => 'Custom HTML head content is disabled on this settings page to ensure any breaking changes can be reverted.', - 'app_logo' => 'Application Logo', - 'app_logo_desc' => 'This is used in the application header bar, among other areas. This image should be 86px in height. Large images will be scaled down.', - 'app_icon' => 'Application Icon', - 'app_icon_desc' => 'This icon is used for browser tabs and shortcut icons. This should be a 256px square PNG image.', - 'app_homepage' => 'Application Homepage', - 'app_homepage_desc' => 'Select a view to show on the homepage instead of the default view. Page permissions are ignored for selected pages.', - 'app_homepage_select' => 'Select a page', - 'app_footer_links' => 'Footer Links', - 'app_footer_links_desc' => 'Add links to show within the site footer. These will be displayed at the bottom of most pages, including those that do not require login. You can use a label of "trans::" to use system-defined translations. For example: Using "trans::common.privacy_policy" will provide the translated text "Privacy Policy" and "trans::common.terms_of_service" will provide the translated text "Terms of Service".', - 'app_footer_links_label' => 'Link Label', - 'app_footer_links_url' => 'Link URL', - 'app_footer_links_add' => 'Add Footer Link', - 'app_disable_comments' => 'Disable Comments', - 'app_disable_comments_toggle' => 'Disable comments', - 'app_disable_comments_desc' => 'Disables comments across all pages in the application.
Existing comments are not shown.', + 'app_customization' => 'Mukauttaminen', + 'app_features_security' => 'Ominaisuudet ja turvallisuus', + 'app_name' => 'Sivuston nimi', + 'app_name_desc' => 'Tämä nimi näkyy ylätunnisteessa ja kaikissa järjestelmän lähettämissä sähköpostiviesteissä.', + 'app_name_header' => 'Näytä nimi ylätunnisteessa', + 'app_public_access' => 'Julkinen pääsy', + 'app_public_access_desc' => 'Ottamalla tämä asetus käyttöön vierailijat voivat lukea sisältöjä kirjautumatta sisään.', + 'app_public_access_desc_guest' => 'Vierailijoiden pääsyoikeuksia voidaan hallinnoida "Vierailija"-käyttäjän asetuksista.', + 'app_public_access_toggle' => 'Salli julkinen pääsy', + 'app_public_viewing' => 'Salli julkinen katselu?', + 'app_secure_images' => 'Turvallisemmat kuvien lataukset', + 'app_secure_images_toggle' => 'Ota käyttöön turvallisemmat kuvien lataukset', + 'app_secure_images_desc' => 'Paremman suorituskyvyn takia kaikki kuvat ovat julkisia. Tämä asetus lisää satunnaisen, vaikeasti arvattavan merkkijonon kuvien url-osoitteisiin. Varmista, että hakemistojen indeksit eivät ole palvelimen asetuksissa päällä, jotta niitä ei pääse selaamaan.', + 'app_default_editor' => 'Sivujen oletuseditori', + 'app_default_editor_desc' => 'Valitse editori, jota käytetään oletuksena uusia sivuja muokattaessa. Valinnan voi ohittaa sivutasolla, jos käyttäjän oikeudet sallivat sen.', + 'app_custom_html' => 'HTML-otsakkeen mukautettu sisältö', + 'app_custom_html_desc' => 'Tähän annettu sisältö lisätään jokaisen sivun -osan loppuun. Tällä tavalla voit lisätä kätevästi esimerkiksi omia CSS-tyylejä tai analytiikkapalveluiden vaatimia koodeja.', + 'app_custom_html_disabled_notice' => 'Mukautettu HTML-otsakkeen sisältö ei ole käytössä tällä asetussivulla, jotta kaikki virheitä aiheuttavat muutokset voidaan poistaa.', + 'app_logo' => 'Sivuston logo', + 'app_logo_desc' => 'Kuvaa käytetään esimerkiksi sivuston otsikkopalkissa. Kuvan korkeuden tulisi olla 86 pikseliä. Suuremmat kuvat skaalataan pienemmiksi.', + 'app_icon' => 'Sivuston kuvake', + 'app_icon_desc' => 'Kuvaketta käytetään selaimen välilehdissä ja pikakuvakkeissa. Kuvakkeen tulisi olla 256 pikselin neliönmuotoinen PNG-kuva.', + 'app_homepage' => 'Sivuston kotisivu', + 'app_homepage_desc' => 'Valitse näkymä, joka näytetään etusivuna oletusnäkymän sijaan. Sivun käyttöoikeuksia ei oteta huomioon valituilla sivuilla.', + 'app_homepage_select' => 'Valitse sivu', + 'app_footer_links' => 'Alatunnisteen linkit', + 'app_footer_links_desc' => 'Lisää linkkejä sivuston alatunnisteeseen. Nämä näkyvät useimpien sivujen alareunassa, myös niiden, jotka eivät vaadi kirjautumista. Voit käyttää merkintää "trans::" käyttääksesi järjestelmän määrittelemiä käännöksiä. Esimerkiksi käyttämällä "trans::common.privacy_policy" saadaan käännetty teksti "Tietosuojaseloste" ja "trans::common.terms_of_service" saadaan käännetty teksti "Palvelun käyttöehdot".', + 'app_footer_links_label' => 'Linkin nimi', + 'app_footer_links_url' => 'Linkin URL-osoite', + 'app_footer_links_add' => 'Lisää alatunnisteen linkki', + 'app_disable_comments' => 'Poista kommentit käytöstä', + 'app_disable_comments_toggle' => 'Poista kommentit käytöstä', + 'app_disable_comments_desc' => 'Poistaa kommentit käytöstä kaikilla sivuilla.
Lisättyjä kommentteja ei näytetä.', // Color settings - 'color_scheme' => 'Application Color Scheme', - 'color_scheme_desc' => 'Set the colors to use in the application user interface. Colors can be configured separately for dark and light modes to best fit the theme and ensure legibility.', - 'ui_colors_desc' => 'Set the application primary color and default link color. The primary color is mainly used for the header banner, buttons and interface decorations. The default link color is used for text-based links and actions, both within written content and in the application interface.', - 'app_color' => 'Primary Color', - 'link_color' => 'Default Link Color', - 'content_colors_desc' => 'Set colors for all elements in the page organisation hierarchy. Choosing colors with a similar brightness to the default colors is recommended for readability.', - 'bookshelf_color' => 'Shelf Color', - 'book_color' => 'Book Color', - 'chapter_color' => 'Chapter Color', - 'page_color' => 'Page Color', - 'page_draft_color' => 'Page Draft Color', + 'color_scheme' => 'Sivuston värimalli', + 'color_scheme_desc' => 'Määritä sivuston käyttöliittymässä käytettävät värit. Värit voidaan määrittää erikseen tummalle ja vaalealle tilalle, jotta ne sopivat parhaiten teemaan ja varmistavat luettavuuden.', + 'ui_colors_desc' => 'Aseta sivuston pääväri ja linkin oletusväri. Ensisijaista väriä käytetään pääasiassa yläpalkissa, painikkeissa ja käyttöliittymän koristeissa. Linkin oletusväriä käytetään tekstipohjaisissa linkeissä ja toiminnoissa sekä kirjoitetussa sisällössä että sivuston käyttöliittymässä.', + 'app_color' => 'Pääväri', + 'link_color' => 'Linkin oletusväri', + 'content_colors_desc' => 'Määritä eri sisältötyyppien värit. Luettavuuden ja saavutettavuuden kannalta on suositeltavaa valita värit, joiden kirkkaus on samankaltainen kuin oletusvärien.', + 'bookshelf_color' => 'Hyllyn väri', + 'book_color' => 'Kirjan väri', + 'chapter_color' => 'Luvun väri', + 'page_color' => 'Sivun väri', + 'page_draft_color' => 'Luonnoksen väri', // Registration Settings - 'reg_settings' => 'Registration', - 'reg_enable' => 'Enable Registration', - 'reg_enable_toggle' => 'Enable registration', - 'reg_enable_desc' => 'When registration is enabled user will be able to sign themselves up as an application user. Upon registration they are given a single, default user role.', - 'reg_default_role' => 'Default user role after registration', - 'reg_enable_external_warning' => 'The option above is ignored while external LDAP or SAML authentication is active. User accounts for non-existing members will be auto-created if authentication, against the external system in use, is successful.', - 'reg_email_confirmation' => 'Email Confirmation', - 'reg_email_confirmation_toggle' => 'Require email confirmation', - 'reg_confirm_email_desc' => 'If domain restriction is used then email confirmation will be required and this option will be ignored.', - 'reg_confirm_restrict_domain' => 'Domain Restriction', - 'reg_confirm_restrict_domain_desc' => 'Enter a comma separated list of email domains you would like to restrict registration to. Users will be sent an email to confirm their address before being allowed to interact with the application.
Note that users will be able to change their email addresses after successful registration.', - 'reg_confirm_restrict_domain_placeholder' => 'No restriction set', + 'reg_settings' => 'Rekisteröityminen', + 'reg_enable' => 'Salli rekisteröityminen', + 'reg_enable_toggle' => 'Salli rekisteröityminen', + 'reg_enable_desc' => 'Kun rekisteröityminen on käytössä, vierailijat voivat rekisteröityä sivuston käyttäjiksi. Rekisteröitymisen yhteydessä heille annetaan oletuskäyttäjärooli.', + 'reg_default_role' => 'Oletuskäyttäjärooli rekisteröitymisen jälkeen', + 'reg_enable_external_warning' => 'Yllä olevaa vaihtoehtoa ei oteta huomioon, kun ulkoinen LDAP- tai SAML-todennus on käytössä. Käyttäjätilit luodaan automaattisesti, jos tunnistautuminen käytössä olevaan ulkoiseen järjestelmään onnistuu.', + 'reg_email_confirmation' => 'Sähköpostivahvistus', + 'reg_email_confirmation_toggle' => 'Vaadi sähköpostivahvistus', + 'reg_confirm_email_desc' => 'Jos domain-rajoitus on käytössä, sähköpostivahvistus on oletuksena päällä, eikä tätä valintaa oteta huomioon.', + 'reg_confirm_restrict_domain' => 'Domain-rajoitus', + 'reg_confirm_restrict_domain_desc' => 'Kirjoita pilkulla erotettu luettelo sähköpostien domain-nimistä, joihin haluat rajoittaa rekisteröitymisen. Käyttäjille lähetetään sähköpostiviesti osoitteen vahvistamiseksi, ennen kuin he pääsevät käyttämään sivustoa.
Huomaa, että käyttäjät voivat muuttaa sähköpostiosoitteensa onnistuneen rekisteröinnin jälkeen.', + 'reg_confirm_restrict_domain_placeholder' => 'Ei rajoituksia', // Maintenance settings - 'maint' => 'Maintenance', - 'maint_image_cleanup' => 'Cleanup Images', - 'maint_image_cleanup_desc' => 'Scans page & revision content to check which images and drawings are currently in use and which images are redundant. Ensure you create a full database and image backup before running this.', - 'maint_delete_images_only_in_revisions' => 'Also delete images that only exist in old page revisions', - 'maint_image_cleanup_run' => 'Run Cleanup', - 'maint_image_cleanup_warning' => ':count potentially unused images were found. Are you sure you want to delete these images?', - 'maint_image_cleanup_success' => ':count potentially unused images found and deleted!', - 'maint_image_cleanup_nothing_found' => 'No unused images found, Nothing deleted!', - 'maint_send_test_email' => 'Send a Test Email', - 'maint_send_test_email_desc' => 'This sends a test email to your email address specified in your profile.', - 'maint_send_test_email_run' => 'Send test email', - 'maint_send_test_email_success' => 'Email sent to :address', - 'maint_send_test_email_mail_subject' => 'Test Email', - 'maint_send_test_email_mail_greeting' => 'Email delivery seems to work!', - 'maint_send_test_email_mail_text' => 'Congratulations! As you received this email notification, your email settings seem to be configured properly.', - 'maint_recycle_bin_desc' => 'Deleted shelves, books, chapters & pages are sent to the recycle bin so they can be restored or permanently deleted. Older items in the recycle bin may be automatically removed after a while depending on system configuration.', - 'maint_recycle_bin_open' => 'Open Recycle Bin', - 'maint_regen_references' => 'Regenerate References', - 'maint_regen_references_desc' => 'This action will rebuild the cross-item reference index within the database. This is usually handled automatically but this action can be useful to index old content or content added via unofficial methods.', - 'maint_regen_references_success' => 'Reference index has been regenerated!', - 'maint_timeout_command_note' => 'Note: This action can take time to run, which can lead to timeout issues in some web environments. As an alternative, this action be performed using a terminal command.', + 'maint' => 'Huolto', + 'maint_image_cleanup' => 'Siivoa kuvat', + 'maint_image_cleanup_desc' => 'Tarkistaa kuvista ja luonnoksista mitkä kuvat ja piirustukset ovat tällä hetkellä käytössä ja mitkä ovat tarpeettomia. Varmista, että olet varmuuskopioinut tietokannan ja kuvat ennen tämän toiminnon suorittamista.', + 'maint_delete_images_only_in_revisions' => 'Poista myös kuvat, jotka ovat olemassa vain vanhoissa sivujen versioissa', + 'maint_image_cleanup_run' => 'Suorita siivous', + 'maint_image_cleanup_warning' => ':count mahdollisesti käyttämätöntä kuvaa löytyi. Haluatko varmasti poistaa nämä kuvat?', + 'maint_image_cleanup_success' => ':count mahdollisesti käyttämätöntä kuvaa löydetty ja poistettu!', + 'maint_image_cleanup_nothing_found' => 'Käyttämättömiä kuvia ei löytynyt, mitään ei poistettu!', + 'maint_send_test_email' => 'Lähetä testisähköposti', + 'maint_send_test_email_desc' => 'Toiminto lähettää testisähköpostin profiilissasi määritettyyn sähköpostiosoitteeseen.', + 'maint_send_test_email_run' => 'Lähetä testisähköposti', + 'maint_send_test_email_success' => 'Sähköposti lähetetty osoitteeseen :address', + 'maint_send_test_email_mail_subject' => 'Testisähköpostiviesti', + 'maint_send_test_email_mail_greeting' => 'Sähköpostin lähetys näyttää toimivan!', + 'maint_send_test_email_mail_text' => 'Onnittelut! Koska sait tämän sähköposti-ilmoituksen, sähköpostiasetuksesi näyttävät olevan oikein määritetty.', + 'maint_recycle_bin_desc' => 'Poistetut hyllyt, kirjat, luvut ja sivut siirretään roskakoriin, josta ne voidaan palauttaa tai poistaa pysyvästi. Vanhemmat kohteet roskakorissa saatetaan poistaa automaattisesti jonkin ajan kuluttua järjestelmän asetuksista riippuen.', + 'maint_recycle_bin_open' => 'Avaa roskakori', + 'maint_regen_references' => 'Luo viitteet uudelleen', + 'maint_regen_references_desc' => 'Tämä toiminto rakentaa sisältöjen väliset viittaukset uudelleen. Tämä tapahtuu yleensä automaattisesti, mutta tämä toiminto voi olla hyödyllinen indeksoitaessa vanhaa sisältöä tai vaihtoehtoisin menetelmin lisättyä sisältöä.', + 'maint_regen_references_success' => 'Viiteindeksi on luotu uudelleen!', + 'maint_timeout_command_note' => 'Huomautus: tämän toiminnon suorittaminen voi kestää jonkin aikaa, mikä voi johtaa aikakatkaisusta johtuviin ongelmiin joissakin verkkoympäristöissä. Vaihtoehtoisesti tämä toiminto voidaan suorittaa komentoriviltä.', // Recycle Bin - 'recycle_bin' => 'Recycle Bin', - 'recycle_bin_desc' => 'Here you can restore items that have been deleted or choose to permanently remove them from the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.', - 'recycle_bin_deleted_item' => 'Deleted Item', - 'recycle_bin_deleted_parent' => 'Parent', - 'recycle_bin_deleted_by' => 'Deleted By', - 'recycle_bin_deleted_at' => 'Deletion Time', - 'recycle_bin_permanently_delete' => 'Permanently Delete', - 'recycle_bin_restore' => 'Restore', - 'recycle_bin_contents_empty' => 'The recycle bin is currently empty', - 'recycle_bin_empty' => 'Empty Recycle Bin', - 'recycle_bin_empty_confirm' => 'This will permanently destroy all items in the recycle bin including content contained within each item. Are you sure you want to empty the recycle bin?', - 'recycle_bin_destroy_confirm' => 'This action will permanently delete this item, along with any child elements listed below, from the system and you will not be able to restore this content. Are you sure you want to permanently delete this item?', - 'recycle_bin_destroy_list' => 'Items to be Destroyed', - 'recycle_bin_restore_list' => 'Items to be Restored', - 'recycle_bin_restore_confirm' => 'This action will restore the deleted item, including any child elements, to their original location. If the original location has since been deleted, and is now in the recycle bin, the parent item will also need to be restored.', - 'recycle_bin_restore_deleted_parent' => 'The parent of this item has also been deleted. These will remain deleted until that parent is also restored.', - 'recycle_bin_restore_parent' => 'Restore Parent', - 'recycle_bin_destroy_notification' => 'Deleted :count total items from the recycle bin.', - 'recycle_bin_restore_notification' => 'Restored :count total items from the recycle bin.', + 'recycle_bin' => 'Roskakori', + 'recycle_bin_desc' => 'Tässä voit palauttaa poistetut kohteet tai poistaa ne pysyvästi järjestelmästä. Tämä luettelo on suodattamaton, toisin kuin järjestelmän vastaavat toimintoluettelot, joihin sovelletaan käyttöoikeussuodattimia.', + 'recycle_bin_deleted_item' => 'Poistettu kohde', + 'recycle_bin_deleted_parent' => 'Vanhempi', + 'recycle_bin_deleted_by' => 'Poistanut', + 'recycle_bin_deleted_at' => 'Poistoaika', + 'recycle_bin_permanently_delete' => 'Poista pysyvästi', + 'recycle_bin_restore' => 'Palauta', + 'recycle_bin_contents_empty' => 'Roskakori on tällä hetkellä tyhjä', + 'recycle_bin_empty' => 'Tyhjennä roskakori', + 'recycle_bin_empty_confirm' => 'Tämä tuhoaa pysyvästi kaikki kohteet roskakorissa, mukaan lukien kunkin kohteen sisältämän sisällön. Haluatko varmasti tyhjentää roskakorin?', + 'recycle_bin_destroy_confirm' => 'Tämä toiminto poistaa tämän kohteen ja kaikki alla luetellut sisältyvät kohteet pysyvästi järjestelmästä, etkä voi enää palauttaa tätä sisältöä. Haluatko varmasti poistaa tämän kohteen pysyvästi?', + 'recycle_bin_destroy_list' => 'Poistettavat kohteet', + 'recycle_bin_restore_list' => 'Palautettavat kohteet', + 'recycle_bin_restore_confirm' => 'Tämä toiminto palauttaa poistetun kohteen, mukaan lukien kaikki siihen sisältyvät kohteet, alkuperäiseen sijaintiinsa. Jos alkuperäinen sijainti on sittemmin poistettu ja on nyt roskakorissa, myös sitä koskeva kohde on palautettava.', + 'recycle_bin_restore_deleted_parent' => 'Kohde, johon tämä kohde sisältyy on myös poistettu. Kohteet pysyvät poistettuina, kunnes kyseinen vanhempi on palautettu.', + 'recycle_bin_restore_parent' => 'Palauta vanhempi', + 'recycle_bin_destroy_notification' => 'Poistettu yhteensä :count kohdetta roskakorista.', + 'recycle_bin_restore_notification' => 'Palautettu yhteensä :count kohdetta roskakorista.', // Audit Log - 'audit' => 'Audit Log', - 'audit_desc' => 'This audit log displays a list of activities tracked in the system. This list is unfiltered unlike similar activity lists in the system where permission filters are applied.', - 'audit_event_filter' => 'Event Filter', - 'audit_event_filter_no_filter' => 'No Filter', - 'audit_deleted_item' => 'Deleted Item', - 'audit_deleted_item_name' => 'Name: :name', - 'audit_table_user' => 'User', - 'audit_table_event' => 'Event', - 'audit_table_related' => 'Related Item or Detail', - 'audit_table_ip' => 'IP Address', - 'audit_table_date' => 'Activity Date', - 'audit_date_from' => 'Date Range From', - 'audit_date_to' => 'Date Range To', + 'audit' => 'Tarkastusloki', + 'audit_desc' => 'Tämä tarkastusloki näyttää listauksen järjestelmässä suoritetuista toiminnoista. Lista on suodattamaton toisin kuin vastaavat järjestelmässä olevat listat, joihin sovelletaan käyttöoikeussuodattimia.', + 'audit_event_filter' => 'Tapahtumasuodatin', + 'audit_event_filter_no_filter' => 'Ei suodatinta', + 'audit_deleted_item' => 'Poistettu kohde', + 'audit_deleted_item_name' => 'Nimi: :name', + 'audit_table_user' => 'Käyttäjä', + 'audit_table_event' => 'Tapahtuma', + 'audit_table_related' => 'Liittyvä kohde tai tieto', + 'audit_table_ip' => 'IP-osoite', + 'audit_table_date' => 'Toiminnan päiväys', + 'audit_date_from' => 'Päiväys alkaen', + 'audit_date_to' => 'Päiväys saakka', // Role Settings - 'roles' => 'Roles', - 'role_user_roles' => 'User Roles', - 'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.', - 'roles_x_users_assigned' => ':count user assigned|:count users assigned', - 'roles_x_permissions_provided' => ':count permission|:count permissions', - 'roles_assigned_users' => 'Assigned Users', - 'roles_permissions_provided' => 'Provided Permissions', - 'role_create' => 'Create New Role', - 'role_delete' => 'Delete Role', - 'role_delete_confirm' => 'This will delete the role with the name \':roleName\'.', - 'role_delete_users_assigned' => 'This role has :userCount users assigned to it. If you would like to migrate the users from this role select a new role below.', - 'role_delete_no_migration' => "Don't migrate users", - 'role_delete_sure' => 'Are you sure you want to delete this role?', - 'role_edit' => 'Edit Role', - 'role_details' => 'Role Details', - 'role_name' => 'Role Name', - 'role_desc' => 'Short Description of Role', - 'role_mfa_enforced' => 'Requires Multi-Factor Authentication', - 'role_external_auth_id' => 'External Authentication IDs', - 'role_system' => 'System Permissions', - 'role_manage_users' => 'Manage users', - 'role_manage_roles' => 'Manage roles & role permissions', - 'role_manage_entity_permissions' => 'Manage all book, chapter & page permissions', - 'role_manage_own_entity_permissions' => 'Manage permissions on own book, chapter & pages', - 'role_manage_page_templates' => 'Manage page templates', - 'role_access_api' => 'Access system API', - 'role_manage_settings' => 'Manage app settings', - 'role_export_content' => 'Export content', - 'role_editor_change' => 'Change page editor', - 'role_notifications' => 'Receive & manage notifications', - 'role_asset' => 'Asset Permissions', - 'roles_system_warning' => 'Be aware that access to any of the above three permissions can allow a user to alter their own privileges or the privileges of others in the system. Only assign roles with these permissions to trusted users.', - 'role_asset_desc' => 'These permissions control default access to the assets within the system. Permissions on Books, Chapters and Pages will override these permissions.', - 'role_asset_admins' => 'Admins are automatically given access to all content but these options may show or hide UI options.', - 'role_asset_image_view_note' => 'This relates to visibility within the image manager. Actual access of uploaded image files will be dependant upon system image storage option.', - 'role_all' => 'All', - 'role_own' => 'Own', - 'role_controlled_by_asset' => 'Controlled by the asset they are uploaded to', - 'role_save' => 'Save Role', - 'role_users' => 'Users in this role', - 'role_users_none' => 'No users are currently assigned to this role', + 'roles' => 'Roolit', + 'role_user_roles' => 'Käyttäjäroolit', + 'roles_index_desc' => 'Rooleja käytetään käyttäjien ryhmittelyyn ja järjestelmän käyttöoikeuksien antamiseen. Kun käyttäjä on useamman roolin jäsen, hän saa kaikkien omien rooliensa kyvyt.', + 'roles_x_users_assigned' => ':count käyttäjä osoitettu|:count käyttäjää osoitettu', + 'roles_x_permissions_provided' => ':count käyttöoikeus|:count käyttöoikeutta', + 'roles_assigned_users' => 'Osoitetut käyttäjät', + 'roles_permissions_provided' => 'Annetut käyttöoikeudet', + 'role_create' => 'Luo uusi rooli', + 'role_delete' => 'Poista rooli', + 'role_delete_confirm' => 'Tämä poistaa roolin \':roleName\'.', + 'role_delete_users_assigned' => 'Tähän rooliin on osoitettu :userCount käyttäjää. Jos haluat siirtää käyttäjät tästä roolista, valitse uusi rooli alta.', + 'role_delete_no_migration' => "Älä siirrä käyttäjiä", + 'role_delete_sure' => 'Oletko varma, että haluat poistaa tämän roolin?', + 'role_edit' => 'Muokkaa roolia', + 'role_details' => 'Roolin tiedot', + 'role_name' => 'Roolin nimi', + 'role_desc' => 'Lyhyt kuvaus roolista', + 'role_mfa_enforced' => 'Vaatii monivaiheisen tunnistautumisen', + 'role_external_auth_id' => 'Ulkoisen tunnistautumisen tunnukset', + 'role_system' => 'Järjestelmän käyttöoikeudet', + 'role_manage_users' => 'Hallinnoi käyttäjiä', + 'role_manage_roles' => 'Hallinnoi rooleja ja roolien käyttöoikeuksia', + 'role_manage_entity_permissions' => 'Hallinnoi kaikkien kirjojen, lukujen ja sivujen käyttöoikeuksia', + 'role_manage_own_entity_permissions' => 'Hallinnoi omien kirjojen, lukujen ja sivujen käyttöoikeuksia', + 'role_manage_page_templates' => 'Hallinnoi mallipohjia', + 'role_access_api' => 'Pääsy järjestelmän ohjelmointirajapintaan', + 'role_manage_settings' => 'Hallinnoi sivuston asetuksia', + 'role_export_content' => 'Vie sisältöjä', + 'role_editor_change' => 'Vaihda sivun editoria', + 'role_notifications' => 'Vastaanota ja hallinnoi ilmoituksia', + 'role_asset' => 'Sisältöjen oikeudet', + 'roles_system_warning' => 'Huomaa, että minkä tahansa edellä mainituista kolmesta käyttöoikeudesta voi antaa käyttäjälle mahdollisuuden muuttaa omia tai muiden järjestelmän käyttäjien oikeuksia. Anna näitä oikeuksia sisältävät roolit vain luotetuille käyttäjille.', + 'role_asset_desc' => 'Näillä asetuksilla hallitaan oletuksena annettavia käyttöoikeuksia järjestelmässä oleviin sisältöihin. Yksittäisten kirjojen, lukujen ja sivujen käyttöoikeudet kumoavat nämä käyttöoikeudet.', + 'role_asset_admins' => 'Ylläpitäjät saavat automaattisesti pääsyn kaikkeen sisältöön, mutta nämä vaihtoehdot voivat näyttää tai piilottaa käyttöliittymävalintoja.', + 'role_asset_image_view_note' => 'Tämä tarkoittaa näkyvyyttä kuvien hallinnassa. Pääsy ladattuihin kuvatiedostoihin riippuu asetetusta kuvien tallennusvaihtoehdosta.', + 'role_all' => 'Kaikki', + 'role_own' => 'Omat', + 'role_controlled_by_asset' => 'Määräytyy sen sisällön mukaan, johon ne on ladattu', + 'role_save' => 'Tallenna rooli', + 'role_users' => 'Käyttäjät tässä roolissa', + 'role_users_none' => 'Yhtään käyttäjää ei ole osoitettuna tähän rooliin', // Users - 'users' => 'Users', - 'users_index_desc' => 'Create & manage individual user accounts within the system. User accounts are used for login and attribution of content & activity. Access permissions are primarily role-based but user content ownership, among other factors, may also affect permissions & access.', - 'user_profile' => 'User Profile', - 'users_add_new' => 'Add New User', - 'users_search' => 'Search Users', - 'users_latest_activity' => 'Latest Activity', - 'users_details' => 'User Details', - 'users_details_desc' => 'Set a display name and an email address for this user. The email address will be used for logging into the application.', - 'users_details_desc_no_email' => 'Set a display name for this user so others can recognise them.', - 'users_role' => 'User Roles', - 'users_role_desc' => 'Select which roles this user will be assigned to. If a user is assigned to multiple roles the permissions from those roles will stack and they will receive all abilities of the assigned roles.', - 'users_password' => 'User Password', - 'users_password_desc' => 'Set a password used to log-in to the application. This must be at least 8 characters long.', - 'users_send_invite_text' => 'You can choose to send this user an invitation email which allows them to set their own password otherwise you can set their password yourself.', - 'users_send_invite_option' => 'Send user invite email', - 'users_external_auth_id' => 'External Authentication ID', - 'users_external_auth_id_desc' => 'When an external authentication system is in use (such as SAML2, OIDC or LDAP) this is the ID which links this BookStack user to the authentication system account. You can ignore this field if using the default email-based authentication.', - 'users_password_warning' => 'Only fill the below if you would like to change the password for this user.', - 'users_system_public' => 'This user represents any guest users that visit your instance. It cannot be used to log in but is assigned automatically.', - 'users_delete' => 'Delete User', - 'users_delete_named' => 'Delete user :userName', - 'users_delete_warning' => 'This will fully delete this user with the name \':userName\' from the system.', - 'users_delete_confirm' => 'Are you sure you want to delete this user?', - 'users_migrate_ownership' => 'Migrate Ownership', - 'users_migrate_ownership_desc' => 'Select a user here if you want another user to become the owner of all items currently owned by this user.', - 'users_none_selected' => 'No user selected', - 'users_edit' => 'Edit User', - 'users_edit_profile' => 'Edit Profile', - 'users_avatar' => 'User Avatar', - 'users_avatar_desc' => 'Select an image to represent this user. This should be approx 256px square.', - 'users_preferred_language' => 'Preferred Language', - 'users_preferred_language_desc' => 'This option will change the language used for the user-interface of the application. This will not affect any user-created content.', - 'users_social_accounts' => 'Social Accounts', - 'users_social_accounts_desc' => 'View the status of the connected social accounts for this user. Social accounts can be used in addition to the primary authentication system for system access.', - 'users_social_accounts_info' => 'Here you can connect your other accounts for quicker and easier login. Disconnecting an account here does not revoke previously authorized access. Revoke access from your profile settings on the connected social account.', - 'users_social_connect' => 'Connect Account', - 'users_social_disconnect' => 'Disconnect Account', - 'users_social_status_connected' => 'Connected', - 'users_social_status_disconnected' => 'Disconnected', - 'users_social_connected' => ':socialAccount account was successfully attached to your profile.', - 'users_social_disconnected' => ':socialAccount account was successfully disconnected from your profile.', - 'users_api_tokens' => 'API Tokens', - 'users_api_tokens_desc' => 'Create and manage the access tokens used to authenticate with the BookStack REST API. Permissions for the API are managed via the user that the token belongs to.', - 'users_api_tokens_none' => 'No API tokens have been created for this user', - 'users_api_tokens_create' => 'Create Token', - 'users_api_tokens_expires' => 'Expires', - 'users_api_tokens_docs' => 'API Documentation', - 'users_mfa' => 'Multi-Factor Authentication', - 'users_mfa_desc' => 'Setup multi-factor authentication as an extra layer of security for your user account.', - 'users_mfa_x_methods' => ':count method configured|:count methods configured', - 'users_mfa_configure' => 'Configure Methods', + 'users' => 'Käyttäjät', + 'users_index_desc' => 'Luo ja hallinnoi yksittäisiä käyttäjätilejä järjestelmässä. Käyttäjätilejä käytetään kirjautumiseen sekä käyttöoikeuksien hallinnointiin. Käyttöoikeudet perustuvat ensisijaisesti rooleihin, mutta käyttöoikeuksiin voi vaikuttaa myös se, onko käyttäjä tietyn sisällön omistaja.', + 'user_profile' => 'Käyttäjäprofiili', + 'users_add_new' => 'Lisää uusi käyttäjä', + 'users_search' => 'Hae käyttäjiä', + 'users_latest_activity' => 'Viimeisin toiminta', + 'users_details' => 'Käyttäjän tiedot', + 'users_details_desc' => 'Aseta tälle käyttäjälle näyttönimi ja sähköpostiosoite. Sähköpostiosoitetta käytetään sovellukseen kirjautumiseen.', + 'users_details_desc_no_email' => 'Aseta tälle käyttäjälle näyttönimi, jonka perusteella käyttäjä voidaan tunnistaa.', + 'users_role' => 'Käyttäjäroolit', + 'users_role_desc' => 'Valitse, mitä rooleja tälle käyttäjälle annetaan. Jos käyttäjälle on määritetty useita rooleja, näiden roolien käyttöoikeudet yhdistetään ja hän saa kaikki osoitettujen roolien kyvyt.', + 'users_password' => 'Käyttäjän salasana', + 'users_password_desc' => 'Aseta salasana, jota käytetään sovellukseen kirjautumiseen. Sen on oltava vähintään 8 merkkiä pitkä.', + 'users_send_invite_text' => 'Voit lähettää käyttäjälle sähköpostilla kutsun ja antaa käyttäjän asettaa oman salasanansa. Vaihtoehtoisesti voit asettaa salasanan itse.', + 'users_send_invite_option' => 'Lähetä kutsu', + 'users_external_auth_id' => 'Ulkoisen tunnistautumisen tunnus', + 'users_external_auth_id_desc' => 'Tätä tunnusta käytetään BookStack-käyttäjätilin ja ulkoisen tunnistautumisen (kuten SAML2, OIDC tai LDAP) kautta käytettävän tilin yhdistämiseen. Voit jättää tämän kentän huomiotta, jos käytät oletuksena sähköpostipohjaista todennusta.', + 'users_password_warning' => 'Täytä alla oleva kenttä vain, jos haluat vaihtaa tämän käyttäjän salasanan.', + 'users_system_public' => 'Tämä käyttäjä tarkoittaa kaikkia vieraita, jotka vierailevat sivustollasi. Sitä ei voi käyttää kirjautumiseen ja se annetaan automaattisesti.', + 'users_delete' => 'Poista käyttäjä', + 'users_delete_named' => 'Poista käyttäjä :userName', + 'users_delete_warning' => 'Tämä poistaa käyttäjän \':userName\' kokonaan järjestelmästä.', + 'users_delete_confirm' => 'Haluatko varmasti poistaa tämän käyttäjän?', + 'users_migrate_ownership' => 'Omistusoikeuden siirto', + 'users_migrate_ownership_desc' => 'Valitse käyttäjä, jolle haluat siirtää kaikki poistettavan käyttäjän omistamat sisällöt.', + 'users_none_selected' => 'Yhtään käyttäjää ei ole valittu', + 'users_edit' => 'Muokkaa käyttäjää', + 'users_edit_profile' => 'Muokkaa profiilia', + 'users_avatar' => 'Käyttäjän kuva', + 'users_avatar_desc' => 'Valitse käyttäjän kuva. Kuvan tulisi olla noin 256 pikselin kokoinen neliö.', + 'users_preferred_language' => 'Ensisijainen kieli', + 'users_preferred_language_desc' => 'Tämä valinta vaihtaa sovelluksen käyttöliittymässä käytettävän kielen. Tämä ei vaikuta käyttäjän luomaan sisältöön.', + 'users_social_accounts' => 'Sosiaalisen median tilit', + 'users_social_accounts_desc' => 'Näytä tämän käyttäjän yhdistettyjen sosiaalisen median tilien tila. Sosiaalisen median tilejä voidaan käyttää ensisijaisen tunnistautumistavan ohella.', + 'users_social_accounts_info' => 'Täällä voit yhdistää muut tilisi ja nopeuttaa kirjautumista. Yhteyden katkaisu tiliin ei peruuta palvelulle annettua käyttöoikeutta. Käyttöoikeus tulee peruuttaa yhdistetyn sosiaalisen median tilin asetuksista.', + 'users_social_connect' => 'Yhdistä tili', + 'users_social_disconnect' => 'Katkaise yhteys tiliin', + 'users_social_status_connected' => 'Yhdistetty', + 'users_social_status_disconnected' => 'Yhteys katkaistu', + 'users_social_connected' => ':socialAccount-tili liitettiin onnistuneesti profiiliisi.', + 'users_social_disconnected' => ':Yhteys socialAccount-tiliin katkaistiin onnistuneesti profiilistasi.', + 'users_api_tokens' => 'API-tunnisteet', + 'users_api_tokens_desc' => 'Luo ja hallinnoi tunnisteita, joita käytetään BookStack REST-rajapinnan todennukseen. Rajapinnan käyttöoikeuksia hallinnoidaan sen käyttäjän asetuksista, jolle tunniste kuuluu.', + 'users_api_tokens_none' => 'Tälle käyttäjälle ei ole luotu API-tunnisteita', + 'users_api_tokens_create' => 'Luo tunniste', + 'users_api_tokens_expires' => 'Vanhenee', + 'users_api_tokens_docs' => 'API-dokumentaatio', + 'users_mfa' => 'Monivaiheinen tunnistautuminen', + 'users_mfa_desc' => 'Paranna käyttäjätilisi turvallisuutta ja ota käyttöön monivaiheinen tunnistautuminen.', + 'users_mfa_x_methods' => ':count menetelmä määritetty|:count menetelmää määritetty', + 'users_mfa_configure' => 'Määritä menetelmiä', // API Tokens - 'user_api_token_create' => 'Create API Token', - 'user_api_token_name' => 'Name', - 'user_api_token_name_desc' => 'Give your token a readable name as a future reminder of its intended purpose.', - 'user_api_token_expiry' => 'Expiry Date', - 'user_api_token_expiry_desc' => 'Set a date at which this token expires. After this date, requests made using this token will no longer work. Leaving this field blank will set an expiry 100 years into the future.', - 'user_api_token_create_secret_message' => 'Immediately after creating this token a "Token ID" & "Token Secret" will be generated and displayed. The secret will only be shown a single time so be sure to copy the value to somewhere safe and secure before proceeding.', - 'user_api_token' => 'API Token', - 'user_api_token_id' => 'Token ID', - 'user_api_token_id_desc' => 'This is a non-editable system generated identifier for this token which will need to be provided in API requests.', - 'user_api_token_secret' => 'Token Secret', - 'user_api_token_secret_desc' => 'This is a system generated secret for this token which will need to be provided in API requests. This will only be displayed this one time so copy this value to somewhere safe and secure.', - 'user_api_token_created' => 'Token created :timeAgo', - 'user_api_token_updated' => 'Token updated :timeAgo', - 'user_api_token_delete' => 'Delete Token', - 'user_api_token_delete_warning' => 'This will fully delete this API token with the name \':tokenName\' from the system.', - 'user_api_token_delete_confirm' => 'Are you sure you want to delete this API token?', + 'user_api_token_create' => 'Luo uusi API-tunniste', + 'user_api_token_name' => 'Nimi', + 'user_api_token_name_desc' => 'Anna tunnisteelle helppolukuinen nimi, josta muistaa sen käyttötarkoituksen.', + 'user_api_token_expiry' => 'Viimeinen voimassaolopäivä', + 'user_api_token_expiry_desc' => 'Aseta päiväys, jolloin tämä tunniste vanhenee. Tämän päiväyksen jälkeen tätä tunnistetta käyttäen tehdyt pyynnöt eivät enää toimi. Tämän kentän jättäminen tyhjäksi asettaa voimassaolon päättymisen 100 vuoden päähän tulevaisuuteen.', + 'user_api_token_create_secret_message' => 'Välittömästi tämän tunnisteen luomisen jälkeen luodaan ja näytetään "Tunnisteen ID" ja "Tunnisteen salaisuus". Salaisuus näytetään vain kerran, joten kopioi arvo jonnekin turvalliseen paikkaan ennen kuin jatkat.', + 'user_api_token' => 'API-tunniste', + 'user_api_token_id' => 'Tunnisteen ID', + 'user_api_token_id_desc' => 'Tämä on järjestelmän luoma tunniste, jota ei voi muokata ja jota on käytettävä API-pyynnöissä.', + 'user_api_token_secret' => 'Tunnisteen salaisuus', + 'user_api_token_secret_desc' => 'Tämä on järjestelmän tälle tunnisteelle luoma salaisuus, jota on käytettävä API-pyynnöissä. Tämä näytetään vain kerran, joten kopioi tämä arvo jonnekin turvalliseen paikkaan.', + 'user_api_token_created' => 'Tunniste luotu :timeAgo', + 'user_api_token_updated' => 'Tunniste päivitetty :timeAgo', + 'user_api_token_delete' => 'Poista tunniste', + 'user_api_token_delete_warning' => 'Tämä poistaa API-tunnisteen \':tokenName\' kokonaan järjestelmästä.', + 'user_api_token_delete_confirm' => 'Oletko varma, että haluat poistaa tämän API-tunnisteen?', // Webhooks - 'webhooks' => 'Webhooks', - 'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.', - 'webhooks_x_trigger_events' => ':count trigger event|:count trigger events', - 'webhooks_create' => 'Create New Webhook', - 'webhooks_none_created' => 'No webhooks have yet been created.', - 'webhooks_edit' => 'Edit Webhook', - 'webhooks_save' => 'Save Webhook', - 'webhooks_details' => 'Webhook Details', - 'webhooks_details_desc' => 'Provide a user friendly name and a POST endpoint as a location for the webhook data to be sent to.', - 'webhooks_events' => 'Webhook Events', - 'webhooks_events_desc' => 'Select all the events that should trigger this webhook to be called.', - 'webhooks_events_warning' => 'Keep in mind that these events will be triggered for all selected events, even if custom permissions are applied. Ensure that use of this webhook won\'t expose confidential content.', - 'webhooks_events_all' => 'All system events', - 'webhooks_name' => 'Webhook Name', - 'webhooks_timeout' => 'Webhook Request Timeout (Seconds)', - 'webhooks_endpoint' => 'Webhook Endpoint', - 'webhooks_active' => 'Webhook Active', - 'webhook_events_table_header' => 'Events', - 'webhooks_delete' => 'Delete Webhook', - 'webhooks_delete_warning' => 'This will fully delete this webhook, with the name \':webhookName\', from the system.', - 'webhooks_delete_confirm' => 'Are you sure you want to delete this webhook?', - 'webhooks_format_example' => 'Webhook Format Example', - 'webhooks_format_example_desc' => 'Webhook data is sent as a POST request to the configured endpoint as JSON following the format below. The "related_item" and "url" properties are optional and will depend on the type of event triggered.', - 'webhooks_status' => 'Webhook Status', - 'webhooks_last_called' => 'Last Called:', - 'webhooks_last_errored' => 'Last Errored:', - 'webhooks_last_error_message' => 'Last Error Message:', + 'webhooks' => 'Toimintokutsut', + 'webhooks_index_desc' => 'Toimintokutsut ovat tapa lähettää tietoja ulkoisiin URL-osoitteisiin, kun järjestelmässä tapahtuu tiettyjä toimintoja ja tapahtumia. Tämä mahdollistaa näihin tapahtumiin perustuvan integroinnin muihin alustoihin, esimerkiksi viesti- tai ilmoitusjärjestelmiin.', + 'webhooks_x_trigger_events' => ':count käynnistävä tapahtuma|:count käynnistävää tapahtumaa', + 'webhooks_create' => 'Luo uusi toimintokutsu', + 'webhooks_none_created' => 'Toimintokutsuja ei ole luotu.', + 'webhooks_edit' => 'Muokkaa toimintokutsua', + 'webhooks_save' => 'Tallenna toimintokutsu', + 'webhooks_details' => 'Toimintokutsun tiedot', + 'webhooks_details_desc' => 'Anna selkeä nimi ja POST-päätepisteen sijainti, johon toimintokutsun tiedot lähetetään.', + 'webhooks_events' => 'Toimintokutsun tapahtumat', + 'webhooks_events_desc' => 'Valitse kaikki tapahtumat, jotka käynnistävät tämän toimintokutsun.', + 'webhooks_events_warning' => 'Huomaa, että toimintokutsu käynnistetään kaikissa valituissa tapahtumissa, vaikka niihin liittyisi muokautettuja käyttöoikeuksia. Varmista, että tämän toimintokutsun käyttö ei paljasta luottamuksellista sisältöä.', + 'webhooks_events_all' => 'Kaikki järjestelmän tapahtumat', + 'webhooks_name' => 'Toimintokutsun nimi', + 'webhooks_timeout' => 'Toimintokutsun aikakatkaisu (sekuntia)', + 'webhooks_endpoint' => 'Toimintokutsun päätepiste', + 'webhooks_active' => 'Toimintokutsu aktiivinen', + 'webhook_events_table_header' => 'Tapahtumat', + 'webhooks_delete' => 'Poista toimintokutsu', + 'webhooks_delete_warning' => 'Tämä poistaa toimintokutsun \':webhookName\' kokonaan järjestelmästä.', + 'webhooks_delete_confirm' => 'Oletko varma, että haluat poistaa tämän toimintokutsun?', + 'webhooks_format_example' => 'Esimerkki toimintokutsusta', + 'webhooks_format_example_desc' => 'Toimintokutsun tiedot lähetetään JSON-muodossa POST-pyyntönä määritettyyn päätepisteeseen alla näkyvän mallin mukaisesti. Ominaisuudet "related_item" ja "url" ovat valinnaisia ja riippuvat käynnistetyn tapahtuman tyypistä.', + 'webhooks_status' => 'Toimintokutsun tila', + 'webhooks_last_called' => 'Viimeksi kutsuttu:', + 'webhooks_last_errored' => 'Viimeisin virhe:', + 'webhooks_last_error_message' => 'Viimeisin virheviesti:', //! If editing translations files directly please ignore this in all @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/fi/validation.php b/lang/fi/validation.php index 2a676c7c4..35c5f1924 100644 --- a/lang/fi/validation.php +++ b/lang/fi/validation.php @@ -8,107 +8,107 @@ return [ // Standard laravel validation lines - 'accepted' => 'The :attribute must be accepted.', - 'active_url' => 'The :attribute is not a valid URL.', - 'after' => 'The :attribute must be a date after :date.', - 'alpha' => 'The :attribute may only contain letters.', - 'alpha_dash' => 'The :attribute may only contain letters, numbers, dashes and underscores.', - 'alpha_num' => 'The :attribute may only contain letters and numbers.', - 'array' => 'The :attribute must be an array.', - 'backup_codes' => 'The provided code is not valid or has already been used.', - 'before' => 'The :attribute must be a date before :date.', + 'accepted' => ':attribute tulee hyväksyä.', + 'active_url' => ':attribute ei ole kelvollinen URL.', + 'after' => ':attribute tulee olla päiväyksen :date jälkeinen päiväys.', + 'alpha' => ':attribute voi sisältää vain kirjaimia.', + 'alpha_dash' => ':attribute voi sisältää vain kirjaimia, numeroita, yhdys- ja alaviivoja.', + 'alpha_num' => ':attribute voi sisältää vain kirjaimia ja numeroita.', + 'array' => ':attribute tulee olla taulukkomuuttuja.', + 'backup_codes' => 'Annettu koodi ei ole kelvollinen tai se on jo käytetty.', + 'before' => ':attribute päiväyksen tulee olla ennen :date.', 'between' => [ - 'numeric' => 'The :attribute must be between :min and :max.', - 'file' => 'The :attribute must be between :min and :max kilobytes.', - 'string' => 'The :attribute must be between :min and :max characters.', - 'array' => 'The :attribute must have between :min and :max items.', + 'numeric' => ':attribute tulee olla välillä :min ja :max.', + 'file' => ':attribute tulee olla :min - :max kilotavua.', + 'string' => ':attribute tulee olla :min - :max merkkiä pitkä.', + 'array' => ':attribute tulee sisältää :min - :max kohdetta.', ], - 'boolean' => 'The :attribute field must be true or false.', - 'confirmed' => 'The :attribute confirmation does not match.', - 'date' => 'The :attribute is not a valid date.', - 'date_format' => 'The :attribute does not match the format :format.', - 'different' => 'The :attribute and :other must be different.', - 'digits' => 'The :attribute must be :digits digits.', - 'digits_between' => 'The :attribute must be between :min and :max digits.', - 'email' => 'The :attribute must be a valid email address.', - 'ends_with' => 'The :attribute must end with one of the following: :values', - 'file' => 'The :attribute must be provided as a valid file.', - 'filled' => 'The :attribute field is required.', + 'boolean' => ':attribute tulee olla tosi tai epätosi.', + 'confirmed' => ':attribute vahvistus ei täsmää.', + 'date' => ':attribute ei ole kelvollinen päiväys.', + 'date_format' => ':attribute ei täsmää muodon :format kanssa.', + 'different' => ':attribute ja :other tulee erota toisistaan.', + 'digits' => ':attribute tulee olla :digits numeroa pitkä.', + 'digits_between' => ':attribute tulee olla :min - :max numeroa.', + 'email' => ':attribute tulee olla kelvollinen sähköpostiosoite.', + 'ends_with' => ':attribute arvon tulee päättyä johonkin seuraavista: :values', + 'file' => ':attribute tulee olla kelvollinen tiedosto.', + 'filled' => 'Kenttä :attribute vaaditaan.', 'gt' => [ - 'numeric' => 'The :attribute must be greater than :value.', - 'file' => 'The :attribute must be greater than :value kilobytes.', - 'string' => 'The :attribute must be greater than :value characters.', - 'array' => 'The :attribute must have more than :value items.', + 'numeric' => ':attribute tulee olla suurempi kuin :value.', + 'file' => ':attribute tulee olla suurempi kuin :value kilotavua.', + 'string' => ':attribute tulee olla suurempi kuin :value merkkiä.', + 'array' => ':attribute tulee sisältää vähintään :value kohdetta.', ], 'gte' => [ - 'numeric' => 'The :attribute must be greater than or equal :value.', - 'file' => 'The :attribute must be greater than or equal :value kilobytes.', - 'string' => 'The :attribute must be greater than or equal :value characters.', - 'array' => 'The :attribute must have :value items or more.', + 'numeric' => ':attribute on oltava suurempi tai samansuuruinen kuin :value.', + 'file' => ':attribute on oltava suurempi tai samansuuruinen kuin :value kilotavua.', + 'string' => ':attribute on oltava suurempi tai samansuuruinen kuin :value merkkiä.', + 'array' => ':attribute tulee sisältää vähintään :value kohdetta tai enemmän.', ], - 'exists' => 'The selected :attribute is invalid.', - 'image' => 'The :attribute must be an image.', - 'image_extension' => 'The :attribute must have a valid & supported image extension.', - 'in' => 'The selected :attribute is invalid.', - 'integer' => 'The :attribute must be an integer.', - 'ip' => 'The :attribute must be a valid IP address.', - 'ipv4' => 'The :attribute must be a valid IPv4 address.', - 'ipv6' => 'The :attribute must be a valid IPv6 address.', - 'json' => 'The :attribute must be a valid JSON string.', + 'exists' => 'Valittu :attribute ei ole kelvollinen.', + 'image' => ':attribute on oltava kuva.', + 'image_extension' => ':-attribute tulee sisältää kelvollisen ja tuetun kuvan tiedostopäätteen.', + 'in' => 'Valittu :attribute ei ole kelvollinen.', + 'integer' => ':attribute tulee olla kokonaisluku.', + 'ip' => ':attribute tulee olla kelvollinen IP-osoite.', + 'ipv4' => ':attribute tulee olla kelvollinen IPv4-osoite.', + 'ipv6' => ':attribute tulee olla kelvollinen IPv6 -osoite.', + 'json' => ':attribute tulee olla kelvollinen JSON-merkkijono.', 'lt' => [ - 'numeric' => 'The :attribute must be less than :value.', - 'file' => 'The :attribute must be less than :value kilobytes.', - 'string' => 'The :attribute must be less than :value characters.', - 'array' => 'The :attribute must have less than :value items.', + 'numeric' => ':attribute tulee olla vähemmän kuin :value.', + 'file' => ':attribute tulee olla vähemmän kuin :value kilotavua.', + 'string' => ':attribute tulee olla vähemmän kuin :value merkkiä.', + 'array' => ':attribute tulee sisältää vähemmän kuin :value kohdetta.', ], 'lte' => [ - 'numeric' => 'The :attribute must be less than or equal :value.', - 'file' => 'The :attribute must be less than or equal :value kilobytes.', - 'string' => 'The :attribute must be less than or equal :value characters.', - 'array' => 'The :attribute must not have more than :value items.', + 'numeric' => ':attribute tulee olla vähemmän tai yhtä suuri kuin :value.', + 'file' => ':attribute tulee olla vähemmän tai yhtä suuri kuin :value kilotavua.', + 'string' => ':attribute tulee olla vähemmän tai yhtä suuri kuin :value merkkiä.', + 'array' => ':attribute ei tule sisältää enempää kuin :value kohdetta.', ], 'max' => [ - 'numeric' => 'The :attribute may not be greater than :max.', - 'file' => 'The :attribute may not be greater than :max kilobytes.', - 'string' => 'The :attribute may not be greater than :max characters.', - 'array' => 'The :attribute may not have more than :max items.', + 'numeric' => ':attribute ei saa olla suurempi kuin :max.', + 'file' => ':attribute ei saa olla suurempi kuin :max kilotavua.', + 'string' => ':attribute ei saa olla suurempi kuin :max merkkiä.', + 'array' => ':attribute ei saa sisältää enempää kuin :max kohdetta.', ], - 'mimes' => 'The :attribute must be a file of type: :values.', + 'mimes' => ':attribute tulee olla tiedosto jonka tyyppi on :values.', 'min' => [ - 'numeric' => 'The :attribute must be at least :min.', - 'file' => 'The :attribute must be at least :min kilobytes.', - 'string' => 'The :attribute must be at least :min characters.', - 'array' => 'The :attribute must have at least :min items.', + 'numeric' => ':attribute tulee olla vähintään :min.', + 'file' => ':attribute tulee olla vähintään :min kilotavua.', + 'string' => ':attribute tulee olla vähintään :min merkkiä.', + 'array' => ':attribute tulee sisältää vähintään :min kohdetta.', ], - 'not_in' => 'The selected :attribute is invalid.', - 'not_regex' => 'The :attribute format is invalid.', - 'numeric' => 'The :attribute must be a number.', - 'regex' => 'The :attribute format is invalid.', - 'required' => 'The :attribute field is required.', - 'required_if' => 'The :attribute field is required when :other is :value.', - 'required_with' => 'The :attribute field is required when :values is present.', - 'required_with_all' => 'The :attribute field is required when :values is present.', - 'required_without' => 'The :attribute field is required when :values is not present.', - 'required_without_all' => 'The :attribute field is required when none of :values are present.', - 'same' => 'The :attribute and :other must match.', - 'safe_url' => 'The provided link may not be safe.', + 'not_in' => 'Valittu :attribute ei ole kelvollinen.', + 'not_regex' => ':attribute muoto ei ole kelvollinen.', + 'numeric' => ':attribute tulee olla numero.', + 'regex' => ':attribute muoto ei ole kelvollinen.', + 'required' => 'Kenttä :attribute vaaditaan.', + 'required_if' => 'Kenttä :attribute vaaditaan, kun :other on :value.', + 'required_with' => 'Kenttä :attribute vaaditaan, kun :values on määritettynä.', + 'required_with_all' => 'Kenttä :attribute vaaditaan, kun kaikki näistä on määritettynä :values.', + 'required_without' => 'Kenttä :attribute vaaditaan, kun :values ei ole määritettynä.', + 'required_without_all' => 'Kenttä :attribute vaaditaan, kun mikään näistä ei ole määritettynä :values.', + 'same' => ':attribute ja :other tulee täsmätä.', + 'safe_url' => 'Annettu linkki ei ole mahdollisesti turvallinen.', 'size' => [ - 'numeric' => 'The :attribute must be :size.', - 'file' => 'The :attribute must be :size kilobytes.', - 'string' => 'The :attribute must be :size characters.', - 'array' => 'The :attribute must contain :size items.', + 'numeric' => ':attribute tulee olla :size.', + 'file' => ':attribute tulee olla :size kilotavua.', + 'string' => ':attribute tulee olla :size merkkiä.', + 'array' => ':attribute tulee sisältää :size kohdetta.', ], - 'string' => 'The :attribute must be a string.', - 'timezone' => 'The :attribute must be a valid zone.', - 'totp' => 'The provided code is not valid or has expired.', - 'unique' => 'The :attribute has already been taken.', - 'url' => 'The :attribute format is invalid.', - 'uploaded' => 'The file could not be uploaded. The server may not accept files of this size.', + 'string' => ':attribute tulee olla merkkijono.', + 'timezone' => ':attribute tulee olla kelvollinen aikavyöhyke.', + 'totp' => 'Annettu koodi ei ole kelvollinen tai se on vanhentunut.', + 'unique' => ':attribute on jo käytössä.', + 'url' => ':attribute muoto ei ole kelvollinen.', + 'uploaded' => 'Tiedostoa ei voitu ladata. Palvelin ei ehkä hyväksy tämän kokoisia tiedostoja.', // Custom validation lines 'custom' => [ 'password-confirm' => [ - 'required_with' => 'Password confirmation required', + 'required_with' => 'Salasanan vahvistus vaaditaan', ], ], diff --git a/lang/fr/entities.php b/lang/fr/entities.php index 5b813b4f6..e890c2787 100644 --- a/lang/fr/entities.php +++ b/lang/fr/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Mis à jour :timeLength', 'meta_updated_name' => 'Mis à jour :timeLength par :user', 'meta_owned_name' => 'Appartient à :user', - 'meta_reference_page_count' => 'Référencé sur :count page|Référencé sur :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Sélectionner l\'entité', 'entity_select_lack_permission' => 'Vous n\'avez pas les permissions requises pour sélectionner cet élément', 'images' => 'Images', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Modifier le livre :bookName', 'books_form_book_name' => 'Nom du livre', 'books_save' => 'Enregistrer le livre', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Permissions du livre', 'books_permissions_updated' => 'Permissions du livre mises à jour', 'books_empty_contents' => 'Aucune page ou chapitre n\'a été ajouté à ce livre.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Supprimer le brouillon', 'pages_delete_success' => 'Page supprimée', 'pages_delete_draft_success' => 'Brouillon supprimé', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cette page ?', 'pages_delete_draft_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer ce brouillon ?', 'pages_editing_named' => 'Modification de la page :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Références', 'references_none' => 'Il n\'y a pas de références suivies à cet élément.', - 'references_to_desc' => 'Vous trouverez ci-dessous toutes les pages connues du système qui ont un lien vers cet élément.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Suivre', diff --git a/lang/fr/errors.php b/lang/fr/errors.php index ce4ae1bb5..2e4cfa216 100644 --- a/lang/fr/errors.php +++ b/lang/fr/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'L\'extension PHP LDAP n\'est pas installée', 'ldap_cannot_connect' => 'Impossible de se connecter au serveur LDAP, la connexion initiale a échoué', 'saml_already_logged_in' => 'Déjà connecté', - 'saml_user_not_registered' => 'L\'utilisateur :name n\'est pas enregistré et l\'enregistrement automatique est désactivé', 'saml_no_email_address' => 'Impossible de trouver une adresse e-mail, pour cet utilisateur, dans les données fournies par le système d\'authentification externe', 'saml_invalid_response_id' => 'La requête du système d\'authentification externe n\'est pas reconnue par un processus démarré par cette application. Naviguer après une connexion peut causer ce problème.', 'saml_fail_authed' => 'Connexion avec :system échouée, le système n\'a pas fourni l\'autorisation réussie', diff --git a/lang/fr/notifications.php b/lang/fr/notifications.php index 37b40882d..eb97ddfda 100644 --- a/lang/fr/notifications.php +++ b/lang/fr/notifications.php @@ -4,15 +4,16 @@ */ return [ - 'new_comment_subject' => 'Nouveau commentaire sur la page : :pageName', + 'new_comment_subject' => 'Nouveau commentaire sur la page: :pageName', 'new_comment_intro' => 'Un utilisateur a commenté une page dans :appName:', - 'new_page_subject' => 'Nouvelle page : :pageName', + 'new_page_subject' => 'Nouvelle page: :pageName', 'new_page_intro' => 'Une nouvelle page a été créée dans :appName:', - 'updated_page_subject' => 'Page mise à jour : :pageName', + 'updated_page_subject' => 'Page mise à jour: :pageName', 'updated_page_intro' => 'Une page a été mise à jour dans :appName:', 'updated_page_debounce' => 'Pour éviter de nombreuses notifications, pendant un certain temps, vous ne recevrez pas de notifications pour d\'autres modifications de cette page par le même éditeur.', 'detail_page_name' => 'Nom de la page :', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenta·teur·trice :', 'detail_comment' => 'Commentaire :', 'detail_created_by' => 'Créé par :', diff --git a/lang/fr/settings.php b/lang/fr/settings.php index 731770e83..928fed4b3 100644 --- a/lang/fr/settings.php +++ b/lang/fr/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Estonien', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'Hébreu', 'hr' => 'Croate', diff --git a/lang/he/entities.php b/lang/he/entities.php index 6813cfcea..113af659b 100644 --- a/lang/he/entities.php +++ b/lang/he/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'עודכן :timeLength', 'meta_updated_name' => 'עודכן :timeLength על ידי :user', 'meta_owned_name' => 'Owned by :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'בחר יישות', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'תמונות', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'עריכת ספר :bookName', 'books_form_book_name' => 'שם הספר', 'books_save' => 'שמור ספר', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'הרשאות ספר', 'books_permissions_updated' => 'הרשאות הספר עודכנו', 'books_empty_contents' => 'לא נוצרו פרקים או דפים עבור ספר זה', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'מחק טיוטת דף', 'pages_delete_success' => 'הדף נמחק', 'pages_delete_draft_success' => 'טיוטת דף נמחקה', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'האם ברצונך למחוק דף זה?', 'pages_delete_draft_confirm' => 'האם ברצונך למחוק את טיוטת הדף הזה?', 'pages_editing_named' => 'עריכת דף :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/he/errors.php b/lang/he/errors.php index abf028417..4de4d88c5 100644 --- a/lang/he/errors.php +++ b/lang/he/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'הרחבת LDAP עבור PHP לא מותקנת', 'ldap_cannot_connect' => 'Cannot connect to ldap server, Initial connection failed', 'saml_already_logged_in' => 'כבר מחובר', - 'saml_user_not_registered' => 'The user :name is not registered and automatic registration is disabled', 'saml_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system', 'saml_invalid_response_id' => 'The request from the external authentication system is not recognised by a process started by this application. Navigating back after a login could cause this issue.', 'saml_fail_authed' => 'Login using :system failed, system did not provide successful authorization', diff --git a/lang/he/notifications.php b/lang/he/notifications.php index 5539ae9a9..1afd23f1d 100644 --- a/lang/he/notifications.php +++ b/lang/he/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/he/settings.php b/lang/he/settings.php index 26b79394c..ead74732c 100644 --- a/lang/he/settings.php +++ b/lang/he/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/hr/entities.php b/lang/hr/entities.php index 67fada531..6a69a0d92 100644 --- a/lang/hr/entities.php +++ b/lang/hr/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Ažurirano :timeLength', 'meta_updated_name' => 'Ažurirano :timeLength od :user', 'meta_owned_name' => 'Vlasništvo :user', - 'meta_reference_page_count' => 'Referencirano na :count stranici|Referencirano na :count stranica', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Odaberi subjekt', 'entity_select_lack_permission' => 'Nemate potrebne ovlasti za odabir ovog elementa', 'images' => 'Slike', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Uredi knjigu :bookName', 'books_form_book_name' => 'Ime knjige', 'books_save' => 'Spremi knjigu', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Dopuštenja za knjigu', 'books_permissions_updated' => 'Ažurirana dopuštenja za knjigu', 'books_empty_contents' => 'U ovoj knjizi još nema stranica ni poglavlja.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Izbriši nacrt stranice', 'pages_delete_success' => 'Izbrisana stranica', 'pages_delete_draft_success' => 'Izbrisan nacrt stranice', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Jeste li sigurni da želite izbrisati stranicu?', 'pages_delete_draft_confirm' => 'Jeste li sigurni da želite izbrisati nacrt stranice?', 'pages_editing_named' => 'Uređivanje stranice :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Reference', 'references_none' => 'Nema praćenih referenci na ovu stavku.', - 'references_to_desc' => 'U nastavku su prikazane sve poznate stranice u sustavu koje se povezuju s ovom stavkom.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Prati', diff --git a/lang/hr/errors.php b/lang/hr/errors.php index 1b789c6c5..608c05e29 100644 --- a/lang/hr/errors.php +++ b/lang/hr/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'LDAP PHP ekstenzija nije instalirana', 'ldap_cannot_connect' => 'Nemoguće pristupiti ldap serveru, problem s mrežom', 'saml_already_logged_in' => 'Već ste prijavljeni', - 'saml_user_not_registered' => 'Korisnik :name nije registriran i automatska registracija je onemogućena', 'saml_no_email_address' => 'Nismo pronašli email adresu za ovog korisnika u vanjskim sustavima', 'saml_invalid_response_id' => 'Sustav za autentifikaciju nije prepoznat. Ovaj problem možda je nastao zbog vraćanja nakon prijave.', 'saml_fail_authed' => 'Prijava pomoću :system nije uspjela zbog neuspješne autorizacije', diff --git a/lang/hr/notifications.php b/lang/hr/notifications.php index e50d57dfe..c3a38cdfa 100644 --- a/lang/hr/notifications.php +++ b/lang/hr/notifications.php @@ -15,6 +15,7 @@ Ažurirana stranica: :pageName', 'updated_page_debounce' => 'Kako biste spriječili velik broj obavijesti, nećete primati obavijesti o daljnjim izmjenama ove stranice od istog urednika neko vrijeme.', 'detail_page_name' => 'Naziv Stranice:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Komentator:', 'detail_comment' => 'Komentar:', 'detail_created_by' => 'Kreirao Korisnik:', diff --git a/lang/hr/settings.php b/lang/hr/settings.php index 4d66abfaa..6040489ac 100644 --- a/lang/hr/settings.php +++ b/lang/hr/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/hu/entities.php b/lang/hu/entities.php index 97c8b206d..339a8d100 100644 --- a/lang/hu/entities.php +++ b/lang/hu/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Frissítve :timeLength', 'meta_updated_name' => ':user frissítette :timeLength', 'meta_owned_name' => ':user tulajdona', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Entitás kiválasztása', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Képek', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => ':bookName könyv szerkesztése', 'books_form_book_name' => 'Könyv neve', 'books_save' => 'Könyv mentése', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Könyv jogosultságok', 'books_permissions_updated' => 'Könyv jogosultságok frissítve', 'books_empty_contents' => 'Ehhez a könyvhöz nincsenek oldalak vagy fejezetek létrehozva.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Vázlat oldal törlése', 'pages_delete_success' => 'Oldal törölve', 'pages_delete_draft_success' => 'Vázlat oldal törölve', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Biztosan törölhető ez az oldal?', 'pages_delete_draft_confirm' => 'Biztosan törölhető ez a vázlatoldal?', 'pages_editing_named' => ':pageName oldal szerkesztése', @@ -405,7 +409,7 @@ return [ // References 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/hu/errors.php b/lang/hu/errors.php index 19407a8e9..e51ef633f 100644 --- a/lang/hu/errors.php +++ b/lang/hu/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'LDAP PHP kiterjesztés nincs telepítve', 'ldap_cannot_connect' => 'Nem lehet kapcsolódni az LDAP kiszolgálóhoz, a kezdeti kapcsolatfelvétel nem sikerült', 'saml_already_logged_in' => 'Már bejelentkezett', - 'saml_user_not_registered' => ':name felhasználó nincs regisztrálva és az automatikus regisztráció le van tiltva', 'saml_no_email_address' => 'Ehhez a felhasználóhoz nem található email cím a külső hitelesítő rendszer által átadott adatokban', 'saml_invalid_response_id' => 'A külső hitelesítő rendszerből érkező kérést nem ismerte fel az alkalmazás által indított folyamat. Bejelentkezés után az előző oldalra történő visszalépés okozhatja ezt a hibát.', 'saml_fail_authed' => 'Bejelentkezés :system használatával sikertelen, a rendszer nem biztosított sikeres hitelesítést', diff --git a/lang/hu/notifications.php b/lang/hu/notifications.php index 5539ae9a9..1afd23f1d 100644 --- a/lang/hu/notifications.php +++ b/lang/hu/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/hu/settings.php b/lang/hu/settings.php index 45f6437c4..333b82574 100644 --- a/lang/hu/settings.php +++ b/lang/hu/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/id/entities.php b/lang/id/entities.php index 7f80d6258..0c7c7a9ef 100644 --- a/lang/id/entities.php +++ b/lang/id/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Diperbaharui :timeLength', 'meta_updated_name' => 'Diperbaharui :timeLength oleh :user', 'meta_owned_name' => 'Dimiliki oleh :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Pilihan Entitas', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Gambar-gambar', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Sunting Buku :bookName', 'books_form_book_name' => 'Nama Buku', 'books_save' => 'Simpan Buku', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Izin Buku', 'books_permissions_updated' => 'Izin Buku Diperbarui', 'books_empty_contents' => 'Tidak ada halaman atau bab yang telah dibuat untuk buku ini.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Hapus Halaman Draf', 'pages_delete_success' => 'Halaman dihapus', 'pages_delete_draft_success' => 'Halaman draf dihapus', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Anda yakin ingin menghapus halaman ini?', 'pages_delete_draft_confirm' => 'Anda yakin ingin menghapus halaman draf ini?', 'pages_editing_named' => 'Menyunting Halaman :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/id/errors.php b/lang/id/errors.php index 6843e62ea..7ed7f2e11 100644 --- a/lang/id/errors.php +++ b/lang/id/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'Ekstensi LDAP PHP tidak terpasang', 'ldap_cannot_connect' => 'Tidak dapat terhubung ke server ldap, Koneksi awal gagal', 'saml_already_logged_in' => 'Telah masuk', - 'saml_user_not_registered' => 'Pengguna :name tidak terdaftar dan pendaftaran otomatis dinonaktifkan', 'saml_no_email_address' => 'Tidak dapat menemukan sebuah alamat email untuk pengguna ini, dalam data yang diberikan oleh sistem autentikasi eksternal', 'saml_invalid_response_id' => 'Permintaan dari sistem otentikasi eksternal tidak dikenali oleh sebuah proses yang dimulai oleh aplikasi ini. Menavigasi kembali setelah masuk dapat menyebabkan masalah ini.', 'saml_fail_authed' => 'Masuk menggunakan :system gagal, sistem tidak memberikan otorisasi yang berhasil', diff --git a/lang/id/notifications.php b/lang/id/notifications.php index 5539ae9a9..1afd23f1d 100644 --- a/lang/id/notifications.php +++ b/lang/id/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/id/settings.php b/lang/id/settings.php index 866f30984..515d4bd95 100644 --- a/lang/id/settings.php +++ b/lang/id/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/it/entities.php b/lang/it/entities.php index a0d570732..383cb4e7c 100644 --- a/lang/it/entities.php +++ b/lang/it/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Aggiornato :timeLength', 'meta_updated_name' => 'Aggiornato :timeLength da :user', 'meta_owned_name' => 'Creati da :user', - 'meta_reference_page_count' => 'Referenziato su :count page|Referenziato su :count pages', + 'meta_reference_count' => 'Referenziato da :count item|Referenziato da :count items', 'entity_select' => 'Selezione Entità', 'entity_select_lack_permission' => 'Non hai i permessi necessari per selezionare questo elemento', 'images' => 'Immagini', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Modifica il libro :bookName', 'books_form_book_name' => 'Nome Libro', 'books_save' => 'Salva Libro', + 'books_default_template' => 'Pagina template predefinita', + 'books_default_template_explain' => 'Assegnare una pagina template che verrà utilizzata come contenuto predefinito per tutte le nuove pagine di questo libro. Tenete presente che questa sarà usata solo se il creatore della pagina ha accesso di visualizzazione alla pagina template scelta.', + 'books_default_template_select' => 'Selezionare una pagina template', 'books_permissions' => 'Permessi Libro', 'books_permissions_updated' => 'Permessi del libro aggiornati', 'books_empty_contents' => 'Non ci sono pagine o capitoli per questo libro.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Elimina Bozza Pagina', 'pages_delete_success' => 'Pagina eliminata', 'pages_delete_draft_success' => 'Bozza di una pagina eliminata', + 'pages_delete_warning_template' => 'Questa pagina è in uso come pagina template predefinita di un libro. Dopo l\'eliminazione di questa pagina, a questi libri non verrà più assegnato una pagina template predefinita.', 'pages_delete_confirm' => 'Sei sicuro di voler eliminare questa pagina?', 'pages_delete_draft_confirm' => 'Sei sicuro di voler eliminare la bozza di questa pagina?', 'pages_editing_named' => 'Modifica :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Riferimenti', 'references_none' => 'Non ci sono riferimenti tracciati a questa voce.', - 'references_to_desc' => 'Di seguito sono riportate tutte le pagine conosciute nel sistema che collegano a questo elemento.', + 'references_to_desc' => 'Di seguito sono elencati tutti i contenuti noti del sistema che rimandano a questo elemento.', // Watch Options 'watch' => 'Osserva', diff --git a/lang/it/errors.php b/lang/it/errors.php index ab05b406c..6ec9c36a0 100644 --- a/lang/it/errors.php +++ b/lang/it/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'L\'estensione PHP LDAP non è installata', 'ldap_cannot_connect' => 'Impossibile connettersi al server ldap, connessione iniziale fallita', 'saml_already_logged_in' => 'Già loggato', - 'saml_user_not_registered' => 'L\'utente :name non è registrato e la registrazione automatica è disabilitata', 'saml_no_email_address' => 'Impossibile trovare un indirizzo email per questo utente nei dati forniti dal sistema di autenticazione esterno', 'saml_invalid_response_id' => 'La richiesta dal sistema di autenticazione esterno non è riconosciuta da un processo iniziato da questa applicazione. Tornare indietro dopo un login potrebbe causare questo problema.', 'saml_fail_authed' => 'Accesso con :system non riuscito, il sistema non ha fornito l\'autorizzazione corretta', diff --git a/lang/it/notifications.php b/lang/it/notifications.php index a17abe368..3b902600f 100644 --- a/lang/it/notifications.php +++ b/lang/it/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'Per evitare una massa di notifiche, per un po \'non verranno inviate notifiche per ulteriori modifiche a questa pagina dallo stesso editor.', 'detail_page_name' => 'Nome Della Pagina:', + 'detail_page_path' => 'Percorso della pagina:', 'detail_commenter' => 'Commentatore:', 'detail_comment' => 'Commento:', 'detail_created_by' => 'Creato Da:', diff --git a/lang/it/settings.php b/lang/it/settings.php index fac7e36f5..9ddde05b4 100644 --- a/lang/it/settings.php +++ b/lang/it/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Estone', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Francese', 'he' => 'Ebraico', 'hr' => 'Croato', diff --git a/lang/ja/entities.php b/lang/ja/entities.php index fd396b1de..dd4c27469 100644 --- a/lang/ja/entities.php +++ b/lang/ja/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => '更新: :timeLength', 'meta_updated_name' => '更新: :timeLength (:user)', 'meta_owned_name' => '所有者: :user', - 'meta_reference_page_count' => '{0}ページの参照はありません|[1,9]:count つのページから参照|[10,*]:count ページから参照', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'エンティティ選択', 'entity_select_lack_permission' => 'この項目を選択するために必要な権限がありません', 'images' => '画像', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'ブック「:bookName」を編集', 'books_form_book_name' => 'ブック名', 'books_save' => 'ブックを保存', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'ブックの権限', 'books_permissions_updated' => 'ブックの権限を更新しました', 'books_empty_contents' => 'まだページまたはチャプターが作成されていません。', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'ページの下書きを削除', 'pages_delete_success' => 'ページを削除しました', 'pages_delete_draft_success' => 'ページの下書きを削除しました', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'このページを削除してもよろしいですか?', 'pages_delete_draft_confirm' => 'このページの下書きを削除してもよろしいですか?', 'pages_editing_named' => 'ページ :pageName を編集', @@ -406,7 +410,7 @@ return [ // References 'references' => '参照', 'references_none' => 'この項目への追跡された参照はありません。', - 'references_to_desc' => 'この項目にリンクしている、システム内のすべての既知のページを以下に示します。', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'ウォッチ', diff --git a/lang/ja/errors.php b/lang/ja/errors.php index b54479a2d..26de050e4 100644 --- a/lang/ja/errors.php +++ b/lang/ja/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'LDAP PHP extensionがインストールされていません', 'ldap_cannot_connect' => 'LDAPサーバに接続できませんでした', 'saml_already_logged_in' => '既にログインしています', - 'saml_user_not_registered' => 'ユーザー :name は登録されておらず、自動登録は無効になっています', 'saml_no_email_address' => '外部認証システムから提供されたデータに、このユーザーのメールアドレスが見つかりませんでした', 'saml_invalid_response_id' => '外部認証システムからの要求がアプリケーションによって開始されたプロセスによって認識されません。ログイン後に戻るとこの問題が発生する可能性があります。', 'saml_fail_authed' => ':systemを利用したログインに失敗しました。システムは正常な認証を提供しませんでした。', diff --git a/lang/ja/notifications.php b/lang/ja/notifications.php index 820b13c8f..364b4a3aa 100644 --- a/lang/ja/notifications.php +++ b/lang/ja/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => '大量の通知を防ぐために、しばらくの間は同じユーザがこのページをさらに編集しても通知は送信されません。', 'detail_page_name' => 'ページ名:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'コメントユーザ:', 'detail_comment' => 'コメント:', 'detail_created_by' => '作成ユーザ:', diff --git a/lang/ja/settings.php b/lang/ja/settings.php index a1a9b74e9..1ef109d79 100644 --- a/lang/ja/settings.php +++ b/lang/ja/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/ka/entities.php b/lang/ka/entities.php index cfb5aae1a..f1f915544 100644 --- a/lang/ka/entities.php +++ b/lang/ka/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Updated :timeLength', 'meta_updated_name' => 'Updated :timeLength by :user', 'meta_owned_name' => 'Owned by :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Entity Select', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Images', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Edit Book :bookName', 'books_form_book_name' => 'Book Name', 'books_save' => 'Save Book', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Book Permissions', 'books_permissions_updated' => 'Book Permissions Updated', 'books_empty_contents' => 'No pages or chapters have been created for this book.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Delete Draft Page', 'pages_delete_success' => 'Page deleted', 'pages_delete_draft_success' => 'Draft page deleted', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Are you sure you want to delete this page?', 'pages_delete_draft_confirm' => 'Are you sure you want to delete this draft page?', 'pages_editing_named' => 'Editing Page :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/ka/errors.php b/lang/ka/errors.php index 8813cf90a..607b6ea83 100644 --- a/lang/ka/errors.php +++ b/lang/ka/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'LDAP PHP extension not installed', 'ldap_cannot_connect' => 'Cannot connect to ldap server, Initial connection failed', 'saml_already_logged_in' => 'Already logged in', - 'saml_user_not_registered' => 'The user :name is not registered and automatic registration is disabled', 'saml_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system', 'saml_invalid_response_id' => 'The request from the external authentication system is not recognised by a process started by this application. Navigating back after a login could cause this issue.', 'saml_fail_authed' => 'Login using :system failed, system did not provide successful authorization', diff --git a/lang/ka/notifications.php b/lang/ka/notifications.php index 5539ae9a9..1afd23f1d 100644 --- a/lang/ka/notifications.php +++ b/lang/ka/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/ka/settings.php b/lang/ka/settings.php index c5ca662c3..03e9bf462 100644 --- a/lang/ka/settings.php +++ b/lang/ka/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/ko/activities.php b/lang/ko/activities.php index 96313de1a..f9002431b 100644 --- a/lang/ko/activities.php +++ b/lang/ko/activities.php @@ -50,9 +50,9 @@ return [ 'bookshelf_delete_notification' => '책꽃이가 삭제되었습니다.', // Revisions - 'revision_restore' => 'restored revision', - 'revision_delete' => 'deleted revision', - 'revision_delete_notification' => 'Revision successfully deleted', + 'revision_restore' => '버전 복원', + 'revision_delete' => '버전 삭제', + 'revision_delete_notification' => '버전 삭제 성공', // Favourites 'favourite_add_notification' => '":name" 북마크에 추가함', @@ -63,17 +63,17 @@ return [ // Auth 'auth_login' => 'logged in', - 'auth_register' => 'registered as new user', - 'auth_password_reset_request' => 'requested user password reset', - 'auth_password_reset_update' => 'reset user password', + 'auth_register' => '신규 사용자 등록', + 'auth_password_reset_request' => '사용자 비밀번호 초기화 요청', + 'auth_password_reset_update' => '사용자 비밀번호 초기화', 'mfa_setup_method' => 'configured MFA method', 'mfa_setup_method_notification' => '다중 인증 설정함', 'mfa_remove_method' => 'removed MFA method', 'mfa_remove_method_notification' => '다중 인증 해제함', // Settings - 'settings_update' => 'updated settings', - 'settings_update_notification' => 'Settings successfully updated', + 'settings_update' => '설정 변경', + 'settings_update_notification' => '설졍 변경 성공', 'maintenance_action_run' => 'ran maintenance action', // Webhooks @@ -85,15 +85,15 @@ return [ 'webhook_delete_notification' => '웹 훅 삭제함', // Users - 'user_create' => 'created user', - 'user_create_notification' => 'User successfully created', - 'user_update' => 'updated user', + 'user_create' => '사용자 생성', + 'user_create_notification' => '사용자 생성 성공', + 'user_update' => '사용자 갱신', 'user_update_notification' => '사용자가 업데이트되었습니다', - 'user_delete' => 'deleted user', + 'user_delete' => '사용자 삭제', 'user_delete_notification' => '사용자가 삭제되었습니다', // API Tokens - 'api_token_create' => 'created api token', + 'api_token_create' => 'aPI 토큰 생성', 'api_token_create_notification' => 'API token successfully created', 'api_token_update' => 'updated api token', 'api_token_update_notification' => 'API token successfully updated', @@ -101,11 +101,11 @@ return [ 'api_token_delete_notification' => 'API token successfully deleted', // Roles - 'role_create' => 'created role', + 'role_create' => '역활 생성', 'role_create_notification' => '역할이 생성되었습니다', - 'role_update' => 'updated role', + 'role_update' => '역활 갱신', 'role_update_notification' => '역할이 수정되었습니다', - 'role_delete' => 'deleted role', + 'role_delete' => '역활 삭제', 'role_delete_notification' => '역할이 삭제되었습니다', // Recycle Bin @@ -115,9 +115,9 @@ return [ // Comments 'commented_on' => '댓글 쓰기', - 'comment_create' => 'added comment', - 'comment_update' => 'updated comment', - 'comment_delete' => 'deleted comment', + 'comment_create' => '댓글 생성', + 'comment_update' => '댓글 변경', + 'comment_delete' => '댓글 삭제', // Other 'permissions_update' => '권한 수정함', diff --git a/lang/ko/components.php b/lang/ko/components.php index 8df54a19e..66e1bde18 100644 --- a/lang/ko/components.php +++ b/lang/ko/components.php @@ -6,9 +6,9 @@ return [ // Image Manager 'image_select' => '이미지 선택', - 'image_list' => 'Image List', - 'image_details' => 'Image Details', - 'image_upload' => 'Upload Image', + 'image_list' => '이미지 목록', + 'image_details' => '이미지 상세정보', + 'image_upload' => '이미지 업로드', 'image_intro' => 'Here you can select and manage images that have been previously uploaded to the system.', 'image_intro_upload' => 'Upload a new image by dragging an image file into this window, or by using the "Upload Image" button above.', 'image_all' => '모든 이미지', @@ -17,22 +17,22 @@ return [ 'image_page_title' => '이 문서에서 쓰고 있는 이미지', 'image_search_hint' => '이미지 이름 검색', 'image_uploaded' => '올림 :uploadedDate', - 'image_uploaded_by' => 'Uploaded by :userName', - 'image_uploaded_to' => 'Uploaded to :pageLink', - 'image_updated' => 'Updated :updateDate', + 'image_uploaded_by' => '업로드:유저 닉네임', + 'image_uploaded_to' => '업로드:링크', + 'image_updated' => '갱신:갱신일', 'image_load_more' => '더 로드하기', 'image_image_name' => '이미지 이름', 'image_delete_used' => '이 이미지는 다음 문서들이 쓰고 있습니다.', 'image_delete_confirm_text' => '이 이미지를 지울 건가요?', 'image_select_image' => '이미지 선택', 'image_dropzone' => '여기에 이미지를 드롭하거나 여기를 클릭하세요. 이미지를 올릴 수 있습니다.', - 'image_dropzone_drop' => 'Drop images here to upload', + 'image_dropzone_drop' => '업로드 할 이미지 파일을 여기에 놓으세요.', 'images_deleted' => '이미지 삭제함', 'image_preview' => '이미지 미리 보기', 'image_upload_success' => '이미지 올림', 'image_update_success' => '이미지 정보 수정함', 'image_delete_success' => '이미지 삭제함', - 'image_replace' => 'Replace Image', + 'image_replace' => '이미지 교체', 'image_replace_success' => 'Image file successfully updated', 'image_rebuild_thumbs' => 'Regenerate Size Variations', 'image_rebuild_thumbs_success' => 'Image size variations successfully rebuilt!', diff --git a/lang/ko/entities.php b/lang/ko/entities.php index 86d4b9347..cd56d3ed8 100644 --- a/lang/ko/entities.php +++ b/lang/ko/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => '수정함 :timeLength', 'meta_updated_name' => '수정함 :timeLength, :user', 'meta_owned_name' => '소유함 :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => '항목 선택', 'entity_select_lack_permission' => '이 항목을 선택하기 위해 필요한 권한이 없습니다', 'images' => '이미지', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => ':bookName(을)를 바꿉니다.', 'books_form_book_name' => '책 이름', 'books_save' => '저장', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => '책 권한', 'books_permissions_updated' => '권한 저장함', 'books_empty_contents' => '이 책에 챕터나 문서가 없습니다.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => '초안 문서 삭제하기', 'pages_delete_success' => '문서 지움', 'pages_delete_draft_success' => '초안 문서 지움', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => '이 문서를 지울 건가요?', 'pages_delete_draft_confirm' => '이 초안을 지울 건가요?', 'pages_editing_named' => ':pageName 수정', @@ -405,7 +409,7 @@ return [ // References 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/ko/errors.php b/lang/ko/errors.php index 464774d29..a652cc697 100644 --- a/lang/ko/errors.php +++ b/lang/ko/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'PHP에 LDAP 확장 도구를 설치하세요.', 'ldap_cannot_connect' => 'LDAP 서버에 연결할 수 없습니다.', 'saml_already_logged_in' => '로그인 중입니다.', - 'saml_user_not_registered' => ':name 사용자가 없습니다. 자동 가입 옵션이 비활성 상태입니다.', 'saml_no_email_address' => '인증 시스템이 제공한 메일 주소가 없습니다.', 'saml_invalid_response_id' => '인증 시스템이 요청을 받지 못했습니다. 인증 후 이전 페이지로 돌아갈 때 발생할 수 있는 현상입니다.', 'saml_fail_authed' => ':system에 로그인할 수 없습니다.', diff --git a/lang/ko/notifications.php b/lang/ko/notifications.php index 5539ae9a9..1afd23f1d 100644 --- a/lang/ko/notifications.php +++ b/lang/ko/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/ko/settings.php b/lang/ko/settings.php index e2174ebc5..e60bb6646 100644 --- a/lang/ko/settings.php +++ b/lang/ko/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => '히브리어', 'hr' => 'Hrvatski', diff --git a/lang/lt/entities.php b/lang/lt/entities.php index 42a7e6018..9880ebb79 100644 --- a/lang/lt/entities.php +++ b/lang/lt/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Atnaujintas :timeLength', 'meta_updated_name' => 'Atnaujinta :timeLength naudotojo :user', 'meta_owned_name' => 'Priklauso :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Pasirinkti subjektą', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Nuotraukos', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Redaguoti knygą :bookName', 'books_form_book_name' => 'Knygos pavadinimas', 'books_save' => 'Išsaugoti knygą', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Knygos leidimas', 'books_permissions_updated' => 'Knygos leidimas atnaujintas', 'books_empty_contents' => 'Jokių puslapių ar skyrių nebuvo skurta šiai knygai', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Ištrinti juodraščio puslapį', 'pages_delete_success' => 'Puslapis ištrintas', 'pages_delete_draft_success' => 'Juodraščio puslapis ištrintas', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Ar esate tikri, kad norite ištrinti šį puslapį?', 'pages_delete_draft_confirm' => 'Ar esate tikri, kad norite ištrinti šį juodraščio puslapį?', 'pages_editing_named' => 'Redaguojamas puslapis :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/lt/errors.php b/lang/lt/errors.php index 44c9ace08..0e14b6517 100644 --- a/lang/lt/errors.php +++ b/lang/lt/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'LDAP PHP išplėtimas neįdiegtas', 'ldap_cannot_connect' => 'Negalima prisijungti prie LDAP serverio, nepavyko prisijungti', 'saml_already_logged_in' => 'Jau prisijungta', - 'saml_user_not_registered' => 'Naudotojas :name neužregistruotas ir automatinė registracija yra išjungta', 'saml_no_email_address' => 'Nerandamas šio naudotojo elektroninio pašto adresas išorinės autentifikavimo sistemos pateiktuose duomenyse', 'saml_invalid_response_id' => 'Prašymas iš išorinės autentifikavimo sistemos nėra atpažintas proceso, kurį pradėjo ši programa. Naršymas po prisijungimo gali sukelti šią problemą.', 'saml_fail_authed' => 'Prisijungimas, naudojant :system nepavyko, sistema nepateikė sėkmingo leidimo.', diff --git a/lang/lt/notifications.php b/lang/lt/notifications.php index 5539ae9a9..1afd23f1d 100644 --- a/lang/lt/notifications.php +++ b/lang/lt/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/lt/settings.php b/lang/lt/settings.php index 1e467ca3e..000e018c6 100644 --- a/lang/lt/settings.php +++ b/lang/lt/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/lv/entities.php b/lang/lv/entities.php index d23e27210..40115afff 100644 --- a/lang/lv/entities.php +++ b/lang/lv/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Atjaunināts :timeLength', 'meta_updated_name' => ':user atjauninājis pirms :timeLength', 'meta_owned_name' => 'Īpašnieks :user', - 'meta_reference_page_count' => 'Atsauce :count lapā|Atsauce :count lapās', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Izvēlēties vienumu', 'entity_select_lack_permission' => 'Jums nav nepieciešamās piekļuves tiesības, lai izvēlētu šo vienumu', 'images' => 'Attēli', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Labot grāmatu :bookName', 'books_form_book_name' => 'Grāmatas nosaukums', 'books_save' => 'Saglabāt grāmatu', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Grāmatas atļaujas', 'books_permissions_updated' => 'Grāmatas atļaujas atjauninātas', 'books_empty_contents' => 'Lapas vai nodaļas vēl nav izveidotas šai grāmatai.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Dzēst melnrakstu', 'pages_delete_success' => 'Lapa ir dzēsta', 'pages_delete_draft_success' => 'Melnraksts ir dzēsts', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Vai esat pārliecināts, ka vēlaties dzēst šo lapu?', 'pages_delete_draft_confirm' => 'Vai esat pārliecināts, ka vēlaties dzēst šo melnrakstu?', 'pages_editing_named' => 'Rediģē lapu :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Atsauces', 'references_none' => 'Uz šo vienumu nav atrasta neviena atsauce.', - 'references_to_desc' => 'Zemāk parādītas visas sistēmā atrastās lapas, kas norāda uz šo vienumu.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/lv/errors.php b/lang/lv/errors.php index d85a4f7fc..72abed6e6 100644 --- a/lang/lv/errors.php +++ b/lang/lv/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'LDAP PHP paplašinājums nav instalēts', 'ldap_cannot_connect' => 'Nav iespējams pieslēgties LDAP serverim, sākotnējais pieslēgums neveiksmīgs', 'saml_already_logged_in' => 'Jau ielogojies', - 'saml_user_not_registered' => 'Lietotājs :name nav reģistrēts un automātiska reģistrācija ir izslēgta', 'saml_no_email_address' => 'Ārējās autentifikācijas sistēmas sniegtajos datos nevarēja atrast šī lietotāja e-pasta adresi', 'saml_invalid_response_id' => 'Ārējās autentifikācijas sistēmas pieprasījums neatpazīst procesu, kuru sākusi šī lietojumprogramma. Pārvietojoties atpakaļ pēc pieteikšanās var rasties šāda problēma.', 'saml_fail_authed' => 'Piekļuve ar :system neizdevās, sistēma nepieļāva veiksmīgu autorizāciju', diff --git a/lang/lv/notifications.php b/lang/lv/notifications.php index b1cc3b6de..d0fee3950 100644 --- a/lang/lv/notifications.php +++ b/lang/lv/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Lapas nosaukums:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Komentētājs:', 'detail_comment' => 'Komentārs:', 'detail_created_by' => 'Izveidoja:', diff --git a/lang/lv/settings.php b/lang/lv/settings.php index 0864c6b37..a443d4977 100644 --- a/lang/lv/settings.php +++ b/lang/lv/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Igauņu', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/nb/common.php b/lang/nb/common.php index bcbf8e13a..92950250f 100644 --- a/lang/nb/common.php +++ b/lang/nb/common.php @@ -44,8 +44,8 @@ return [ 'configure' => 'Konfigurer', 'manage' => 'Administrer', 'fullscreen' => 'Fullskjerm', - 'favourite' => 'Favorisér', - 'unfavourite' => 'Avfavorisér', + 'favourite' => 'Favoriser', + 'unfavourite' => 'Avfavoriser', 'next' => 'Neste', 'previous' => 'Forrige', 'filter_active' => 'Aktivt filter:', diff --git a/lang/nb/components.php b/lang/nb/components.php index 8a8c69f17..e424e6493 100644 --- a/lang/nb/components.php +++ b/lang/nb/components.php @@ -20,7 +20,7 @@ return [ 'image_uploaded_by' => 'Lastet opp av :userName', 'image_uploaded_to' => 'Lastet opp til :pageLink', 'image_updated' => 'Oppdatert :updateDate', - 'image_load_more' => 'Last in flere', + 'image_load_more' => 'Last inn flere', 'image_image_name' => 'Bildenavn', 'image_delete_used' => 'Dette bildet er brukt på sidene nedenfor.', 'image_delete_confirm_text' => 'Vil du slette dette bildet?', diff --git a/lang/nb/entities.php b/lang/nb/entities.php index 87fbf5138..b843fcc0d 100644 --- a/lang/nb/entities.php +++ b/lang/nb/entities.php @@ -23,9 +23,9 @@ return [ 'meta_updated' => 'Oppdatert :timeLength', 'meta_updated_name' => 'Oppdatert :timeLength av :user', 'meta_owned_name' => 'Eies av :user', - 'meta_reference_page_count' => 'Sitert på :count side|Sitert på :count sider', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Velg entitet', - 'entity_select_lack_permission' => 'Do har ikke tilgang til å velge dette elementet', + 'entity_select_lack_permission' => 'Du har ikke tilgang til å velge dette elementet', 'images' => 'Bilder', 'my_recent_drafts' => 'Mine nylige utkast', 'my_recently_viewed' => 'Mine nylige visninger', @@ -44,7 +44,7 @@ return [ 'permissions' => 'Tilganger', 'permissions_desc' => 'Endringer gjort her vil overstyre standardrettigheter gitt via brukerroller.', 'permissions_book_cascade' => 'Rettigheter satt på bøker vil automatisk arves ned til sidenivå. Du kan overstyre arv ved å definere egne rettigheter på kapitler eller sider.', - 'permissions_chapter_cascade' => 'Rettigheter satt på kapitler vi automatisk arves ned til sider. Du kan overstyre arv ved å definere rettigheter på enkeltsider.', + 'permissions_chapter_cascade' => 'Rettigheter satt på kapitler vil automatisk arves ned til sider. Du kan overstyre arv ved å definere rettigheter på enkeltsider.', 'permissions_save' => 'Lagre tillatelser', 'permissions_owner' => 'Eier', 'permissions_role_everyone_else' => 'Alle andre', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Endre boken :bookName', 'books_form_book_name' => 'Boktittel', 'books_save' => 'Lagre bok', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Boktilganger', 'books_permissions_updated' => 'Boktilganger oppdatert', 'books_empty_contents' => 'Ingen sider eller kapitler finnes i denne boken.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Slett utkastet', 'pages_delete_success' => 'Siden er slettet', 'pages_delete_draft_success' => 'Sideutkastet ble slettet', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Er du sikker på at du vil slette siden?', 'pages_delete_draft_confirm' => 'Er du sikker på at du vil slette utkastet?', 'pages_editing_named' => 'Redigerer :pageName (side)', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Referanser', 'references_none' => 'Det er ingen sporede referanser til dette elementet.', - 'references_to_desc' => 'Nedenfor vises alle de kjente sidene i systemet som lenker til denne oppføringen.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Overvåk', diff --git a/lang/nb/errors.php b/lang/nb/errors.php index 941158744..c167fd49a 100644 --- a/lang/nb/errors.php +++ b/lang/nb/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'LDAP PHP modulen er ikke installert.', 'ldap_cannot_connect' => 'Klarer ikke koble til LDAP på denne adressen', 'saml_already_logged_in' => 'Allerede logget inn', - 'saml_user_not_registered' => 'Kontoen med navn :name er ikke registert, registrering er også deaktivert.', 'saml_no_email_address' => 'Denne kontoinformasjonen finnes ikke i det eksterne autentiseringssystemet.', 'saml_invalid_response_id' => 'Forespørselen fra det eksterne autentiseringssystemet gjenkjennes ikke av en prosess som startes av dette programmet. Å navigere tilbake etter pålogging kan forårsake dette problemet.', 'saml_fail_authed' => 'Innlogging gjennom :system feilet. Fikk ikke kontakt med autentiseringstjeneren.', diff --git a/lang/nb/notifications.php b/lang/nb/notifications.php index 78aad7181..0d4bc266f 100644 --- a/lang/nb/notifications.php +++ b/lang/nb/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'For å forhindre mange varslinger, vil du ikke få nye varslinger for endringer på denne siden fra samme forfatter.', 'detail_page_name' => 'Sidenavn:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Kommentar fra:', 'detail_comment' => 'Kommentar:', 'detail_created_by' => 'Opprettet av:', diff --git a/lang/nb/settings.php b/lang/nb/settings.php index 9f607519e..5d038c4f7 100644 --- a/lang/nb/settings.php +++ b/lang/nb/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/nl/entities.php b/lang/nl/entities.php index a408aa1e5..48625edd6 100644 --- a/lang/nl/entities.php +++ b/lang/nl/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Bijgewerkt: :timeLength', 'meta_updated_name' => 'Bijgewerkt: :timeLength door :user', 'meta_owned_name' => 'Eigendom van :user', - 'meta_reference_page_count' => 'Naartoe verwezen op :count pagina|Naartoe verwezen op :count pagina\'s', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Entiteit selecteren', 'entity_select_lack_permission' => 'Je hebt niet de vereiste machtiging om dit item te selecteren', 'images' => 'Afbeeldingen', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Bewerk boek :bookName', 'books_form_book_name' => 'Boek naam', 'books_save' => 'Boek opslaan', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Boek machtigingen', 'books_permissions_updated' => 'Boek Machtigingen Bijgewerkt', 'books_empty_contents' => 'Er zijn nog geen hoofdstukken en pagina\'s voor dit boek gemaakt.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Verwijder concept pagina', 'pages_delete_success' => 'Pagina verwijderd', 'pages_delete_draft_success' => 'Concept verwijderd', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Weet je zeker dat je deze pagina wilt verwijderen?', 'pages_delete_draft_confirm' => 'Weet je zeker dat je dit concept wilt verwijderen?', 'pages_editing_named' => 'Pagina :pageName bewerken', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Verwijzingen', 'references_none' => 'Er zijn geen verwijzingen naar dit artikel bijgehouden.', - 'references_to_desc' => 'Hieronder staan alle gekende pagina\'s in het systeem die naar dit item linken.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Volg', diff --git a/lang/nl/errors.php b/lang/nl/errors.php index f30014702..7a026615a 100644 --- a/lang/nl/errors.php +++ b/lang/nl/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'LDAP PHP-extensie is niet geïnstalleerd', 'ldap_cannot_connect' => 'Kan geen verbinding maken met de ldap server, initiële verbinding is mislukt', 'saml_already_logged_in' => 'Reeds ingelogd', - 'saml_user_not_registered' => 'De gebruiker :name is niet geregistreerd en automatische registratie is uitgeschakeld', 'saml_no_email_address' => 'In de gegevens van het externe verificatiesysteem kon voor deze gebruiker geen e-mailadres gevonden worden', 'saml_invalid_response_id' => 'Het verzoek van het externe authenticatiesysteem wordt niet herkend door een proces dat door deze applicatie wordt gestart. Terugkeren na inloggen kan dit probleem veroorzaken.', 'saml_fail_authed' => 'Inloggen met :system mislukt, het systeem gaf geen succesvolle autorisatie', diff --git a/lang/nl/notifications.php b/lang/nl/notifications.php index 9e173dff5..79c7e7e8b 100644 --- a/lang/nl/notifications.php +++ b/lang/nl/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'Om een stortvloed aan meldingen te voorkomen, zul je een tijdje geen meldingen ontvangen voor verdere bewerkingen van deze pagina door dezelfde redacteur.', 'detail_page_name' => 'Pagina Naam:', + 'detail_page_path' => 'Paginapad:', 'detail_commenter' => 'Reageerder:', 'detail_comment' => 'Opmerking:', 'detail_created_by' => 'Gemaakt Door:', diff --git a/lang/nl/settings.php b/lang/nl/settings.php index 6ef1d4a3c..c6e2ad2ae 100644 --- a/lang/nl/settings.php +++ b/lang/nl/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel (Estisch)', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français (Frans)', 'he' => 'עברית (Hebreeuws)', 'hr' => 'Hrvatski (Kroatisch)', diff --git a/lang/nn/activities.php b/lang/nn/activities.php index 3545db129..dfb7043c4 100644 --- a/lang/nn/activities.php +++ b/lang/nn/activities.php @@ -41,7 +41,7 @@ return [ // Bookshelves 'bookshelf_create' => 'oppretta hylle', - 'bookshelf_create_notification' => 'Hylla vart opprettet', + 'bookshelf_create_notification' => 'Hylla vart oppretta', 'bookshelf_create_from_book' => 'endra frå bok til hylle', 'bookshelf_create_from_book_notification' => 'Boka vart konvertert til ei bokhylle', 'bookshelf_update' => 'oppdaterte hylle', @@ -59,7 +59,7 @@ return [ 'favourite_remove_notification' => '«:name» vart fjerna frå dine favorittar', // Watching - 'watch_update_level_notification' => 'Overvåkingsinnstillingene vart oppdatert', + 'watch_update_level_notification' => 'Overvakingsinnstillingane vart oppdatert', // Auth 'auth_login' => 'logga inn', @@ -103,7 +103,7 @@ return [ // Roles 'role_create' => 'oppretta rolle', 'role_create_notification' => 'Rolla vart oppretta', - 'role_update' => 'oppdatert rolla', + 'role_update' => 'oppdatert rolle', 'role_update_notification' => 'Rolla vart oppdatert', 'role_delete' => 'sletta rolla', 'role_delete_notification' => 'Rolla vart fjerna', diff --git a/lang/nn/auth.php b/lang/nn/auth.php index 615f7bb9c..a6ae5917a 100644 --- a/lang/nn/auth.php +++ b/lang/nn/auth.php @@ -40,78 +40,78 @@ return [ // Login auto-initiation 'auto_init_starting' => 'Prøver innlogging', - 'auto_init_starting_desc' => 'Me kontaktar autentiseringssystemet ditt for å starte innloggingsprosessen. Dersom det ikkje er noko fremdrift i løpet av fem sekunder kan du trykke på lenka under.', + 'auto_init_starting_desc' => 'Me kontaktar autentiseringssystemet ditt for å starte innloggingsprosessen. Dersom det ikkje er noko framdrift i løpet av fem sekunder kan du trykke på lenka under.', 'auto_init_start_link' => 'Fortsett med autentisering', // Password Reset 'reset_password' => 'Nullstill passord', 'reset_password_send_instructions' => 'Oppgi e-posten kobla til kontoen din, så sender me ein e-post der du kan nullstille passordet.', 'reset_password_send_button' => 'Send nullstillingslenke', - 'reset_password_sent' => 'Ein nullstillingslenke vart sendt til :email om den eksisterer i systemet.', + 'reset_password_sent' => 'Ei nullstillingslenke vart sendt til :email om den eksisterer i systemet.', 'reset_password_success' => 'Passordet vart nullstilt.', 'email_reset_subject' => 'Nullstill ditt :appName passord', 'email_reset_text' => 'Du mottar denne e-posten fordi det er blitt bedt om ei nullstilling av passord for denne kontoen.', - 'email_reset_not_requested' => 'Om det ikkje var deg, så trenger du ikkje gjere noko.', + 'email_reset_not_requested' => 'Om det ikkje var deg, så treng du ikkje gjere noko.', // Email Confirmation - 'email_confirm_subject' => 'Stadfest epost-adressa for :appName', + 'email_confirm_subject' => 'Stadfest e-postadressa for :appName', 'email_confirm_greeting' => 'Takk for at du registrerte deg på :appName!', 'email_confirm_text' => 'Stadfest e-posten din ved å trykke på knappen under:', 'email_confirm_action' => 'Stadfest e-post', - 'email_confirm_send_error' => 'Bekreftelse er krevd av systemet, men systemet kan ikke sende disse. Kontakt admin for å løse problemet.', - 'email_confirm_success' => 'Epost-adressen din er verifisert! Du kan nå logge inn ved å bruke denne ved innlogging.', - 'email_confirm_resent' => 'Bekreftelsespost ble sendt, sjekk innboksen din.', - 'email_confirm_thanks' => 'Takk for verifiseringen!', - 'email_confirm_thanks_desc' => 'Vent et øyeblikk mens verifiseringen blir utført. Om du ikke blir videresendt i løpet av tre sekunder kan du trykke «Fortsett» nedenfor.', + 'email_confirm_send_error' => 'Stadfesting er krevd av systemet, men systemet kan ikkje sende desse. Kontakt admin for å løyse problemet.', + 'email_confirm_success' => 'E-postadressa di er verifisert! Du kan no logge inn ved å bruke denne ved innlogging.', + 'email_confirm_resent' => 'Stadfesting sendt på e-post, sjekk innboksen din.', + 'email_confirm_thanks' => 'Takk for verifiseringa!', + 'email_confirm_thanks_desc' => 'Vent litt medan me verifiserer. Om du ikkje vert sendt vidare i løpet av tre sekunder, kan du klikke på "Fortsett" under.', - 'email_not_confirmed' => 'E-posten er ikke bekreftet.', - 'email_not_confirmed_text' => 'Epost-adressen er ennå ikke bekreftet.', - 'email_not_confirmed_click_link' => 'Trykk på lenken i e-posten du fikk vedrørende din registrering.', - 'email_not_confirmed_resend' => 'Om du ikke finner den i innboksen eller søppelboksen, kan du få tilsendt ny ved å trykke på knappen under.', - 'email_not_confirmed_resend_button' => 'Send bekreftelsespost på nytt', + 'email_not_confirmed' => 'E-posten er ikkje stadfesta', + 'email_not_confirmed_text' => 'E-postadressa er ennå ikkje stadfesta.', + 'email_not_confirmed_click_link' => 'Trykk på lenka i e-posten du fekk då du registrerte deg.', + 'email_not_confirmed_resend' => 'Finner du den ikkje i innboks eller useriøs e-post? Trykk på knappen under for å få ny.', + 'email_not_confirmed_resend_button' => 'Send stadfesting på e-post på nytt', // User Invite 'user_invite_email_subject' => 'Du har blitt invitert til :appName!', - 'user_invite_email_greeting' => 'En konto har blitt opprettet for deg på :appName.', - 'user_invite_email_text' => 'Trykk på knappen under for å opprette et sikkert passord:', - 'user_invite_email_action' => 'Angi passord', + 'user_invite_email_greeting' => 'Ein konto har blitt oppretta for deg på :appName.', + 'user_invite_email_text' => 'Trykk på knappen under for å opprette eit sikkert passord:', + 'user_invite_email_action' => 'Skriv inn passord', 'user_invite_page_welcome' => 'Velkommen til :appName!', - 'user_invite_page_text' => 'For å fullføre prosessen må du oppgi et passord som sikrer din konto på :appName for fremtidige besøk.', - 'user_invite_page_confirm_button' => 'Bekreft passord', - 'user_invite_success_login' => 'Passordet ble satt, du skal nå kunne logge inn med ditt nye passord for å få tilgang til :appName!', + 'user_invite_page_text' => 'For å fullføre prosessen må du oppgi eit passord som sikrar din konto på :appName for neste besøk.', + 'user_invite_page_confirm_button' => 'Stadfest passord', + 'user_invite_success_login' => 'Passordet vart lagra, du skal nå kunne logge inn med ditt nye passord for å få tilgang til :appName!', // Multi-factor Authentication - 'mfa_setup' => 'Konfigurer flerfaktor-autentisering', - 'mfa_setup_desc' => 'Konfigurer flerfaktor-autentisering som et ekstra lag med sikkerhet for brukerkontoen din.', - 'mfa_setup_configured' => 'Allerede konfigurert', - 'mfa_setup_reconfigure' => 'Omkonfigurer', - 'mfa_setup_remove_confirmation' => 'Er du sikker på at du vil deaktivere denne flerfaktor-autentiseringsmetoden?', + 'mfa_setup' => 'Konfigurer fleirfaktor-autentisering', + 'mfa_setup_desc' => 'Konfigurer fleirfaktor-autentisering som eit ekstra lag med tryggleik for brukarkontoen din.', + 'mfa_setup_configured' => 'Allereie konfigurert', + 'mfa_setup_reconfigure' => 'Konfigurer på nytt', + 'mfa_setup_remove_confirmation' => 'Er du sikker på at du vil deaktivere denne fleirfaktor-autentiseringsmetoden?', 'mfa_setup_action' => 'Konfigurasjon', - 'mfa_backup_codes_usage_limit_warning' => 'Du har mindre enn 5 sikkerhetskoder igjen; vennligst generer og lagre ett nytt sett før du går tom for koder, for å unngå å bli låst ute av kontoen din.', + 'mfa_backup_codes_usage_limit_warning' => 'Du har mindre enn 5 tryggleikskodar igjen; generer gjerne nye og lagre eit nytt sett før du går tom for kodar. Då slepper du å bli låst ute frå kontoen din.', 'mfa_option_totp_title' => 'Mobilapplikasjon', - 'mfa_option_totp_desc' => 'For å bruke flerfaktorautentisering trenger du en mobilapplikasjon som støtter TOTP-teknologien, slik som Google Authenticator, Authy eller Microsoft Authenticator.', - 'mfa_option_backup_codes_title' => 'Sikkerhetskoder', - 'mfa_option_backup_codes_desc' => 'Lagre sikkerhetskoder til engangsbruk på et trygt sted, disse kan du bruke for å verifisere identiteten din.', - 'mfa_gen_confirm_and_enable' => 'Bekreft og aktiver', - 'mfa_gen_backup_codes_title' => 'Konfigurasjon av sikkerhetskoder', - 'mfa_gen_backup_codes_desc' => 'Lagre nedeforstående liste med koder på et trygt sted. Når du skal ha tilgang til systemet kan du bruke en av disse som en faktor under innlogging.', - 'mfa_gen_backup_codes_download' => 'Last ned koder', - 'mfa_gen_backup_codes_usage_warning' => 'Hver kode kan kun brukes en gang', + 'mfa_option_totp_desc' => 'For å bruka fleirfaktorautentisering treng du ein mobilapplikasjon som støttar TOTP-teknologien, slik som Google Authenticator, Authy eller Microsoft Authenticator.', + 'mfa_option_backup_codes_title' => 'Tryggleikskodar', + 'mfa_option_backup_codes_desc' => 'Lagre tryggleiksskodar til eingongsbruk på ein trygg stad, desse kan du bruka for å verifisera identiteten din.', + 'mfa_gen_confirm_and_enable' => 'Stadfest og aktiver', + 'mfa_gen_backup_codes_title' => 'Konfigurasjon av tryggleikskodar', + 'mfa_gen_backup_codes_desc' => 'Lagre lista under med kodar på ein trygg stad. Når du skal ha tilgang til systemet kan du bruka ein av desse som ein faktor under innlogging.', + 'mfa_gen_backup_codes_download' => 'Last ned kodar', + 'mfa_gen_backup_codes_usage_warning' => 'Kvar kode kan berre brukast ein gong', 'mfa_gen_totp_title' => 'Oppsett for mobilapplikasjon', - 'mfa_gen_totp_desc' => 'For å bruke flerfaktorautentisering trenger du en mobilapplikasjon som støtter TOTP-teknologien, slik som Google Authenticator, Authy eller Microsoft Authenticator.', - 'mfa_gen_totp_scan' => 'Scan QR-koden nedenfor med valgt TOTP-applikasjon for å starte.', - 'mfa_gen_totp_verify_setup' => 'Bekreft oppsett', - 'mfa_gen_totp_verify_setup_desc' => 'Bekreft at oppsettet fungerer ved å skrive inn koden fra TOTP-applikasjonen i boksen nedenfor:', + 'mfa_gen_totp_desc' => 'For å bruka fleirfaktorautentisering treng du ein mobilapplikasjon som støttar TOTP-teknologien, slik som Google Authenticator, Authy eller Microsoft Authenticator.', + 'mfa_gen_totp_scan' => 'Scan QR-koden nedanfor med vald TOTP-applikasjon for å starta.', + 'mfa_gen_totp_verify_setup' => 'Stadfest oppsett', + 'mfa_gen_totp_verify_setup_desc' => 'Stadfest at oppsettet fungerer ved å skrive inn koden fra TOTP-applikasjonen i boksen nedanfor:', 'mfa_gen_totp_provide_code_here' => 'Skriv inn den genererte koden her', - 'mfa_verify_access' => 'Bekreft tilgang', - 'mfa_verify_access_desc' => 'Brukerkontoen din krever at du bekrefter din identitet med en ekstra autentiseringsfaktor før du får tilgang. Bekreft identiteten med en av dine konfigurerte metoder for å fortsette.', - 'mfa_verify_no_methods' => 'Ingen metoder er konfigurert', - 'mfa_verify_no_methods_desc' => 'Ingen flerfaktorautentiseringsmetoder er satt opp for din konto. Du må sette opp minst en metode for å få tilgang.', - 'mfa_verify_use_totp' => 'Bekreft med mobilapplikasjon', - 'mfa_verify_use_backup_codes' => 'Bekreft med sikkerhetskode', - 'mfa_verify_backup_code' => 'Sikkerhetskode', - 'mfa_verify_backup_code_desc' => 'Skriv inn en av dine ubrukte sikkerhetskoder under:', - 'mfa_verify_backup_code_enter_here' => 'Skriv inn sikkerhetskode her', - 'mfa_verify_totp_desc' => 'Skriv inn koden, generert ved hjelp av mobilapplikasjonen, nedenfor:', - 'mfa_setup_login_notification' => 'Flerfaktorautentisering er konfigurert, vennligst logg inn på nytt med denne metoden.', + 'mfa_verify_access' => 'Stadfest tilgang', + 'mfa_verify_access_desc' => 'Brukarkontoen din krev at du stadfestar identiteten din med ein ekstra autentiseringsfaktor før du får tilgang. Stadfest identiteten med ein av dine konfigurerte metodar for å halda fram.', + 'mfa_verify_no_methods' => 'Ingen metodar er konfigurert', + 'mfa_verify_no_methods_desc' => 'Ingen fleirfaktorautentiseringsmetoder er satt opp for din konto. Du må setje opp minst ein metode for å få tilgang.', + 'mfa_verify_use_totp' => 'Stadfest med mobilapplikasjon', + 'mfa_verify_use_backup_codes' => 'Stadfest med tryggleikskode', + 'mfa_verify_backup_code' => 'Tryggleikskode', + 'mfa_verify_backup_code_desc' => 'Skriv inn ein av dei ubrukte tryggleikskodane dine under:', + 'mfa_verify_backup_code_enter_here' => 'Skriv inn tryggleikskode her', + 'mfa_verify_totp_desc' => 'Skriv inn koden, generert ved hjelp av mobilapplikasjonen, nedanfor:', + 'mfa_setup_login_notification' => 'Fleirfaktorautentisering er konfigurert, vennlegast logg inn på nytt med denne metoden.', ]; diff --git a/lang/nn/common.php b/lang/nn/common.php index d817fc355..74b39efa2 100644 --- a/lang/nn/common.php +++ b/lang/nn/common.php @@ -7,23 +7,23 @@ return [ // Buttons 'cancel' => 'Avbryt', 'close' => 'Lukk', - 'confirm' => 'Bekreft', + 'confirm' => 'Stadfest', 'back' => 'Tilbake', 'save' => 'Lagre', 'continue' => 'Fortsett', - 'select' => 'Velg', - 'toggle_all' => 'Bytt alle', - 'more' => 'Mer', + 'select' => 'Vel', + 'toggle_all' => 'Byt alle', + 'more' => 'Meir', // Form Labels - 'name' => 'Navn', - 'description' => 'Beskrivelse', + 'name' => 'Namn', + 'description' => 'Skildring', 'role' => 'Rolle', - 'cover_image' => 'Forside', - 'cover_image_description' => 'Bildet bør være ca. 440x250px.', + 'cover_image' => 'Framside', + 'cover_image_description' => 'Bilete bør vere ca. 440x250px.', // Actions - 'actions' => 'Handlinger', + 'actions' => 'Handlingar', 'view' => 'Vis', 'view_all' => 'Vis alle', 'new' => 'Ny', @@ -35,7 +35,7 @@ return [ 'copy' => 'Kopier', 'reply' => 'Svar', 'delete' => 'Slett', - 'delete_confirm' => 'Bekreft sletting', + 'delete_confirm' => 'Stadfest sletting', 'search' => 'Søk', 'search_clear' => 'Nullstill søk', 'reset' => 'Nullstill', @@ -44,39 +44,39 @@ return [ 'configure' => 'Konfigurer', 'manage' => 'Administrer', 'fullscreen' => 'Fullskjerm', - 'favourite' => 'Favorisér', - 'unfavourite' => 'Avfavorisér', + 'favourite' => 'Merk som favoritt', + 'unfavourite' => 'Fjern som favoritt', 'next' => 'Neste', - 'previous' => 'Forrige', + 'previous' => 'Førre', 'filter_active' => 'Aktivt filter:', 'filter_clear' => 'Tøm filter', 'download' => 'Last ned', - 'open_in_tab' => 'Åpne i fane', + 'open_in_tab' => 'Opne i fane', 'open' => 'Opne', // Sort Options - 'sort_options' => 'Sorteringsalternativer', + 'sort_options' => 'Sorteringsalternativ', 'sort_direction_toggle' => 'Sorteringsretning', - 'sort_ascending' => 'Stigende sortering', - 'sort_descending' => 'Synkende sortering', - 'sort_name' => 'Navn', + 'sort_ascending' => 'Stigande sortering', + 'sort_descending' => 'Synkande sortering', + 'sort_name' => 'Namn', 'sort_default' => 'Standard', - 'sort_created_at' => 'Dato opprettet', + 'sort_created_at' => 'Dato oppretta', 'sort_updated_at' => 'Dato oppdatert', // Misc - 'deleted_user' => 'Slett bruker', + 'deleted_user' => 'Slett brukar', 'no_activity' => 'Ingen aktivitet å vise', - 'no_items' => 'Ingen ting å vise', - 'back_to_top' => 'Hopp til toppen', - 'skip_to_main_content' => 'Gå til hovedinnhold', - 'toggle_details' => 'Vis/skjul detaljer', - 'toggle_thumbnails' => 'Vis/skjul miniatyrbilder', - 'details' => 'Detaljer', - 'grid_view' => 'Rutenettvisning', - 'list_view' => 'Listevisning', + 'no_items' => 'Ingenting å vise', + 'back_to_top' => 'Tilbake til toppen', + 'skip_to_main_content' => 'Gå til hovudinnhald', + 'toggle_details' => 'Vis/skjul detaljar', + 'toggle_thumbnails' => 'Vis/skjul miniatyrbilete', + 'details' => 'Detaljar', + 'grid_view' => 'Rutenettvising', + 'list_view' => 'Listevising', 'default' => 'Standard', - 'breadcrumb' => 'Brødsmuler', + 'breadcrumb' => 'Brødsmular', 'status' => 'Status', 'status_active' => 'Aktiv', 'status_inactive' => 'Inaktiv', @@ -84,11 +84,11 @@ return [ 'none' => 'Ingen', // Header - 'homepage' => 'Hjemmeside', + 'homepage' => 'Heimeside', 'header_menu_expand' => 'Utvid toppmeny', 'profile_menu' => 'Profilmeny', 'view_profile' => 'Vis profil', - 'edit_profile' => 'Endre Profile', + 'edit_profile' => 'Endre profil', 'dark_mode' => 'Kveldsmodus', 'light_mode' => 'Dagmodus', 'global_search' => 'Globalt søk', @@ -96,15 +96,15 @@ return [ // Layout tabs 'tab_info' => 'Informasjon', 'tab_info_label' => 'Fane: Vis tilleggsinfo', - 'tab_content' => 'Innhold', - 'tab_content_label' => 'Fane: Vis hovedinnhold', + 'tab_content' => 'Innhald', + 'tab_content_label' => 'Fane: Vis hovudinnhald', // Email Content - 'email_action_help' => 'Om du har problemer med å trykke på «:actionText»-knappen, bruk nettadressen under for å gå direkte dit:', - 'email_rights' => 'Kopibeskyttet', + 'email_action_help' => 'Om du har problem med å trykkja på ":actionText"-knappen, bruk nettadressa under for å gå direkte dit:', + 'email_rights' => 'Kopibeskytta', // Footer Link Options // Not directly used but available for convenience to users. - 'privacy_policy' => 'Personvernregler', + 'privacy_policy' => 'Personvernreglar', 'terms_of_service' => 'Bruksvilkår', ]; diff --git a/lang/nn/components.php b/lang/nn/components.php index 20abbc3af..881156427 100644 --- a/lang/nn/components.php +++ b/lang/nn/components.php @@ -5,42 +5,42 @@ return [ // Image Manager - 'image_select' => 'Velg bilde', - 'image_list' => 'Bilde liste', - 'image_details' => 'Bildedetaljer', - 'image_upload' => 'Last opp bilde', - 'image_intro' => 'Her kan du velge og behandle bilder som tidligere har blitt lastet opp til systemet.', - 'image_intro_upload' => 'Last opp et nytt bilde ved å dra et bilde i dette vinduet, eller ved å bruke knappen "Last opp bilde" ovenfor.', + 'image_select' => 'Vel bilete', + 'image_list' => 'Bileteliste', + 'image_details' => 'Biletedetaljar', + 'image_upload' => 'Last opp bilete', + 'image_intro' => 'Her kan du velja og behandla bilete som tidlegare har vorte lasta opp til systemet.', + 'image_intro_upload' => 'Last opp eit nytt bilete ved å dra eit bilete i dette vindauget, eller ved å bruka knappen "Last opp bilete" ovanfor.', 'image_all' => 'Alle', - 'image_all_title' => 'Vis alle bilder', - 'image_book_title' => 'Vis bilder som er lastet opp i denne boken', - 'image_page_title' => 'Vis bilder lastet opp til denne siden', - 'image_search_hint' => 'Søk på bilder etter navn', - 'image_uploaded' => 'Opplastet :uploadedDate', - 'image_uploaded_by' => 'Lastet opp av :userName', - 'image_uploaded_to' => 'Lastet opp til :pageLink', + 'image_all_title' => 'Vis alle bilete', + 'image_book_title' => 'Vis bilete som er lasta opp i denne boka', + 'image_page_title' => 'Vis bilete lastet opp til denne sida', + 'image_search_hint' => 'Søk på bilete etter namn', + 'image_uploaded' => 'Lasta opp :uploadedDate', + 'image_uploaded_by' => 'Lasta opp av :userName', + 'image_uploaded_to' => 'Lasta opp til :pageLink', 'image_updated' => 'Oppdatert :updateDate', - 'image_load_more' => 'Last in flere', - 'image_image_name' => 'Bildenavn', - 'image_delete_used' => 'Dette bildet er brukt på sidene nedenfor.', - 'image_delete_confirm_text' => 'Vil du slette dette bildet?', - 'image_select_image' => 'Velg bilde', - 'image_dropzone' => 'Dra og slipp eller trykk her for å laste opp bilder', - 'image_dropzone_drop' => 'Slipp bilder her for å laste opp', - 'images_deleted' => 'Bilder slettet', - 'image_preview' => 'Hurtigvisning av bilder', - 'image_upload_success' => 'Bilde ble lastet opp', - 'image_update_success' => 'Bildedetaljer ble oppdatert', - 'image_delete_success' => 'Bilde ble slettet', - 'image_replace' => 'Erstatt bilde', - 'image_replace_success' => 'Bildefil ble oppdatert', + 'image_load_more' => 'Last inn fleire', + 'image_image_name' => 'Biletenavn', + 'image_delete_used' => 'Dette biletet er brukt på sidene nedanfor.', + 'image_delete_confirm_text' => 'Vil du slette dette biletet?', + 'image_select_image' => 'Velg bilete', + 'image_dropzone' => 'Dra og slepp eller trykk her for å laste opp bilete', + 'image_dropzone_drop' => 'Slepp bilete her for å laste opp', + 'images_deleted' => 'Bilete sletta', + 'image_preview' => 'Snøggvising av bilete', + 'image_upload_success' => 'Bilete vart lasta opp', + 'image_update_success' => 'Biletedetaljar vart oppdatert', + 'image_delete_success' => 'Bilete vart sletta', + 'image_replace' => 'Erstatt bilete', + 'image_replace_success' => 'Biletefil vart oppdatert', 'image_rebuild_thumbs' => 'Regenerer ulike storleikar', 'image_rebuild_thumbs_success' => 'Bilete i ulike storleikar vart bygd på nytt!', // Code Editor 'code_editor' => 'Endre kode', 'code_language' => 'Kodespråk', - 'code_content' => 'Kodeinnhold', + 'code_content' => 'Kodeinnhald', 'code_session_history' => 'Sesjonshistorikk', 'code_save' => 'Lagre kode', ]; diff --git a/lang/nn/entities.php b/lang/nn/entities.php index bed008b73..f2ad69677 100644 --- a/lang/nn/entities.php +++ b/lang/nn/entities.php @@ -6,76 +6,76 @@ return [ // Shared - 'recently_created' => 'Nylig opprettet', - 'recently_created_pages' => 'Nylig opprettede sider', - 'recently_updated_pages' => 'Nylig oppdaterte sider', - 'recently_created_chapters' => 'Nylig opprettede kapitler', - 'recently_created_books' => 'Nylig opprettede bøker', - 'recently_created_shelves' => 'Nylig opprettede bokhyller', - 'recently_update' => 'Nylig oppdatert', - 'recently_viewed' => 'Nylig vist', - 'recent_activity' => 'Nylig aktivitet', - 'create_now' => 'Opprett en nå', - 'revisions' => 'Revisjoner', + 'recently_created' => 'Nylig oppretta', + 'recently_created_pages' => 'Nyleg oppretta sider', + 'recently_updated_pages' => 'Nyleg oppdaterte sider', + 'recently_created_chapters' => 'Nyleg oppretta kapitler', + 'recently_created_books' => 'Nyleg oppretta bøker', + 'recently_created_shelves' => 'Nyleg oppretta bokhyller', + 'recently_update' => 'Nyleg oppdatert', + 'recently_viewed' => 'Nyleg vist', + 'recent_activity' => 'Nyleg aktivitet', + 'create_now' => 'Opprett ein no', + 'revisions' => 'Revisjonar', 'meta_revision' => 'Revisjon #:revisionCount', - 'meta_created' => 'Opprettet :timeLength', - 'meta_created_name' => 'Opprettet :timeLength av :user', + 'meta_created' => 'Oppretta :timeLength', + 'meta_created_name' => 'Oppretta :timeLength av :user', 'meta_updated' => 'Oppdatert :timeLength', 'meta_updated_name' => 'Oppdatert :timeLength av :user', - 'meta_owned_name' => 'Eies av :user', - 'meta_reference_page_count' => 'Sitert på :count side|Sitert på :count sider', + 'meta_owned_name' => 'Eigd av :user', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Velg entitet', - 'entity_select_lack_permission' => 'Do har ikke tilgang til å velge dette elementet', - 'images' => 'Bilder', - 'my_recent_drafts' => 'Mine nylige utkast', - 'my_recently_viewed' => 'Mine nylige visninger', - 'my_most_viewed_favourites' => 'Mine mest sette favoritter', - 'my_favourites' => 'Mine favoritter', - 'no_pages_viewed' => 'Du har ikke sett på noen sider', - 'no_pages_recently_created' => 'Ingen sider har nylig blitt opprettet', + 'entity_select_lack_permission' => 'Du har ikkje tilgang til å velge dette elementet', + 'images' => 'Bilete', + 'my_recent_drafts' => 'Mine nylege utkast', + 'my_recently_viewed' => 'Mine nylege visingar', + 'my_most_viewed_favourites' => 'Mine mest sette favorittar', + 'my_favourites' => 'Mine favorittar', + 'no_pages_viewed' => 'Du har ikkje sett på nokre sider', + 'no_pages_recently_created' => 'Ingen sider har nylig blitt oppretta', 'no_pages_recently_updated' => 'Ingen sider har nylig blitt oppdatert', 'export' => 'Eksporter', 'export_html' => 'Nettside med alt', - 'export_pdf' => 'PDF Fil', + 'export_pdf' => 'PDF-fil', 'export_text' => 'Tekstfil', 'export_md' => 'Markdownfil', // Permissions and restrictions - 'permissions' => 'Tilganger', - 'permissions_desc' => 'Endringer gjort her vil overstyre standardrettigheter gitt via brukerroller.', - 'permissions_book_cascade' => 'Rettigheter satt på bøker vil automatisk arves ned til sidenivå. Du kan overstyre arv ved å definere egne rettigheter på kapitler eller sider.', - 'permissions_chapter_cascade' => 'Rettigheter satt på kapitler vi automatisk arves ned til sider. Du kan overstyre arv ved å definere rettigheter på enkeltsider.', - 'permissions_save' => 'Lagre tillatelser', - 'permissions_owner' => 'Eier', + 'permissions' => 'Tilgongar', + 'permissions_desc' => 'Endringar gjort her vil overstyra standardrettar gitt via brukarroller.', + 'permissions_book_cascade' => 'Rettar sett på bøker vil automatisk arvast ned til sidenivå. Du kan overstyra arv ved å definera eigne rettar på kapittel eller sider.', + 'permissions_chapter_cascade' => 'Rettar sett på kapittel vil automatisk arvast ned til sider. Du kan overstyra arv ved å definera rettar på enkeltsider.', + 'permissions_save' => 'Lagre løyve', + 'permissions_owner' => 'Eigar', 'permissions_role_everyone_else' => 'Alle andre', - 'permissions_role_everyone_else_desc' => 'Angi rettigheter for alle roller som ikke blir overstyrt (arvede rettigheter).', - 'permissions_role_override' => 'Overstyr rettigheter for rolle', - 'permissions_inherit_defaults' => 'Arv standardrettigheter', + 'permissions_role_everyone_else_desc' => 'Angi rettar for alle roller som ikkje blir overstyrt (arva rettar).', + 'permissions_role_override' => 'Overstyr rettar for rolle', + 'permissions_inherit_defaults' => 'Arv standardrettar', // Search - 'search_results' => 'Søkeresultater', - 'search_total_results_found' => ':count resultater funnet|:count totalt', + 'search_results' => 'Søkeresultat', + 'search_total_results_found' => ':count resultat funne|:count totalt', 'search_clear' => 'Nullstill søk', - 'search_no_pages' => 'Ingen sider passer med søket', + 'search_no_pages' => 'Ingen sider passar med søket', 'search_for_term' => 'Søk etter :term', - 'search_more' => 'Flere resultater', + 'search_more' => 'Fleire resultat', 'search_advanced' => 'Avansert søk', 'search_terms' => 'Søkeord', - 'search_content_type' => 'Innholdstype', + 'search_content_type' => 'Innhaldstype', 'search_exact_matches' => 'Eksakte ord', - 'search_tags' => 'Søk på merker', - 'search_options' => 'ALternativer', + 'search_tags' => 'Søk på merke', + 'search_options' => 'Alternativ', 'search_viewed_by_me' => 'Sett av meg', - 'search_not_viewed_by_me' => 'Ikke sett av meg', - 'search_permissions_set' => 'Tilganger er angitt', - 'search_created_by_me' => 'Opprettet av meg', + 'search_not_viewed_by_me' => 'Ikkje sett av meg', + 'search_permissions_set' => 'Tilgongar er sett', + 'search_created_by_me' => 'Oppretta av meg', 'search_updated_by_me' => 'Oppdatert av meg', - 'search_owned_by_me' => 'Eid av meg', - 'search_date_options' => 'Datoalternativer', + 'search_owned_by_me' => 'Eigd av meg', + 'search_date_options' => 'Datoalternativ', 'search_updated_before' => 'Oppdatert før', 'search_updated_after' => 'Oppdatert etter', - 'search_created_before' => 'Opprettet før', - 'search_created_after' => 'Opprettet etter', + 'search_created_before' => 'Oppretta før', + 'search_created_after' => 'Oppretta etter', 'search_set_date' => 'Angi dato', 'search_update' => 'Oppdater søk', @@ -83,34 +83,34 @@ return [ 'shelf' => 'Hylle', 'shelves' => 'Hyller', 'x_shelves' => ':count hylle|:count hyller', - 'shelves_empty' => 'Ingen bokhyller er opprettet', + 'shelves_empty' => 'Ingen bokhyller er oppretta', 'shelves_create' => 'Opprett ny bokhylle', 'shelves_popular' => 'Populære bokhyller', 'shelves_new' => 'Nye bokhyller', 'shelves_new_action' => 'Ny bokhylle', - 'shelves_popular_empty' => 'De mest populære bokhyllene blir vist her.', + 'shelves_popular_empty' => 'Dei mest populære bokhyllene blir vist her.', 'shelves_new_empty' => 'Nylig opprettede bokhyller vises her.', 'shelves_save' => 'Lagre hylle', - 'shelves_books' => 'Bøker på denne hyllen', + 'shelves_books' => 'Bøker på denne hylla', 'shelves_add_books' => 'Legg til bøker på denne hyllen', - 'shelves_drag_books' => 'Dra og slipp bøker nedenfor for å legge dem til i denne hyllen', + 'shelves_drag_books' => 'Dra og slepp bøker nedanfor for å legge dei til i denne hylla', 'shelves_empty_contents' => 'Ingen bøker er stabla i denne hylla', 'shelves_edit_and_assign' => 'Endre hylla for å legge til bøker', 'shelves_edit_named' => 'Rediger :name (hylle)', 'shelves_edit' => 'Rediger hylle', 'shelves_delete' => 'Fjern hylle', 'shelves_delete_named' => 'Fjern :name (hylle)', - 'shelves_delete_explain' => "Dette vil fjerne hyllen «:name». Bøkene på hyllen vil ikke bli slettet fra systemet.", - 'shelves_delete_confirmation' => 'Er du sikker på at du vil fjerne denne hyllen?', - 'shelves_permissions' => 'Hyllerettigheter', - 'shelves_permissions_updated' => 'Oppdaterte hyllerettigheter', - 'shelves_permissions_active' => 'Aktiverte hyllerettigheter', - 'shelves_permissions_cascade_warning' => 'Rettigheter på en hylle blir ikke automatisk arvet av bøker på hylla. Dette er fordi en bok kan finnes på flere hyller samtidig. Rettigheter kan likevel kopieres til bøker på hylla ved å bruke alternativene under.', - 'shelves_permissions_create' => 'Bokhylle-tillatelser brukes kun for kopiering av tillatelser til under-bøker ved hjelp av handlingen nedenfor. De kontrollerer ikke muligheten til å lage bøker.', - 'shelves_copy_permissions_to_books' => 'Kopier tilganger til bøkene på hylla', - 'shelves_copy_permissions' => 'Kopier tilganger', - 'shelves_copy_permissions_explain' => 'Dette vil kopiere rettighetene på denne hylla til alle bøkene som er plassert på den. Før du starter kopieringen bør du sjekke at rettighetene på hylla er lagret først.', - 'shelves_copy_permission_success' => 'Rettighetene ble kopiert til :count bøker', + 'shelves_delete_explain' => "Dette vil fjerne hylla «:name». Bøkene på hylla vil ikkje bli sletta frå systemet.", + 'shelves_delete_confirmation' => 'Er du sikker på at du vil fjerne denne hylla?', + 'shelves_permissions' => 'Hylletilgangar', + 'shelves_permissions_updated' => 'Oppdaterte hylletilgangar', + 'shelves_permissions_active' => 'Aktiverte hylletilgangar', + 'shelves_permissions_cascade_warning' => 'Tilgangar på ei hylle vert ikkje automatisk arva av bøker på hylla. Dette er fordi ei bok kan finnast på fleire hyller samstundes. Tilgangar kan likevel verte kopiert til bøker på hylla ved å bruke alternativa under.', + 'shelves_permissions_create' => 'Bokhylle-tilgangar vert brukt for kopiering av løyver til under-bøker ved hjelp av handlinga nedanfor. Dei kontrollerer ikkje rettane til å lage bøker.', + 'shelves_copy_permissions_to_books' => 'Kopier tilgangar til bøkene på hylla', + 'shelves_copy_permissions' => 'Kopier tilgangar', + 'shelves_copy_permissions_explain' => 'Dette vil kopiere tilgangar på denne hylla til alle bøkene som er plassert på den. Før du starter kopieringen bør du sjekke at tilgangane på hylla er lagra.', + 'shelves_copy_permission_success' => 'Løyver vart kopiert til :count bøker', // Books 'book' => 'Bok', @@ -132,58 +132,61 @@ return [ 'books_edit_named' => 'Endre boken :bookName', 'books_form_book_name' => 'Boktittel', 'books_save' => 'Lagre bok', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Boktilganger', 'books_permissions_updated' => 'Boktilganger oppdatert', - 'books_empty_contents' => 'Ingen sider eller kapitler finnes i denne boken.', - 'books_empty_create_page' => 'Skriv en ny side', - 'books_empty_sort_current_book' => 'Sorter innholdet i boken', + 'books_empty_contents' => 'Ingen sider eller kapittel finst i denne boka.', + 'books_empty_create_page' => 'Skriv ei ny side', + 'books_empty_sort_current_book' => 'Sorter innhaldet i boka', 'books_empty_add_chapter' => 'Start på nytt kapittel', - 'books_permissions_active' => 'Boktilganger er aktive', - 'books_search_this' => 'Søk i boken', + 'books_permissions_active' => 'Boktilgangar er aktive', + 'books_search_this' => 'Søk i boka', 'books_navigation' => 'Boknavigasjon', - 'books_sort' => 'Sorter bokinnhold', - 'books_sort_desc' => 'Flytt kapitler og sider inni en bok for å omorganisere dem. Andre bøker kan bli lagt til slik at det er enklere å flytte frem og tilbake mellom dem.', - 'books_sort_named' => 'Omorganisér :bookName (bok)', - 'books_sort_name' => 'Sorter på navn', - 'books_sort_created' => 'Sorter på opprettet dato', + 'books_sort' => 'Sorter bokinnhald', + 'books_sort_desc' => 'Flytt kapittel og sider i ei bok for å omorganisere dei. Andre bøker kan bli lagt til slik at det er enklere å flytte fram og tilbake mellom dei.', + 'books_sort_named' => 'Omorganiser :bookName', + 'books_sort_name' => 'Sorter på namn', + 'books_sort_created' => 'Sorter på oppretta dato', 'books_sort_updated' => 'Sorter på oppdatert dato', - 'books_sort_chapters_first' => 'Kapitler først', - 'books_sort_chapters_last' => 'Kapitler sist', + 'books_sort_chapters_first' => 'Kapittel først', + 'books_sort_chapters_last' => 'Kapittel sist', 'books_sort_show_other' => 'Vis andre bøker', 'books_sort_save' => 'Lagre sortering', - 'books_sort_show_other_desc' => 'Legg til andre bøker her for å inkludere dem i omorganiseringen og muliggjør enkel flytting på tvers av dem.', + 'books_sort_show_other_desc' => 'Legg til andre bøker her for å inkludere dei i omorganiseringa og gjer det enklare å flytte på tvers av dei.', 'books_sort_move_up' => 'Flytt opp', 'books_sort_move_down' => 'Flytt ned', - 'books_sort_move_prev_book' => 'Flytt til forrige bok', + 'books_sort_move_prev_book' => 'Flytt til førre bok', 'books_sort_move_next_book' => 'Flytt til neste bok', - 'books_sort_move_prev_chapter' => 'Flytt inn i forrige kapittel', + 'books_sort_move_prev_chapter' => 'Flytt inn i førre kapittel', 'books_sort_move_next_chapter' => 'Flytt inn i neste kapittel', - 'books_sort_move_book_start' => 'Flytt til starten av boken', - 'books_sort_move_book_end' => 'Flytt til slutten av boken', + 'books_sort_move_book_start' => 'Flytt til starten av boka', + 'books_sort_move_book_end' => 'Flytt til slutten av boka', 'books_sort_move_before_chapter' => 'Flytt før kapittel', 'books_sort_move_after_chapter' => 'Flytt etter kapittel', - 'books_copy' => 'Kopiér bok', - 'books_copy_success' => 'Boken ble kopiert', + 'books_copy' => 'Kopier bok', + 'books_copy_success' => 'Boka vart kopiert', // Chapters 'chapter' => 'Kapittel', - 'chapters' => 'Kapitler', - 'x_chapters' => ':count kapittel|:count kapitler', - 'chapters_popular' => 'Populære kapitler', + 'chapters' => 'Kapittel', + 'x_chapters' => ':count kapittel|:count kapittel', + 'chapters_popular' => 'Populære kapittel', 'chapters_new' => 'Nytt kapittel', 'chapters_create' => 'Skriv nytt kapittel', 'chapters_delete' => 'Riv ut kapittel', - 'chapters_delete_named' => 'Slett :chapterName (kapittel)', - 'chapters_delete_explain' => 'Dette vil slette «:chapterName» (kapittel). Alle sider i kapittelet vil også slettes.', + 'chapters_delete_named' => 'Slett :chapterName', + 'chapters_delete_explain' => 'Dette vil slette «:chapterName». Alle sider i kapittelet vil og verte sletta.', 'chapters_delete_confirm' => 'Er du sikker på at du vil slette dette kapittelet?', - 'chapters_edit' => 'Redigér kapittel', - 'chapters_edit_named' => 'Redigér :chapterName (kapittel)', + 'chapters_edit' => 'Rediger kapittel', + 'chapters_edit_named' => 'Rediger :chapterName', 'chapters_save' => 'Lagre kapittel', 'chapters_move' => 'Flytt kapittel', - 'chapters_move_named' => 'Flytt :chapterName (kapittel)', - 'chapters_copy' => 'Kopiér kapittel', - 'chapters_copy_success' => 'Kapitelet ble kopiert', - 'chapters_permissions' => 'Kapitteltilganger', + 'chapters_move_named' => 'Flytt :chapterName', + 'chapters_copy' => 'Kopier kapittel', + 'chapters_copy_success' => 'Kapittelet vart kopiert', + 'chapters_permissions' => 'Kapitteltilgongar', 'chapters_empty' => 'Det finnes ingen sider i dette kapittelet.', 'chapters_permissions_active' => 'Kapitteltilganger er aktivert', 'chapters_permissions_success' => 'Kapitteltilgager er oppdatert', @@ -203,7 +206,8 @@ return [ 'pages_delete_draft_named' => 'Slett utkastet :pageName (side)', 'pages_delete_draft' => 'Slett utkastet', 'pages_delete_success' => 'Siden er slettet', - 'pages_delete_draft_success' => 'Sideutkastet ble slettet', + 'pages_delete_draft_success' => 'Sideutkastet vart sletta', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Er du sikker på at du vil slette siden?', 'pages_delete_draft_confirm' => 'Er du sikker på at du vil slette utkastet?', 'pages_editing_named' => 'Redigerer :pageName (side)', @@ -221,54 +225,54 @@ return [ 'pages_edit_switch_to_markdown_stable' => '(Urørt innhold)', 'pages_edit_switch_to_wysiwyg' => 'Bytt til WYSIWYG tekstredigering', 'pages_edit_set_changelog' => 'Angi endringslogg', - 'pages_edit_enter_changelog_desc' => 'Gi en kort beskrivelse av endringene dine', - 'pages_edit_enter_changelog' => 'Se endringslogg', + 'pages_edit_enter_changelog_desc' => 'Gi ei kort skildring av endringane dine', + 'pages_edit_enter_changelog' => 'Sjå endringslogg', 'pages_editor_switch_title' => 'Bytt tekstredigeringsprogram', - 'pages_editor_switch_are_you_sure' => 'Er du sikker på at du vil bytte tekstredigeringsprogram for denne siden?', - 'pages_editor_switch_consider_following' => 'Husk dette når du bytter tekstredigeringsprogram:', - 'pages_editor_switch_consideration_a' => 'Når du bytter, vil den nye tekstredigereren bli satt for alle fremtidige redaktører. Dette inkluderer alle redaktører som ikke kan endre type selv.', - 'pages_editor_switch_consideration_b' => 'Dette kan potensielt føre til tap av formatdetaljer eller syntaks i noen tilfeller.', - 'pages_editor_switch_consideration_c' => 'Etikett- eller redigeringslogg-endringer loggført siden siste lagring vil ikke føres videre etter endringen.', + 'pages_editor_switch_are_you_sure' => 'Er du sikker på at du vil bytte tekstredigeringsprogram for denne sida?', + 'pages_editor_switch_consider_following' => 'Hugs dette når du byttar tekstredigeringsprogram:', + 'pages_editor_switch_consideration_a' => 'Når du bytter, vil den nye tekstredigeraren bli valgt for alle framtidige redaktørar. Dette inkluderer alle redaktørar som ikkje kan endre type sjølv.', + 'pages_editor_switch_consideration_b' => 'I visse tilfeller kan det føre til tap av detaljar og syntaks.', + 'pages_editor_switch_consideration_c' => 'Etikett- eller redigeringslogg-endringar loggført sidan siste lagring vil ikkje føres vidare etter endringa.', 'pages_save' => 'Lagre side', 'pages_title' => 'Sidetittel', - 'pages_name' => 'Sidenavn', - 'pages_md_editor' => 'Tekstbehandler', - 'pages_md_preview' => 'Forhåndsvisning', - 'pages_md_insert_image' => 'Sett inn bilde', + 'pages_name' => 'Sidenamn', + 'pages_md_editor' => 'Tekstbehandlar', + 'pages_md_preview' => 'Førehandsvising', + 'pages_md_insert_image' => 'Sett inn bilete', 'pages_md_insert_link' => 'Sett inn lenke', 'pages_md_insert_drawing' => 'Sett inn tegning', - 'pages_md_show_preview' => 'Forhåndsvisning', - 'pages_md_sync_scroll' => 'Synkroniser forhåndsvisningsrulle', - 'pages_drawing_unsaved' => 'Ulagret tegning funnet', - 'pages_drawing_unsaved_confirm' => 'Ulagret tegningsdata ble funnet fra en tidligere mislykket lagring. Vil du gjenopprette og fortsette å redigere denne ulagrede tegningen?', - 'pages_not_in_chapter' => 'Siden tilhører ingen kapittel', - 'pages_move' => 'Flytt side', - 'pages_copy' => 'Kopiér side', + 'pages_md_show_preview' => 'Førhandsvisning', + 'pages_md_sync_scroll' => 'Synkroniser førehandsvisingsrulle', + 'pages_drawing_unsaved' => 'Ulagra teikning funne', + 'pages_drawing_unsaved_confirm' => 'Ulagra teikninga vart funne frå ei tidligare mislykka lagring. Vil du gjenopprette og fortsette å redigere denne ulagra teikninga?', + 'pages_not_in_chapter' => 'Sida tilhøyrer ingen kapittel', + 'pages_move' => 'Flytt sida', + 'pages_copy' => 'Kopier side', 'pages_copy_desination' => 'Destinasjon', - 'pages_copy_success' => 'Siden ble flyttet', - 'pages_permissions' => 'Sidetilganger', - 'pages_permissions_success' => 'Sidens tilganger ble endret', + 'pages_copy_success' => 'Sida vart flytta', + 'pages_permissions' => 'Sidetilgangar', + 'pages_permissions_success' => 'Sidetilgangar vart endra', 'pages_revision' => 'Revisjon', - 'pages_revisions' => 'Sidens revisjoner', - 'pages_revisions_desc' => 'Oppført nedenfor er alle tidligere revisjoner av denne siden. Du kan se tilbake igjen, sammenligne og gjenopprette tidligere sideversjoner hvis du tillater det. Den hele sidens historikk kan kanskje ikke gjenspeiles fullstendig her, avhengig av systemkonfigurasjonen, kan gamle revisjoner bli slettet automatisk.', - 'pages_revisions_named' => 'Revisjoner for :pageName', - 'pages_revision_named' => 'Revisjoner for :pageName', - 'pages_revision_restored_from' => 'Gjenopprettet fra #:id; :summary', - 'pages_revisions_created_by' => 'Skrevet av', + 'pages_revisions' => 'Revisjonar for sida', + 'pages_revisions_desc' => 'Nedanfor er alle tidlegare revisjonar av denne sida. Du kan sjå tilbake igjen, samanlikna og retta opp igjen tidlegare sideversjonar viss du tillet det. Den heile historikken til sida kan kanskje ikkje speglast fullstendig her. Avhengig av systemkonfigurasjonen, kan gamle revisjonar bli sletta automatisk.', + 'pages_revisions_named' => 'Revisjonar for :pageName', + 'pages_revision_named' => 'Revisjonar for :pageName', + 'pages_revision_restored_from' => 'Gjenoppretta fra #:id; :summary', + 'pages_revisions_created_by' => 'Skrive av', 'pages_revisions_date' => 'Revideringsdato', 'pages_revisions_number' => '#', 'pages_revisions_sort_number' => 'Revisjonsnummer', 'pages_revisions_numbered' => 'Revisjon #:id', - 'pages_revisions_numbered_changes' => 'Endringer på revisjon #:id', + 'pages_revisions_numbered_changes' => 'Endringar på revisjon #:id', 'pages_revisions_editor' => 'Tekstredigeringstype', 'pages_revisions_changelog' => 'Endringslogg', - 'pages_revisions_changes' => 'Endringer', + 'pages_revisions_changes' => 'Endringar', 'pages_revisions_current' => 'Siste versjon', 'pages_revisions_preview' => 'Forhåndsvisning', 'pages_revisions_restore' => 'Gjenopprett', 'pages_revisions_none' => 'Denne siden har ingen revisjoner', 'pages_copy_link' => 'Kopier lenke', - 'pages_edit_content_link' => 'Hopp til seksjonen i tekstbehandleren', + 'pages_edit_content_link' => 'Hopp til seksjonen i tekstbehandlaren', 'pages_pointer_enter_mode' => 'Gå til seksjonen velg modus', 'pages_pointer_label' => 'Sidens seksjon alternativer', 'pages_pointer_permalink' => 'Sideseksjons permalenke', @@ -279,18 +283,18 @@ return [ 'pages_initial_revision' => 'Første publisering', 'pages_references_update_revision' => 'Automatisk oppdatering av interne lenker', 'pages_initial_name' => 'Ny side', - 'pages_editing_draft_notification' => 'Du skriver på et utkast som sist ble lagret :timeDiff.', + 'pages_editing_draft_notification' => 'Du skriver på eit utkast som sist vart lagra :timeDiff.', 'pages_draft_edited_notification' => 'Siden har blitt endret siden du startet. Det anbefales at du forkaster dine endringer.', - 'pages_draft_page_changed_since_creation' => 'Denne siden er blitt oppdatert etter at dette utkastet ble opprettet. Det anbefales at du forkaster dette utkastet, eller er ekstra forsiktig slik at du ikke overskriver noen sideendringer.', + 'pages_draft_page_changed_since_creation' => 'Denne siden har blitt oppdatert etter at dette utkastet ble oppretta. Me trur det er lurt å forkaste dette utkastet, eller er ekstra forsiktig, slik at du ikkje overskriver andre sine sideendringar.', 'pages_draft_edit_active' => [ 'start_a' => ':count forfattere har begynt å endre denne siden.', 'start_b' => ':userName skriver på siden for øyeblikket', - 'time_a' => 'siden sist siden ble oppdatert', + 'time_a' => 'sidan sist sida vart oppdatert', 'time_b' => 'i løpet av de siste :minCount minuttene', 'message' => ':start :time. Prøv å ikke overskriv hverandres endringer!', ], 'pages_draft_discarded' => 'Utkastet er forkastet! Redigeringsprogrammet er oppdatert med gjeldende sideinnhold', - 'pages_draft_deleted' => 'Utkast slettet! Redigeringsprogrammet er oppdatert med gjeldende sideinnhold', + 'pages_draft_deleted' => 'Utkast sletta! Redigeringsprogrammet er oppdatert med gjeldande sideinnhald', 'pages_specific' => 'Bestemt side', 'pages_is_template' => 'Sidemal', @@ -341,9 +345,9 @@ return [ 'attachments_order_updated' => 'Vedleggssortering endret', 'attachments_updated_success' => 'Vedleggsdetaljer endret', 'attachments_deleted' => 'Vedlegg fjernet', - 'attachments_file_uploaded' => 'Vedlegg ble lastet opp', - 'attachments_file_updated' => 'Vedlegget ble oppdatert', - 'attachments_link_attached' => 'Lenken ble festet til siden', + 'attachments_file_uploaded' => 'Vedlegg vart lasta opp', + 'attachments_file_updated' => 'Vedlegget vart oppdatert', + 'attachments_link_attached' => 'Lenka vart festa til sida', 'templates' => 'Maler', 'templates_set_as_template' => 'Siden er en mal', 'templates_explain_set_as_template' => 'Du kan angi denne siden som en mal slik at innholdet kan brukes når du oppretter andre sider. Andre brukere vil kunne bruke denne malen hvis de har visningstillatelser for denne siden.', @@ -386,7 +390,7 @@ return [ 'copy_consider' => 'Vennligst vurder nedenfor når du kopierer innholdet.', 'copy_consider_permissions' => 'Egendefinerte tilgangsinnstillinger vil ikke bli kopiert.', 'copy_consider_owner' => 'Du vil bli eier av alt kopiert innhold.', - 'copy_consider_images' => 'Sidebildefiler vil ikke bli duplisert og de opprinnelige bildene beholder relasjonen til siden de opprinnelig ble lastet opp til.', + 'copy_consider_images' => 'Sidebildefiler vil ikkle bli duplisert og dei opprinnelege bileta beholder relasjonen til sida dei opprinnelig vart lasta opp til.', 'copy_consider_attachments' => 'Sidevedlegg vil ikke bli kopiert.', 'copy_consider_access' => 'Endring av sted, eier eller rettigheter kan føre til at innholdet er tilgjengelig for dem som tidligere har vært uten adgang.', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Referanser', 'references_none' => 'Det er ingen sporede referanser til dette elementet.', - 'references_to_desc' => 'Nedenfor vises alle de kjente sidene i systemet som lenker til denne oppføringen.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Overvåk', diff --git a/lang/nn/errors.php b/lang/nn/errors.php index 5cea95da0..e9087bac4 100644 --- a/lang/nn/errors.php +++ b/lang/nn/errors.php @@ -19,12 +19,11 @@ return [ 'ldap_extension_not_installed' => 'LDAP PHP modulen er ikke installert.', 'ldap_cannot_connect' => 'Klarer ikke koble til LDAP på denne adressen', 'saml_already_logged_in' => 'Allerede logget inn', - 'saml_user_not_registered' => 'Kontoen med navn :name er ikke registert, registrering er også deaktivert.', 'saml_no_email_address' => 'Denne kontoinformasjonen finnes ikke i det eksterne autentiseringssystemet.', 'saml_invalid_response_id' => 'Forespørselen fra det eksterne autentiseringssystemet gjenkjennes ikke av en prosess som startes av dette programmet. Å navigere tilbake etter pålogging kan forårsake dette problemet.', 'saml_fail_authed' => 'Innlogging gjennom :system feilet. Fikk ikke kontakt med autentiseringstjeneren.', 'oidc_already_logged_in' => 'Allerede logget inn', - 'oidc_user_not_registered' => 'Brukeren :name er ikke registrert og automatisk registrering er deaktivert', + 'oidc_user_not_registered' => 'Brukeren :name er ikkje registrert og automatisk registrering er deaktivert', 'oidc_no_email_address' => 'Finner ikke en e-postadresse, for denne brukeren, i dataene som leveres av det eksterne autentiseringssystemet', 'oidc_fail_authed' => 'Innlogging ved hjelp av :system feilet, systemet ga ikke vellykket godkjenning', 'social_no_action_defined' => 'Ingen handlinger er definert', @@ -40,17 +39,17 @@ return [ 'invite_token_expired' => 'Invitasjonslenken har utgått, du kan forsøke å be om nytt passord istede.', // System - 'path_not_writable' => 'Filstien :filePath aksepterer ikke filer, du må sjekke filstitilganger i systemet.', - 'cannot_get_image_from_url' => 'Kan ikke hente bilde fra :url', - 'cannot_create_thumbs' => 'Kan ikke opprette miniatyrbilder. GD PHP er ikke installert.', + 'path_not_writable' => 'Filstien :filePath aksepterer ikkje filer, du må sjekke filstitilganger i systemet.', + 'cannot_get_image_from_url' => 'Kan ikkje hente bilete frå :url', + 'cannot_create_thumbs' => 'Kan ikkje opprette miniatyrbilete. GD PHP er ikkje installert.', 'server_upload_limit' => 'Vedlegget er for stort, forsøk med et mindre vedlegg.', 'server_post_limit' => 'Serveren kan ikkje ta i mot denne mengda med data. Prøv igjen med mindre data eller ei mindre fil.', 'uploaded' => 'Tjenesten aksepterer ikke vedlegg som er så stor.', // Drawing & Images - 'image_upload_error' => 'Bildet kunne ikke lastes opp, forsøk igjen.', - 'image_upload_type_error' => 'Bildeformatet støttes ikke, forsøk med et annet format.', - 'image_upload_replace_type' => 'Bildeerstatning må være av samme type', + 'image_upload_error' => 'Biletet kunne ikkje lastast opp, prøv igjen', + 'image_upload_type_error' => 'Bileteformatet er ikkje støtta, prøv med eit anna format', + 'image_upload_replace_type' => 'Bileteerstatning må vere av same type', 'image_upload_memory_limit' => 'Klarte ikkje å ta i mot bilete og lage miniatyrbilete grunna grenser knytt til systemet.', 'image_thumbnail_memory_limit' => 'Klarte ikkje å lage miniatyrbilete grunna grenser knytt til systemet.', 'image_gallery_thumbnail_memory_limit' => 'Klarte ikkje å lage miniatyrbilete grunna grenser knytt til systemet.', @@ -96,9 +95,9 @@ return [ '404_page_not_found' => 'Siden finnes ikke', 'sorry_page_not_found' => 'Beklager, siden du leter etter ble ikke funnet.', 'sorry_page_not_found_permission_warning' => 'Hvis du forventet at denne siden skulle eksistere, har du kanskje ikke tillatelse til å se den.', - 'image_not_found' => 'Bildet ble ikke funnet', - 'image_not_found_subtitle' => 'Beklager, bildefilen du ser etter ble ikke funnet.', - 'image_not_found_details' => 'Om du forventet at dette bildet skal eksistere, er det mulig det er slettet.', + 'image_not_found' => 'Bilete vart ikkje funne', + 'image_not_found_subtitle' => 'Orsak, biletefila vart ikkje funne.', + 'image_not_found_details' => 'Det kan sjå ut til at biletet du leiter etter er sletta.', 'return_home' => 'Gå til hovedside', 'error_occurred' => 'En feil oppsto', 'app_down' => ':appName er nede for øyeblikket', diff --git a/lang/nn/notifications.php b/lang/nn/notifications.php index 78aad7181..98c686e44 100644 --- a/lang/nn/notifications.php +++ b/lang/nn/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'For å forhindre mange varslinger, vil du ikke få nye varslinger for endringer på denne siden fra samme forfatter.', 'detail_page_name' => 'Sidenavn:', + 'detail_page_path' => 'Sidenamn:', 'detail_commenter' => 'Kommentar fra:', 'detail_comment' => 'Kommentar:', 'detail_created_by' => 'Opprettet av:', diff --git a/lang/nn/settings.php b/lang/nn/settings.php index a6d848331..f269c35bc 100644 --- a/lang/nn/settings.php +++ b/lang/nn/settings.php @@ -14,7 +14,7 @@ return [ // App Settings 'app_customization' => 'Tilpassing', - 'app_features_security' => 'Funksjoner og sikkerhet', + 'app_features_security' => 'Funksjonar og tryggleik', 'app_name' => 'Applikasjonsnavn', 'app_name_desc' => 'Dette navnet vises i overskriften og i alle e-postmeldinger som sendes av systemet.', 'app_name_header' => 'Vis navn i topptekst', @@ -23,8 +23,8 @@ return [ 'app_public_access_desc_guest' => 'Tilgang for offentlige besøkende kan kontrolleres gjennom "Gjest" -brukeren.', 'app_public_access_toggle' => 'Tillat offentlig tilgang', 'app_public_viewing' => 'Tillat offentlig visning?', - 'app_secure_images' => 'Høyere sikkerhet på bildeopplastinger', - 'app_secure_images_toggle' => 'Enable høyere sikkerhet på bildeopplastinger', + 'app_secure_images' => 'Høyere tryggleik på bileteopplastingar', + 'app_secure_images_toggle' => 'Skru på høgare tryggleik på bileteopplastingar', 'app_secure_images_desc' => 'Av ytelsesgrunner er alle bilder offentlige. Dette alternativet legger til en tilfeldig streng som er vanskelig å gjette foran bildets nettadresser. Forsikre deg om at katalogindekser ikke er aktivert for å forhindre enkel tilgang.', 'app_default_editor' => 'Standard sideredigeringsprogram', 'app_default_editor_desc' => 'Velg hvilken tekstbehandler som skal brukes som standard når du redigerer nye sider. Dette kan overskrives på et sidenivå der tillatelser tillates.', @@ -76,8 +76,8 @@ return [ // Maintenance settings 'maint' => 'Vedlikehold', - 'maint_image_cleanup' => 'Bildeopprydding', - 'maint_image_cleanup_desc' => 'Skanner side og revisjonsinnhold for å sjekke hvilke bilder og tegninger som for øyeblikket er i bruk, og hvilke bilder som er overflødige. Forsikre deg om at du lager en full database og sikkerhetskopiering av bilder før du kjører denne.', + 'maint_image_cleanup' => 'Rydd opp bilete', + 'maint_image_cleanup_desc' => 'Skanner side og revisjonsinnhold for å sjekke kva bilete og teikninar som for er i bruk no, og kva bilete som er til overs. Sørg for å tryggleikskopiere heile databasen og alle bilete før du kjører denne.', 'maint_delete_images_only_in_revisions' => 'Slett også bilder som bare finnes i game siderevisjoner', 'maint_image_cleanup_run' => 'Kjør opprydding', 'maint_image_cleanup_warning' => ':count potensielt ubrukte bilder ble funnet. Er du sikker på at du vil slette disse bildene?', @@ -109,7 +109,7 @@ return [ 'recycle_bin_contents_empty' => 'Papirkurven er for øyeblikket tom', 'recycle_bin_empty' => 'Tøm papirkurven', 'recycle_bin_empty_confirm' => 'Dette vil slette alle elementene i papirkurven permanent. Dette inkluderer innhold i hvert element. Er du sikker på at du vil tømme papirkurven?', - 'recycle_bin_destroy_confirm' => 'Denne handlingen vil permanent slette dette elementet og alle dets underelementer fra systemet, som beskrevet nedenfor. Du vil ikke kunne gjenopprette dette innholdet med mindre du har en tidligere sikkerhetskopi av databasen. Er du sikker på at du vil fortsette?', + 'recycle_bin_destroy_confirm' => 'Denne handlinga vil permanent slette dette elementet og alle dets underelementer frå systemet, som skildra nedanfor. Du vil ikkje kunne gjenopprette dette innholdet, med mindre du har ein tidligere tryggleiksskopi av databasen. Er du sikker på at du vil fortsette?', 'recycle_bin_destroy_list' => 'Elementer som skal slettes', 'recycle_bin_restore_list' => 'Elementer som skal gjenopprettes', 'recycle_bin_restore_confirm' => 'Denne handlingen vil hente opp elementet fra papirkurven, inkludert underliggende innhold, til sin opprinnelige sted. Om den opprinnelige plassen har blitt slettet i mellomtiden og nå befinner seg i papirkurven, vil også dette bli hentet opp igjen.', @@ -225,7 +225,7 @@ return [ 'users_api_tokens_expires' => 'Utløper', 'users_api_tokens_docs' => 'API-dokumentasjon', 'users_mfa' => 'Flerfaktorautentisering', - 'users_mfa_desc' => 'Konfigurer flerfaktorautentisering som et ekstra lag med sikkerhet for din konto.', + 'users_mfa_desc' => 'Konfigurer flerfaktorautentisering som eit ekstra lag med tryggleik for din konto.', 'users_mfa_x_methods' => ':count metode konfigurert|:count metoder konfigurert', 'users_mfa_configure' => 'Konfigurer metoder', @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/pl/common.php b/lang/pl/common.php index 8006bd539..13939f0e0 100644 --- a/lang/pl/common.php +++ b/lang/pl/common.php @@ -52,7 +52,7 @@ return [ 'filter_clear' => 'Wyczyść Filtr', 'download' => 'Pobierz', 'open_in_tab' => 'Otwórz w karcie', - 'open' => 'Open', + 'open' => 'Otwórz', // Sort Options 'sort_options' => 'Opcje sortowania', diff --git a/lang/pl/components.php b/lang/pl/components.php index fcc5a32a8..0aa3f0741 100644 --- a/lang/pl/components.php +++ b/lang/pl/components.php @@ -34,8 +34,8 @@ return [ 'image_delete_success' => 'Obrazek usunięty pomyślnie', 'image_replace' => 'Zastąp obraz', 'image_replace_success' => 'Plik obrazu zaktualizowany pomyślnie', - 'image_rebuild_thumbs' => 'Regenerate Size Variations', - 'image_rebuild_thumbs_success' => 'Image size variations successfully rebuilt!', + 'image_rebuild_thumbs' => 'Zregeneruj warianty rozmiaru', + 'image_rebuild_thumbs_success' => 'Warianty rozmiaru obrazu przebudowane pomyślnie!', // Code Editor 'code_editor' => 'Edytuj kod', diff --git a/lang/pl/entities.php b/lang/pl/entities.php index ae2bbc20b..6d1bb4348 100644 --- a/lang/pl/entities.php +++ b/lang/pl/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Zaktualizowano :timeLength', 'meta_updated_name' => 'Zaktualizowano :timeLength przez :user', 'meta_owned_name' => 'Właściciel: :user', - 'meta_reference_page_count' => 'Odniesienie na :count stronie|Odniesienie na :count stronach', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Wybór obiektu', 'entity_select_lack_permission' => 'Nie masz wymaganych uprawnień do wybrania tej pozycji', 'images' => 'Obrazki', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Edytuj książkę :bookName', 'books_form_book_name' => 'Nazwa książki', 'books_save' => 'Zapisz książkę', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Uprawnienia książki', 'books_permissions_updated' => 'Zaktualizowano uprawnienia książki', 'books_empty_contents' => 'Brak stron lub rozdziałów w tej książce.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Usuń wersje roboczą', 'pages_delete_success' => 'Strona usunięta pomyślnie', 'pages_delete_draft_success' => 'Werjsa robocza usunięta pomyślnie', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Czy na pewno chcesz usunąć tę stronę?', 'pages_delete_draft_confirm' => 'Czy na pewno chcesz usunąć wersje roboczą strony?', 'pages_editing_named' => 'Edytowanie strony :pageName', @@ -295,7 +299,7 @@ return [ 'pages_is_template' => 'Szablon strony', // Editor Sidebar - 'toggle_sidebar' => 'Toggle Sidebar', + 'toggle_sidebar' => 'Przełącz pasek boczny', 'page_tags' => 'Tagi strony', 'chapter_tags' => 'Tagi rozdziału', 'book_tags' => 'Tagi książki', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Odniesienia', 'references_none' => 'Brak śledzonych odwołań do tego elementu.', - 'references_to_desc' => 'Poniżej znajdują się wszystkie znane strony w systemie, które odnoszą się do tego elementu.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Obserwuj', diff --git a/lang/pl/errors.php b/lang/pl/errors.php index 3e9bce6f7..d4ba103e8 100644 --- a/lang/pl/errors.php +++ b/lang/pl/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'Rozszerzenie LDAP PHP nie zostało zainstalowane', 'ldap_cannot_connect' => 'Nie można połączyć z serwerem LDAP, połączenie nie zostało ustanowione', 'saml_already_logged_in' => 'Już zalogowany', - 'saml_user_not_registered' => 'Użytkownik :name nie jest zarejestrowany i automatyczna rejestracja jest wyłączona', 'saml_no_email_address' => 'Nie można odnaleźć adresu email dla tego użytkownika w danych dostarczonych przez zewnętrzny system uwierzytelniania', 'saml_invalid_response_id' => 'Żądanie z zewnętrznego systemu uwierzytelniania nie zostało rozpoznane przez proces rozpoczęty przez tę aplikację. Cofnięcie po zalogowaniu mogło spowodować ten problem.', 'saml_fail_authed' => 'Logowanie przy użyciu :system nie powiodło się, system nie mógł pomyślnie ukończyć uwierzytelniania', @@ -44,16 +43,16 @@ return [ 'cannot_get_image_from_url' => 'Nie można pobrać obrazka z :url', 'cannot_create_thumbs' => 'Serwer nie może utworzyć miniaturek. Upewnij się że rozszerzenie GD PHP zostało zainstalowane.', 'server_upload_limit' => 'Serwer nie pozwala na przyjęcie pliku o tym rozmiarze. Spróbuj przesłać plik o mniejszym rozmiarze.', - 'server_post_limit' => 'The server cannot receive the provided amount of data. Try again with less data or a smaller file.', + 'server_post_limit' => 'Serwer nie może przyjąć tej ilości danych. Spróbuj ponownie z mniejszą ilością danych lub mniejszym plikiem.', 'uploaded' => 'Serwer nie pozwala na przyjęcie pliku o tym rozmiarze. Spróbuj przesłać plik o mniejszym rozmiarze.', // Drawing & Images 'image_upload_error' => 'Wystąpił błąd podczas przesyłania obrazka', 'image_upload_type_error' => 'Typ przesłanego obrazka jest nieprwidłowy.', 'image_upload_replace_type' => 'Zamienniki plików graficznych muszą być tego samego typu', - 'image_upload_memory_limit' => 'Failed to handle image upload and/or create thumbnails due to system resource limits.', - 'image_thumbnail_memory_limit' => 'Failed to create image size variations due to system resource limits.', - 'image_gallery_thumbnail_memory_limit' => 'Failed to create gallery thumbnails due to system resource limits.', + 'image_upload_memory_limit' => 'Nie udało się obsłużyć przesyłania zdjęć i/lub tworzenia miniatur ze względu na limity zasobów systemowych.', + 'image_thumbnail_memory_limit' => 'Nie udało się utworzyć wariantów rozmiaru obrazu ze względu na limity zasobów systemowych.', + 'image_gallery_thumbnail_memory_limit' => 'Nie udało się utworzyć miniatur galerii ze względu na limity zasobów systemowych.', 'drawing_data_not_found' => 'Nie można załadować danych rysunku. Plik rysunku może już nie istnieć lub nie masz uprawnień dostępu do niego.', // Attachments diff --git a/lang/pl/notifications.php b/lang/pl/notifications.php index d3f86d02d..661149994 100644 --- a/lang/pl/notifications.php +++ b/lang/pl/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'Aby zapobiec nadmiarowi powiadomień, przez jakiś czas nie będziesz otrzymywać powiadomień o dalszych edycjach tej strony przez tego samego edytora.', 'detail_page_name' => 'Nazwa strony:', + 'detail_page_path' => 'Ścieżka strony:', 'detail_commenter' => 'Skomentował:', 'detail_comment' => 'Komentarz:', 'detail_created_by' => 'Utworzono przez:', diff --git a/lang/pl/preferences.php b/lang/pl/preferences.php index 5b664fd50..6d67dc1b3 100644 --- a/lang/pl/preferences.php +++ b/lang/pl/preferences.php @@ -5,10 +5,10 @@ */ return [ - 'my_account' => 'My Account', + 'my_account' => 'Moje konto', 'shortcuts' => 'Skróty', - 'shortcuts_interface' => 'UI Shortcut Preferences', + 'shortcuts_interface' => 'Ustawienia skrótów interfejsu użytkownika', 'shortcuts_toggle_desc' => 'Tutaj możesz włączyć lub wyłączyć interfejs skrótów klawiszowych używanych do nawigacji i akcji.', 'shortcuts_customize_desc' => 'Możesz dostosować każdy z poniższych skrótów. Wystarczy nacisnąć wybraną kombinację klawiszy po wybraniu wprowadzania dla danego skrótu.', 'shortcuts_toggle_label' => 'Skróty klawiszowe włączone', @@ -29,23 +29,23 @@ return [ 'notifications_watched' => 'Obserwowane i ignorowane elementy', 'notifications_watched_desc' => ' Poniżej znajdują się elementy, które mają własne preferencje obserwowania. Aby zaktualizować swoje preferencje, zobacz dany element, a następnie znajdź opcje obserwowania na pasku bocznym.', - 'auth' => 'Access & Security', - 'auth_change_password' => 'Change Password', - 'auth_change_password_desc' => 'Change the password you use to log-in to the application. This must be at least 8 characters long.', - 'auth_change_password_success' => 'Password has been updated!', + 'auth' => 'Dostęp i bezpieczeństwo', + 'auth_change_password' => 'Zmień hasło', + 'auth_change_password_desc' => 'Zmień hasło logowania do aplikacji. Hasło musi mieć minimum 8 znaków.', + 'auth_change_password_success' => 'Hasło zostało zaktualizowane!', - 'profile' => 'Profile Details', - 'profile_desc' => 'Manage the details of your account which represents you to other users, in addition to details that are used for communication and system personalisation.', - 'profile_view_public' => 'View Public Profile', - 'profile_name_desc' => 'Configure your display name which will be visible to other users in the system through the activity you perform, and content you own.', - 'profile_email_desc' => 'This email will be used for notifications and, depending on active system authentication, system access.', - 'profile_email_no_permission' => 'Unfortunately you don\'t have permission to change your email address. If you want to change this, you\'d need to ask an administrator to change this for you.', - 'profile_avatar_desc' => 'Select an image which will be used to represent yourself to others in the system. Ideally this image should be square and about 256px in width and height.', - 'profile_admin_options' => 'Administrator Options', - 'profile_admin_options_desc' => 'Additional administrator-level options, like those to manage role assignments, can be found for your user account in the "Settings > Users" area of the application.', + 'profile' => 'Szczegóły profilu', + 'profile_desc' => 'Zarządzaj szczegółami swojego konta, które widzą inni użytkownicy, oprócz danych używanych do komunikacji i personalizacji systemu.', + 'profile_view_public' => 'Zobacz profil publiczny', + 'profile_name_desc' => 'Skonfiguruj wyświetlaną nazwę, która będzie widoczna dla innych użytkowników systemu poprzez wykonywane zadania i posiadaną treść.', + 'profile_email_desc' => 'Ten e-mail będzie używany do powiadomień, a w zależności od wybranego sposobu uwierzytelniania, również do dostępu do systemu.', + 'profile_email_no_permission' => 'Niestety nie masz uprawnień do zmiany adresu e-mail. Jeśli chcesz to zmienić, musisz poprosić administratora, aby zrobił to za ciebie.', + 'profile_avatar_desc' => 'Wybierz obraz, który będzie cię reprezentował wśród innych w systemie. Obraz powinien być kwadratem o długości boku około 256 pikseli.', + 'profile_admin_options' => 'Opcje administratora', + 'profile_admin_options_desc' => 'Dodatkowe opcje na poziomie administratora dla twojego konta, takie jak zarządzanie przydzielaniem ról można znaleźć w sekcji "Ustawienia > Użytkownicy".', - 'delete_account' => 'Delete Account', - 'delete_my_account' => 'Delete My Account', - 'delete_my_account_desc' => 'This will fully delete your user account from the system. You will not be able to recover this account or revert this action. Content you\'ve created, such as created pages and uploaded images, will remain.', - 'delete_my_account_warning' => 'Are you sure you want to delete your account?', + 'delete_account' => 'Usuń konto', + 'delete_my_account' => 'Usuń moje konto', + 'delete_my_account_desc' => 'Spowoduje to całkowite usunięcie twojego konta z systemu. Nie będziesz miał możliwości odzyskania konta lub cofnięcia tej czynności. Stworzona przez Ciebie zawartość, taka jak utworzone strony i przesłane obrazy, pozostanie niezmieniona.', + 'delete_my_account_warning' => 'Jesteś pewny, że chcesz usunąć swoje konto?', ]; diff --git a/lang/pl/settings.php b/lang/pl/settings.php index 80e320d56..4d0bc9f7a 100644 --- a/lang/pl/settings.php +++ b/lang/pl/settings.php @@ -193,8 +193,8 @@ return [ 'users_send_invite_text' => 'Możesz wybrać wysłanie do tego użytkownika wiadomości e-mail z zaproszeniem, która pozwala mu ustawić własne hasło, w przeciwnym razie możesz ustawić je samemu.', 'users_send_invite_option' => 'Wyślij e-mail z zaproszeniem', 'users_external_auth_id' => 'Zewnętrzne identyfikatory autentykacji', - 'users_external_auth_id_desc' => 'When an external authentication system is in use (such as SAML2, OIDC or LDAP) this is the ID which links this BookStack user to the authentication system account. You can ignore this field if using the default email-based authentication.', - 'users_password_warning' => 'Only fill the below if you would like to change the password for this user.', + 'users_external_auth_id_desc' => 'Gdy używany jest zewnętrzny system uwierzytelniania (np. SAML2, OIDC lub LDAP), to jest ID, które łączy tego użytkownika BookStack z kontem w systemie uwierzytelniania. Możesz zignorować to pole, jeśli używasz domyślnego uwierzytelniania e-mailem.', + 'users_password_warning' => 'Wypełnij poniższe tylko, jeśli chcesz zmienić hasło dla tego użytkownika.', 'users_system_public' => 'Ten użytkownik reprezentuje każdego gościa odwiedzającego tę aplikację. Nie można się na niego zalogować, lecz jest przyznawany automatycznie.', 'users_delete' => 'Usuń użytkownika', 'users_delete_named' => 'Usuń :userName', @@ -210,16 +210,16 @@ return [ 'users_preferred_language' => 'Preferowany język', 'users_preferred_language_desc' => 'Opcja ta zmieni język używany w interfejsie użytkownika aplikacji. Nie wpłynie to na zawartość stworzoną przez użytkownika.', 'users_social_accounts' => 'Konta społecznościowe', - 'users_social_accounts_desc' => 'View the status of the connected social accounts for this user. Social accounts can be used in addition to the primary authentication system for system access.', + 'users_social_accounts_desc' => 'Zobacz status połączonych kont społecznościowych dla tego użytkownika. Konta społecznościowe mogą być używane jako uzupełnienie podstawowego systemu uwierzytelniania w celu uzyskania dostępu do systemu.', 'users_social_accounts_info' => 'Tutaj możesz połączyć kilka kont społecznościowych w celu łatwiejszego i szybszego logowania. Odłączenie konta tutaj nie autoryzowało dostępu. Odwołaj dostęp z ustawień profilu na podłączonym koncie społecznościowym.', 'users_social_connect' => 'Podłącz konto', 'users_social_disconnect' => 'Odłącz konto', - 'users_social_status_connected' => 'Connected', - 'users_social_status_disconnected' => 'Disconnected', + 'users_social_status_connected' => 'Połączono', + 'users_social_status_disconnected' => 'Rozłączono', 'users_social_connected' => ':socialAccount zostało dodane do Twojego profilu.', 'users_social_disconnected' => ':socialAccount zostało odłączone od Twojego profilu.', 'users_api_tokens' => 'Tokeny API', - 'users_api_tokens_desc' => 'Create and manage the access tokens used to authenticate with the BookStack REST API. Permissions for the API are managed via the user that the token belongs to.', + 'users_api_tokens_desc' => 'Twórz i zarządzaj tokenami dostępu używanymi do uwierzytelniania z BookStack REST API. Uprawnienia dla API są zarządzane za pośrednictwem użytkownika, do którego należy token.', 'users_api_tokens_none' => 'Nie utworzono tokenów API dla tego użytkownika', 'users_api_tokens_create' => 'Utwórz token', 'users_api_tokens_expires' => 'Wygasa', @@ -296,6 +296,7 @@ return [ 'et' => 'Estoński', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/pt/activities.php b/lang/pt/activities.php index 18645acf9..d12d640cb 100644 --- a/lang/pt/activities.php +++ b/lang/pt/activities.php @@ -59,7 +59,7 @@ return [ 'favourite_remove_notification' => '":name" foi removido dos seus favoritos', // Watching - 'watch_update_level_notification' => 'Watch preferences successfully updated', + 'watch_update_level_notification' => 'Ver preferências atualizadas com sucesso', // Auth 'auth_login' => 'sessão iniciada', @@ -115,9 +115,9 @@ return [ // Comments 'commented_on' => 'comentado a', - 'comment_create' => 'added comment', - 'comment_update' => 'updated comment', - 'comment_delete' => 'deleted comment', + 'comment_create' => 'comentário adicionado', + 'comment_update' => 'comentário atualizado', + 'comment_delete' => 'comentário eliminado', // Other 'permissions_update' => 'permissões atualizadas', diff --git a/lang/pt/common.php b/lang/pt/common.php index d2d373911..586ac1911 100644 --- a/lang/pt/common.php +++ b/lang/pt/common.php @@ -42,7 +42,7 @@ return [ 'remove' => 'Remover', 'add' => 'Adicionar', 'configure' => 'Configurar', - 'manage' => 'Manage', + 'manage' => 'Gerir', 'fullscreen' => 'Ecrã completo', 'favourite' => 'Favorito', 'unfavourite' => 'Retirar Favorito', @@ -52,7 +52,7 @@ return [ 'filter_clear' => 'Limpar Filtro', 'download' => 'Transferir', 'open_in_tab' => 'Abrir em novo separador', - 'open' => 'Open', + 'open' => 'Abrir', // Sort Options 'sort_options' => 'Opções de Ordenação', diff --git a/lang/pt/components.php b/lang/pt/components.php index 1bf504dd1..67e8c2f01 100644 --- a/lang/pt/components.php +++ b/lang/pt/components.php @@ -34,8 +34,8 @@ return [ 'image_delete_success' => 'Imagem eliminada com sucesso', 'image_replace' => 'Substituir Imagem', 'image_replace_success' => 'Imagem carregada com sucesso', - 'image_rebuild_thumbs' => 'Regenerate Size Variations', - 'image_rebuild_thumbs_success' => 'Image size variations successfully rebuilt!', + 'image_rebuild_thumbs' => 'Recriar Variação de Tamanho', + 'image_rebuild_thumbs_success' => 'Variações de tamanho da imagem reconstruídas com sucesso!', // Code Editor 'code_editor' => 'Editar Código', diff --git a/lang/pt/entities.php b/lang/pt/entities.php index 3da42f432..b2171ba64 100644 --- a/lang/pt/entities.php +++ b/lang/pt/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Atualizado :timeLength', 'meta_updated_name' => 'Atualizado :timeLength por :user', 'meta_owned_name' => 'Propriedade de :user', - 'meta_reference_page_count' => 'Referenciado em :count página|Referenciado em :count páginas', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Seleção de Entidade', 'entity_select_lack_permission' => 'Não tem as permissões necessárias para selecionar este item', 'images' => 'Imagens', @@ -106,7 +106,7 @@ return [ 'shelves_permissions_updated' => 'Permissões da Estante Atualizada', 'shelves_permissions_active' => 'Permissões da Estante Ativas', 'shelves_permissions_cascade_warning' => 'As permissões nas estantes não são passadas automaticamente em efeito dominó para os livros contidos. Isto acontece porque um livro pode existir em várias estantes. As permissões podem, no entanto, ser copiadas para livros filhos usando a opção abaixo.', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', + 'shelves_permissions_create' => 'As permissões de criação de prateleira são usadas apenas para copiar livros filhos usando a ação abaixo. Eles não controlam a capacidade de criar livros.', 'shelves_copy_permissions_to_books' => 'Copiar Permissões para Livros', 'shelves_copy_permissions' => 'Copiar Permissões', 'shelves_copy_permissions_explain' => 'Isto aplicará as configurações de permissões atuais desta estante a todos os livros nela contidos. Antes de ativar, assegure-se de que quaisquer alterações nas permissões desta estante foram guardadas.', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Editar Livro :bookName', 'books_form_book_name' => 'Nome do Livro', 'books_save' => 'Guardar Livro', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Permissões do Livro', 'books_permissions_updated' => 'Permissões do Livro Atualizadas', 'books_empty_contents' => 'Nenhuma página ou capítulo foram criados para este livro.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Eliminar Rascunho de Página', 'pages_delete_success' => 'Página eliminada', 'pages_delete_draft_success' => 'Rascunho de página eliminado', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Tem certeza que deseja eliminar a página?', 'pages_delete_draft_confirm' => 'Tem certeza que deseja eliminar o rascunho de página?', 'pages_editing_named' => 'A Editar a Página :pageName', @@ -239,8 +243,8 @@ return [ 'pages_md_insert_drawing' => 'Inserir Desenho', 'pages_md_show_preview' => 'Mostrar pré-visualização', 'pages_md_sync_scroll' => 'Sincronizar pré-visualização', - 'pages_drawing_unsaved' => 'Unsaved Drawing Found', - 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', + 'pages_drawing_unsaved' => 'Encontrado um rascunho não guardado', + 'pages_drawing_unsaved_confirm' => 'Dados de um rascunho não guardado foi encontrado de um tentativa anteriormente falhada. Deseja restaurar e continuar a edição desse rascunho?', 'pages_not_in_chapter' => 'A página não está dentro de um capítulo', 'pages_move' => 'Mover Página', 'pages_copy' => 'Copiar Página', @@ -295,7 +299,7 @@ return [ 'pages_is_template' => 'Modelo de Página', // Editor Sidebar - 'toggle_sidebar' => 'Toggle Sidebar', + 'toggle_sidebar' => 'Alternar barra lateral', 'page_tags' => 'Etiquetas de Página', 'chapter_tags' => 'Etiquetas do Capítulo', 'book_tags' => 'Etiquetas do Livro', @@ -405,29 +409,29 @@ return [ // References 'references' => 'Referências', 'references_none' => 'Não há referências registadas para este item.', - 'references_to_desc' => 'Abaixo estão todas as páginas conhecidas do sistema que vinculam este item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options - 'watch' => 'Watch', - 'watch_title_default' => 'Default Preferences', - 'watch_desc_default' => 'Revert watching to just your default notification preferences.', - 'watch_title_ignore' => 'Ignore', - 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', - 'watch_title_new' => 'New Pages', - 'watch_desc_new' => 'Notify when any new page is created within this item.', - 'watch_title_updates' => 'All Page Updates', - 'watch_desc_updates' => 'Notify upon all new pages and page changes.', - 'watch_desc_updates_page' => 'Notify upon all page changes.', - 'watch_title_comments' => 'All Page Updates & Comments', - 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', - 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', - 'watch_change_default' => 'Change default notification preferences', - 'watch_detail_ignore' => 'Ignoring notifications', + 'watch' => 'Ver', + 'watch_title_default' => 'Preferências Predefinidas', + 'watch_desc_default' => 'Reverter visualização para as preferências de notificação padrão.', + 'watch_title_ignore' => 'Ignorar', + 'watch_desc_ignore' => 'Ignorar todas as notificações, incluindo as de preferências de nível de usuário.', + 'watch_title_new' => 'Novas Páginas', + 'watch_desc_new' => 'Notificar quando qualquer nova página for criada dentro deste item.', + 'watch_title_updates' => 'Todas as atualizações da página', + 'watch_desc_updates' => 'Notificar sobre todas as novas páginas e alterações na página.', + 'watch_desc_updates_page' => 'Notificar sobre todas as alterações da página.', + 'watch_title_comments' => 'Todas as atualizações e comentários da página', + 'watch_desc_comments' => 'Notificar sobre todas as novas páginas, alterações de página e novos comentários.', + 'watch_desc_comments_page' => 'Notificar sobre alterações na página e novos comentários.', + 'watch_change_default' => 'Alterar preferências padrão de notificação', + 'watch_detail_ignore' => 'Ignorar notificações', 'watch_detail_new' => 'Watching for new pages', 'watch_detail_updates' => 'Watching new pages and updates', 'watch_detail_comments' => 'Watching new pages, updates & comments', 'watch_detail_parent_book' => 'Watching via parent book', - 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', + 'watch_detail_parent_book_ignore' => 'A ignorar através do livro pai', 'watch_detail_parent_chapter' => 'Watching via parent chapter', 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', ]; diff --git a/lang/pt/errors.php b/lang/pt/errors.php index 381337561..34613872e 100644 --- a/lang/pt/errors.php +++ b/lang/pt/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'A extensão LDAP PHP não está instalada', 'ldap_cannot_connect' => 'Não foi possível conectar ao servidor LDAP. Conexão inicial falhou', 'saml_already_logged_in' => 'Sessão já iniciada', - 'saml_user_not_registered' => 'O utilizador :name não está registado e o registo automático está desativado', 'saml_no_email_address' => 'Não foi possível encontrar um endereço de e-mail para este utilizador nos dados providenciados pelo sistema de autenticação externa', 'saml_invalid_response_id' => 'A requisição do sistema de autenticação externa não foi reconhecia por um processo iniciado por esta aplicação. Navegar para o caminho anterior após o inicio de sessão pode provocar este problema.', 'saml_fail_authed' => 'Inicio de sessão com :system falhou. O sistema não forneceu uma autorização bem sucedida', @@ -44,7 +43,7 @@ return [ 'cannot_get_image_from_url' => 'Não foi possível obter a imagem a partir de :url', 'cannot_create_thumbs' => 'O servidor não pôde criar as miniaturas de imagem. Por favor, verifique se a extensão GD PHP está instalada.', 'server_upload_limit' => 'O servidor não permite o carregamento de arquivos com esse tamanho. Por favor, tente fazer o carregamento de arquivos mais pequenos.', - 'server_post_limit' => 'The server cannot receive the provided amount of data. Try again with less data or a smaller file.', + 'server_post_limit' => 'O servidor não pode receber a quantidade de dados fornecida. Tente novamente com menos dados ou um arquivo menor.', 'uploaded' => 'O servidor não permite o carregamento de arquivos com esse tamanho. Por favor, tente fazer o carregamento de arquivos mais pequenos.', // Drawing & Images diff --git a/lang/pt/notifications.php b/lang/pt/notifications.php index 5539ae9a9..1243c6680 100644 --- a/lang/pt/notifications.php +++ b/lang/pt/notifications.php @@ -4,23 +4,24 @@ */ return [ - 'new_comment_subject' => 'New comment on page: :pageName', - 'new_comment_intro' => 'A user has commented on a page in :appName:', - 'new_page_subject' => 'New page: :pageName', - 'new_page_intro' => 'A new page has been created in :appName:', - 'updated_page_subject' => 'Updated page: :pageName', - 'updated_page_intro' => 'A page has been updated in :appName:', - 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'new_comment_subject' => 'Novo comentário na página: :pageName', + 'new_comment_intro' => 'Um utilizador comentou uma página em :appName:', + 'new_page_subject' => 'Nova página: :pageName', + 'new_page_intro' => 'Uma nova página foi criada em :appName:', + 'updated_page_subject' => 'Página atualizada: :pageName', + 'updated_page_intro' => 'Uma página foi atualizada em :appName:', + 'updated_page_debounce' => 'Para evitar um grande volume de notificações, durante algum tempo não serão enviadas notificações de edições futuras para esta página através do mesmo editor.', - 'detail_page_name' => 'Page Name:', - 'detail_commenter' => 'Commenter:', - 'detail_comment' => 'Comment:', - 'detail_created_by' => 'Created By:', - 'detail_updated_by' => 'Updated By:', + 'detail_page_name' => 'Nome da Página:', + 'detail_page_path' => 'Page Path:', + 'detail_commenter' => 'Comentador:', + 'detail_comment' => 'Comentário:', + 'detail_created_by' => 'Criado Por:', + 'detail_updated_by' => 'Atualizado Por:', - 'action_view_comment' => 'View Comment', - 'action_view_page' => 'View Page', + 'action_view_comment' => 'Ver Comentário', + 'action_view_page' => 'Ver Página', - 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', - 'footer_reason_link' => 'your notification preferences', + 'footer_reason' => 'Esta notificação foi enviada a você porque :link cobre este tipo de atividade para este item.', + 'footer_reason_link' => 'suas preferências de notificação', ]; diff --git a/lang/pt/preferences.php b/lang/pt/preferences.php index 8cf0697b4..ae7e2cc43 100644 --- a/lang/pt/preferences.php +++ b/lang/pt/preferences.php @@ -5,10 +5,10 @@ */ return [ - 'my_account' => 'My Account', + 'my_account' => 'A Minha Conta', 'shortcuts' => 'Atalhos', - 'shortcuts_interface' => 'UI Shortcut Preferences', + 'shortcuts_interface' => 'Preferências de Atalho UI', 'shortcuts_toggle_desc' => 'Aqui pode ativar ou desativar os atalhos de teclado do sistema, usados para navegação e ações.', 'shortcuts_customize_desc' => 'Pode personalizar cada um dos atalhos abaixo. Pressione a combinação de tecla desejada após selecionar a entrada para um atalho.', 'shortcuts_toggle_label' => 'Atalhos de teclado ativados', @@ -19,7 +19,7 @@ return [ 'shortcuts_update_success' => 'As suas preferências de atalhos foram guardadas!', 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', - 'notifications' => 'Notification Preferences', + 'notifications' => 'Preferências das Notificações', 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', @@ -29,14 +29,14 @@ return [ 'notifications_watched' => 'Watched & Ignored Items', 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', - 'auth' => 'Access & Security', - 'auth_change_password' => 'Change Password', - 'auth_change_password_desc' => 'Change the password you use to log-in to the application. This must be at least 8 characters long.', - 'auth_change_password_success' => 'Password has been updated!', + 'auth' => 'Acesso e Segurança', + 'auth_change_password' => 'Alterar Palavra-passe', + 'auth_change_password_desc' => 'Altere a palavra-passe que você usa para entrar no aplicativo. Ela deve ter pelo menos 8 caracteres.', + 'auth_change_password_success' => 'A palavra-passe foi atualizada!', - 'profile' => 'Profile Details', + 'profile' => 'Detalhes Do Perfil', 'profile_desc' => 'Manage the details of your account which represents you to other users, in addition to details that are used for communication and system personalisation.', - 'profile_view_public' => 'View Public Profile', + 'profile_view_public' => 'Visualizar Perfil Público', 'profile_name_desc' => 'Configure your display name which will be visible to other users in the system through the activity you perform, and content you own.', 'profile_email_desc' => 'This email will be used for notifications and, depending on active system authentication, system access.', 'profile_email_no_permission' => 'Unfortunately you don\'t have permission to change your email address. If you want to change this, you\'d need to ask an administrator to change this for you.', @@ -44,8 +44,8 @@ return [ 'profile_admin_options' => 'Administrator Options', 'profile_admin_options_desc' => 'Additional administrator-level options, like those to manage role assignments, can be found for your user account in the "Settings > Users" area of the application.', - 'delete_account' => 'Delete Account', - 'delete_my_account' => 'Delete My Account', - 'delete_my_account_desc' => 'This will fully delete your user account from the system. You will not be able to recover this account or revert this action. Content you\'ve created, such as created pages and uploaded images, will remain.', - 'delete_my_account_warning' => 'Are you sure you want to delete your account?', + 'delete_account' => 'Excluir Conta', + 'delete_my_account' => 'Excluir a Minha Conta', + 'delete_my_account_desc' => 'Isto excluirá completamente sua conta de utilizador do sistema. Você não poderá recuperar esta conta ou reverter esta ação. O conteúdo que você criou, como páginas criadas e imagens carregadas, permanecerá.', + 'delete_my_account_warning' => 'Tem certeza que deseja excluir sua conta?', ]; diff --git a/lang/pt/settings.php b/lang/pt/settings.php index 45bea9d05..94da707f3 100644 --- a/lang/pt/settings.php +++ b/lang/pt/settings.php @@ -214,8 +214,8 @@ return [ 'users_social_accounts_info' => 'Aqui pode ligar outras contas para acesso mais rápido. Desligar uma conta não retira a possibilidade de acesso usando-a. Para revogar o acesso ao perfil através da conta social, você deverá fazê-lo na sua conta social.', 'users_social_connect' => 'Contas Associadas', 'users_social_disconnect' => 'Dissociar Conta', - 'users_social_status_connected' => 'Connected', - 'users_social_status_disconnected' => 'Disconnected', + 'users_social_status_connected' => 'Conectado', + 'users_social_status_disconnected' => 'Desconectado', 'users_social_connected' => 'A conta:socialAccount foi associada com sucesso ao seu perfil.', 'users_social_disconnected' => 'A conta:socialAccount foi dissociada com sucesso de seu perfil.', 'users_api_tokens' => 'Tokens de API', @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/pt_BR/entities.php b/lang/pt_BR/entities.php index e012042c8..6e32f2801 100644 --- a/lang/pt_BR/entities.php +++ b/lang/pt_BR/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Atualizado :timeLength', 'meta_updated_name' => 'Atualizado :timeLength por :user', 'meta_owned_name' => 'De :user', - 'meta_reference_page_count' => 'Referenciado em :count página|Referenciado em :count páginas', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Seleção de Entidade', 'entity_select_lack_permission' => 'Você não tem as permissões necessárias para selecionar este item', 'images' => 'Imagens', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Editar Livro :bookName', 'books_form_book_name' => 'Nome do Livro', 'books_save' => 'Salvar Livro', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Permissões do Livro', 'books_permissions_updated' => 'Permissões do Livro Atualizadas', 'books_empty_contents' => 'Nenhuma página ou capítulo foram criados para este livro.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Excluir Rascunho de Página', 'pages_delete_success' => 'Página excluída', 'pages_delete_draft_success' => 'Rascunho de página excluído', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Tem certeza que deseja excluir a página?', 'pages_delete_draft_confirm' => 'Tem certeza que deseja excluir o rascunho de página?', 'pages_editing_named' => 'Editando a Página :pageName', @@ -295,7 +299,7 @@ return [ 'pages_is_template' => 'Modelo de Página', // Editor Sidebar - 'toggle_sidebar' => 'Toggle Sidebar', + 'toggle_sidebar' => '', 'page_tags' => 'Tags de Página', 'chapter_tags' => 'Tags de Capítulo', 'book_tags' => 'Tags de Livro', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Referências', 'references_none' => 'Não há referências rastreadas para este item.', - 'references_to_desc' => 'Abaixo estão todas as páginas conhecidas no sistema que vinculam a este item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Acompanhar', diff --git a/lang/pt_BR/errors.php b/lang/pt_BR/errors.php index c7d82e18d..93ed24a59 100644 --- a/lang/pt_BR/errors.php +++ b/lang/pt_BR/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'A extensão LDAP PHP não está instalada', 'ldap_cannot_connect' => 'Não foi possível conectar ao servidor LDAP. Conexão inicial falhou', 'saml_already_logged_in' => 'Login já efetuado', - 'saml_user_not_registered' => 'O usuário :name não está cadastrado e o cadastro automático está desativado', 'saml_no_email_address' => 'Não foi possível encontrar um endereço de e-mail para este usuário nos dados providos pelo sistema de autenticação externa', 'saml_invalid_response_id' => 'A requisição do sistema de autenticação externa não foi reconhecia por um processo iniciado por esta aplicação. Após o login, navegar para o caminho anterior pode causar um problema.', 'saml_fail_authed' => 'Login utilizando :system falhou. Sistema não forneceu autorização bem sucedida', diff --git a/lang/pt_BR/notifications.php b/lang/pt_BR/notifications.php index 2c315d4f1..e8fc217ad 100644 --- a/lang/pt_BR/notifications.php +++ b/lang/pt_BR/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'Para prevenir notificações em massa, por enquanto notificações não serão enviadas para você para próximas edições nessa página pelo mesmo editor.', 'detail_page_name' => 'Nome da Página:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Comentador:', 'detail_comment' => 'Comentário:', 'detail_created_by' => 'Criado por: ', diff --git a/lang/pt_BR/settings.php b/lang/pt_BR/settings.php index f9e1bfdb3..3502a0e92 100644 --- a/lang/pt_BR/settings.php +++ b/lang/pt_BR/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/ro/entities.php b/lang/ro/entities.php index bcaa41695..c203df437 100644 --- a/lang/ro/entities.php +++ b/lang/ro/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Actualizat :timeLength', 'meta_updated_name' => 'Actualizat :timeLength de :user', 'meta_owned_name' => 'Deținut de :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Selectare entitate', 'entity_select_lack_permission' => 'Nu ai drepturile necesare pentru a selecta acest element', 'images' => 'Imagini', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Editează cartea :bookName', 'books_form_book_name' => 'Nume carte', 'books_save' => 'Salvează cartea', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Permisiuni carte', 'books_permissions_updated' => 'Permisiuni carte actualizate', 'books_empty_contents' => 'Nu au fost create pagini sau capitole pentru această carte.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Șterge ciorna', 'pages_delete_success' => 'Pagină ștearsă', 'pages_delete_draft_success' => 'Pagină ciornă ștearsă', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Ești sigur că dorești să ștergi acestă pagină?', 'pages_delete_draft_confirm' => 'Ești sigur că vrei să ștergi această pagină schiță?', 'pages_editing_named' => 'Editare pagină :pageNume', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Referințe', 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/ro/errors.php b/lang/ro/errors.php index 1ed422f71..b1ce69cfd 100644 --- a/lang/ro/errors.php +++ b/lang/ro/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'Extensia LDAP PHP nu este instalată', 'ldap_cannot_connect' => 'Nu se poate conecta la serverul ldap, conexiunea inițială a eșuat', 'saml_already_logged_in' => 'Deja conectat', - 'saml_user_not_registered' => 'Utilizatorul :name nu este înregistrat și înregistrarea automată este dezactivată', 'saml_no_email_address' => 'Nu s-a putut găsi o adresă de e-mail, pentru acest utilizator, în datele furnizate de sistemul extern de autentificare', 'saml_invalid_response_id' => 'Solicitarea de la sistemul extern de autentificare nu este recunoscută de un proces inițiat de această aplicație. Navigarea înapoi după o autentificare ar putea cauza această problemă.', 'saml_fail_authed' => 'Autentificarea folosind :system a eșuat, sistemul nu a furnizat autorizare cu succes', diff --git a/lang/ro/notifications.php b/lang/ro/notifications.php index 26a901575..676eeb814 100644 --- a/lang/ro/notifications.php +++ b/lang/ro/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'Pentru a preveni notificări în masă, pentru un timp nu veți primi notificări suplimentare la această pagină de către același editor.', 'detail_page_name' => 'Nume pagină:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Cine a comentat:', 'detail_comment' => 'Comentariu:', 'detail_created_by' => 'Creat de:', diff --git a/lang/ro/preferences.php b/lang/ro/preferences.php index f4018b171..893b44eaf 100644 --- a/lang/ro/preferences.php +++ b/lang/ro/preferences.php @@ -5,7 +5,7 @@ */ return [ - 'my_account' => 'My Account', + 'my_account' => 'Contul meu', 'shortcuts' => 'Scurtături', 'shortcuts_interface' => 'UI Shortcut Preferences', @@ -29,14 +29,14 @@ return [ 'notifications_watched' => 'Articole urmărite și ignorate', 'notifications_watched_desc' => ' Mai jos sunt elementele care au fost aplicate preferințe personalizate. Pentru a actualiza preferințele pentru acestea, vizualizați elementul și apoi găsiți opțiunile de ceas în bara laterală.', - 'auth' => 'Access & Security', - 'auth_change_password' => 'Change Password', + 'auth' => 'Acces & Securitate', + 'auth_change_password' => 'Schimbă Parola', 'auth_change_password_desc' => 'Change the password you use to log-in to the application. This must be at least 8 characters long.', 'auth_change_password_success' => 'Password has been updated!', - 'profile' => 'Profile Details', + 'profile' => 'Detalii Profil', 'profile_desc' => 'Manage the details of your account which represents you to other users, in addition to details that are used for communication and system personalisation.', - 'profile_view_public' => 'View Public Profile', + 'profile_view_public' => 'Vezi profilul Public', 'profile_name_desc' => 'Configure your display name which will be visible to other users in the system through the activity you perform, and content you own.', 'profile_email_desc' => 'This email will be used for notifications and, depending on active system authentication, system access.', 'profile_email_no_permission' => 'Unfortunately you don\'t have permission to change your email address. If you want to change this, you\'d need to ask an administrator to change this for you.', diff --git a/lang/ro/settings.php b/lang/ro/settings.php index 91b1c8064..062d402bc 100644 --- a/lang/ro/settings.php +++ b/lang/ro/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/ru/entities.php b/lang/ru/entities.php index 066dc48f9..a08912445 100644 --- a/lang/ru/entities.php +++ b/lang/ru/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Обновлено :timeLength', 'meta_updated_name' => ':user обновил :timeLength', 'meta_owned_name' => 'Владелец :user', - 'meta_reference_page_count' => 'Ссылается на :count страницу|Ссылается на :count страниц', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Выбор объекта', 'entity_select_lack_permission' => 'У вас нет разрешения на выбор этого элемента', 'images' => 'Изображения', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Редактировать книгу :bookName', 'books_form_book_name' => 'Название книги', 'books_save' => 'Сохранить книгу', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Разрешения на книгу', 'books_permissions_updated' => 'Разрешения на книгу обновлены', 'books_empty_contents' => 'Для этой книги нет страниц или разделов.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Удалить черновик', 'pages_delete_success' => 'Страница удалена', 'pages_delete_draft_success' => 'Черновик удален', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Вы действительно хотите удалить эту страницу?', 'pages_delete_draft_confirm' => 'Вы действительно хотите удалить этот черновик?', 'pages_editing_named' => 'Редактирование страницы :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Ссылки', 'references_none' => 'Нет отслеживаемых ссылок на этот элемент.', - 'references_to_desc' => 'Ниже показаны все известные страницы в системе, которые ссылаются на этот элемент.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/ru/errors.php b/lang/ru/errors.php index ca9eed368..2b18494e7 100644 --- a/lang/ru/errors.php +++ b/lang/ru/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'LDAP расширение для PHP не установлено', 'ldap_cannot_connect' => 'Не удается подключиться к серверу LDAP, не удалось выполнить начальное соединение', 'saml_already_logged_in' => 'Уже вошли в систему', - 'saml_user_not_registered' => 'Пользователь :name не зарегистрирован. Автоматическая регистрация отключена', 'saml_no_email_address' => 'Не удалось найти email для этого пользователя в данных, предоставленных внешней системой аутентификации', 'saml_invalid_response_id' => 'Запрос от внешней системы аутентификации не распознается процессом, запущенным этим приложением. Переход назад после входа в систему может вызвать эту проблему.', 'saml_fail_authed' => 'Вход с помощью :system не удался, система не предоставила успешную авторизацию', diff --git a/lang/ru/notifications.php b/lang/ru/notifications.php index 888fb8b0e..f37f11986 100644 --- a/lang/ru/notifications.php +++ b/lang/ru/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Имя страницы:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Комментатор:', 'detail_comment' => 'Комментарий:', 'detail_created_by' => 'Создано:', diff --git a/lang/ru/settings.php b/lang/ru/settings.php index f49fd092b..ef11be4bd 100644 --- a/lang/ru/settings.php +++ b/lang/ru/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/sk/entities.php b/lang/sk/entities.php index 315033f4d..0d74c691a 100644 --- a/lang/sk/entities.php +++ b/lang/sk/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Aktualizované :timeLength', 'meta_updated_name' => 'Aktualizované :timeLength používateľom :user', 'meta_owned_name' => 'Vlastník :user', - 'meta_reference_page_count' => 'Referencia na :count page|Referencia na :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Entita vybraná', 'entity_select_lack_permission' => 'Na výber tejto položky nemáte potrebné povolenia', 'images' => 'Obrázky', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Upraviť knihu :bookName', 'books_form_book_name' => 'Názov knihy', 'books_save' => 'Uložiť knihu', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Oprávnenia knihy', 'books_permissions_updated' => 'Oprávnenia knihy aktualizované', 'books_empty_contents' => 'Pre túto knihu neboli vytvorené žiadne stránky alebo kapitoly.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Zmazať koncept', 'pages_delete_success' => 'Stránka zmazaná', 'pages_delete_draft_success' => 'Koncept stránky zmazaný', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Ste si istý, že chcete zmazať túto stránku?', 'pages_delete_draft_confirm' => 'Ste si istý, že chcete zmazať tento koncept stránky?', 'pages_editing_named' => 'Upraviť stránku :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Referencie', 'references_none' => 'Neexistujú žiadne sledované referencie na túto položku.', - 'references_to_desc' => 'Nižšie sú zobrazené všetky známe stránky v systéme, ktoré odkazujú na túto položku.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/sk/errors.php b/lang/sk/errors.php index ed22e542a..06f396f17 100644 --- a/lang/sk/errors.php +++ b/lang/sk/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'Rozšírenie LDAP PHP nie je nainštalované', 'ldap_cannot_connect' => 'Nedá sa pripojiť k serveru ldap, počiatočné pripojenie zlyhalo', 'saml_already_logged_in' => 'Používateľ sa už prihlásil', - 'saml_user_not_registered' => 'Používateľ :name nie je zaregistrovaný a automatická registrácia je zakázaná', 'saml_no_email_address' => 'V údajoch poskytnutých externým overovacím systémom sa nepodarilo nájsť e-mailovú adresu tohto používateľa', 'saml_invalid_response_id' => 'Požiadavka z externého autentifikačného systému nie je rozpoznaná procesom spusteným touto aplikáciou. Tento problém môže spôsobiť navigácia späť po prihlásení.', 'saml_fail_authed' => 'Prihlásenie pomocou :system zlyhalo, systém neposkytol úspešnú autorizáciu', diff --git a/lang/sk/notifications.php b/lang/sk/notifications.php index 5539ae9a9..1afd23f1d 100644 --- a/lang/sk/notifications.php +++ b/lang/sk/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/sk/settings.php b/lang/sk/settings.php index d1b614705..0192066de 100644 --- a/lang/sk/settings.php +++ b/lang/sk/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/sl/entities.php b/lang/sl/entities.php index f6628dd1e..591dd009e 100644 --- a/lang/sl/entities.php +++ b/lang/sl/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Posodobljeno :timeLength', 'meta_updated_name' => 'Posodobil :timeLength uporabnik :user', 'meta_owned_name' => 'V lasti :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Izbira entitete', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Slike', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Uredi knjigo :bookName', 'books_form_book_name' => 'Ime knjige', 'books_save' => 'Shrani knjigo', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Dovoljenja knjige', 'books_permissions_updated' => 'Posodobljena dovoljenja knjige', 'books_empty_contents' => 'V tej knjigi ni bila ustvarjena še nobena stran ali poglavje.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Izbriši osnutek strani', 'pages_delete_success' => 'Stran izbirsana', 'pages_delete_draft_success' => 'Osnutek strani izbrisan', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Ste prepričani, da želite izbrisati to stran?', 'pages_delete_draft_confirm' => 'Ali ste prepričani, da želite izbrisati ta osnutek?', 'pages_editing_named' => 'Urejanje strani :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/sl/errors.php b/lang/sl/errors.php index ffabee7ec..51c34132b 100644 --- a/lang/sl/errors.php +++ b/lang/sl/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'PHP razširitev za LDAP ni nameščena', 'ldap_cannot_connect' => 'Ne morem se povezati na LDAP strežnik, neuspešna začetna povezava', 'saml_already_logged_in' => 'Že prijavljen', - 'saml_user_not_registered' => 'Uporabniško ime :name ni registrirano in avtomatska registracija je onemogočena', 'saml_no_email_address' => 'Nisem našel e-naslova za tega uporabnika v podatkih iz zunanjega sistema za preverjanje pristnosti', 'saml_invalid_response_id' => 'Zahteva iz zunanjega sistema za preverjanje pristnosti ni prepoznana s strani procesa zagnanega s strani te aplikacije. Pomik nazaj po prijavi je lahko vzrok teh težav.', 'saml_fail_authed' => 'Prijava z uporabo :system ni uspela, sistem ni zagotovil uspešne avtorizacije', diff --git a/lang/sl/notifications.php b/lang/sl/notifications.php index 5539ae9a9..1afd23f1d 100644 --- a/lang/sl/notifications.php +++ b/lang/sl/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/sl/settings.php b/lang/sl/settings.php index a4469e53b..9b543040d 100644 --- a/lang/sl/settings.php +++ b/lang/sl/settings.php @@ -297,6 +297,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/sq/entities.php b/lang/sq/entities.php index cfb5aae1a..f1f915544 100644 --- a/lang/sq/entities.php +++ b/lang/sq/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Updated :timeLength', 'meta_updated_name' => 'Updated :timeLength by :user', 'meta_owned_name' => 'Owned by :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Entity Select', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => 'Images', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Edit Book :bookName', 'books_form_book_name' => 'Book Name', 'books_save' => 'Save Book', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Book Permissions', 'books_permissions_updated' => 'Book Permissions Updated', 'books_empty_contents' => 'No pages or chapters have been created for this book.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Delete Draft Page', 'pages_delete_success' => 'Page deleted', 'pages_delete_draft_success' => 'Draft page deleted', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Are you sure you want to delete this page?', 'pages_delete_draft_confirm' => 'Are you sure you want to delete this draft page?', 'pages_editing_named' => 'Editing Page :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/sq/errors.php b/lang/sq/errors.php index 8813cf90a..607b6ea83 100644 --- a/lang/sq/errors.php +++ b/lang/sq/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'LDAP PHP extension not installed', 'ldap_cannot_connect' => 'Cannot connect to ldap server, Initial connection failed', 'saml_already_logged_in' => 'Already logged in', - 'saml_user_not_registered' => 'The user :name is not registered and automatic registration is disabled', 'saml_no_email_address' => 'Could not find an email address, for this user, in the data provided by the external authentication system', 'saml_invalid_response_id' => 'The request from the external authentication system is not recognised by a process started by this application. Navigating back after a login could cause this issue.', 'saml_fail_authed' => 'Login using :system failed, system did not provide successful authorization', diff --git a/lang/sq/notifications.php b/lang/sq/notifications.php index 5539ae9a9..1afd23f1d 100644 --- a/lang/sq/notifications.php +++ b/lang/sq/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/sq/settings.php b/lang/sq/settings.php index c5ca662c3..03e9bf462 100644 --- a/lang/sq/settings.php +++ b/lang/sq/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/sv/entities.php b/lang/sv/entities.php index a1c0a97d7..5ff5cfe20 100644 --- a/lang/sv/entities.php +++ b/lang/sv/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Uppdaterad :timeLength', 'meta_updated_name' => 'Uppdaterad :timeLength av :user', 'meta_owned_name' => 'Ägs av :user', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Välj enhet', 'entity_select_lack_permission' => 'Du har inte den behörighet som krävs för att välja det här objektet', 'images' => 'Bilder', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Redigera bok :bookName', 'books_form_book_name' => 'Bokens namn', 'books_save' => 'Spara bok', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Rättigheter för boken', 'books_permissions_updated' => 'Bokens rättigheter har uppdaterats', 'books_empty_contents' => 'Det finns inga sidor eller kapitel i den här boken.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Ta bort utkast', 'pages_delete_success' => 'Sidan har tagits bort', 'pages_delete_draft_success' => 'Utkastet har tagits bort', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Är du säker på att du vill ta bort den här sidan?', 'pages_delete_draft_confirm' => 'Är du säker på att du vill ta bort det här utkastet?', 'pages_editing_named' => 'Redigerar sida :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Referenser', 'references_none' => 'Det finns inga referenser kopplade till detta objekt.', - 'references_to_desc' => 'Nedan visas alla kända sidor i systemet som länkar till detta objekt.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/sv/errors.php b/lang/sv/errors.php index 865a067c7..e24e54807 100644 --- a/lang/sv/errors.php +++ b/lang/sv/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'LDAP PHP-tillägg inte installerat', 'ldap_cannot_connect' => 'Kan inte ansluta till ldap-servern. Anslutningen misslyckades', 'saml_already_logged_in' => 'Redan inloggad', - 'saml_user_not_registered' => 'Användarnamnet är inte registrerat och automatisk registrering är inaktiverad', 'saml_no_email_address' => 'Kunde inte hitta en e-postadress för den här användaren i data som tillhandahålls av det externa autentiseringssystemet', 'saml_invalid_response_id' => 'En begäran från det externa autentiseringssystemet känns inte igen av en process som startats av denna applikation. Att navigera bakåt efter en inloggning kan orsaka detta problem.', 'saml_fail_authed' => 'Inloggning med :system misslyckades, systemet godkände inte auktoriseringen', diff --git a/lang/sv/notifications.php b/lang/sv/notifications.php index b7702558c..9d2269ed8 100644 --- a/lang/sv/notifications.php +++ b/lang/sv/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/sv/settings.php b/lang/sv/settings.php index e0b2564f4..830680b65 100644 --- a/lang/sv/settings.php +++ b/lang/sv/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/tr/activities.php b/lang/tr/activities.php index be6a0777e..6a0c6ec5d 100644 --- a/lang/tr/activities.php +++ b/lang/tr/activities.php @@ -10,7 +10,7 @@ return [ 'page_create_notification' => 'Sayfa Başarıyla Oluşturuldu', 'page_update' => 'sayfayı güncelledi', 'page_update_notification' => 'Sayfa başarıyla güncellendi', - 'page_delete' => 'sayfayı sildi', + 'page_delete' => 'sayfa silindi', 'page_delete_notification' => 'Sayfa başarıyla silindi', 'page_restore' => 'sayfayı eski haline getirdi', 'page_restore_notification' => 'Sayfa Başarıyla Eski Haline Getirildi', @@ -32,7 +32,7 @@ return [ 'book_create_notification' => 'Kitap başarıyla oluşturuldu', 'book_create_from_chapter' => 'converted chapter to book', 'book_create_from_chapter_notification' => 'Bölüm başarıyla kitaba dönüştürüldü', - 'book_update' => 'kitabı güncelledi', + 'book_update' => 'güncellenen kitap', 'book_update_notification' => 'Kitap başarıyla güncellendi', 'book_delete' => 'kitabı sildi', 'book_delete_notification' => 'Kitap başarıyla silindi', @@ -65,7 +65,7 @@ return [ 'auth_login' => 'oturum açıldı', 'auth_register' => 'yeni kullanıcı olarak kayıt yapıldı', 'auth_password_reset_request' => 'requested user password reset', - 'auth_password_reset_update' => 'reset user password', + 'auth_password_reset_update' => 'Kullanıcı parolasını sıfırla', 'mfa_setup_method' => 'configured MFA method', 'mfa_setup_method_notification' => 'Çok aşamalı kimlik doğrulama yöntemi başarıyla yapılandırıldı', 'mfa_remove_method' => 'removed MFA method', @@ -97,13 +97,13 @@ return [ 'api_token_create_notification' => 'API anahtarı başarıyla oluşturuldu', 'api_token_update' => 'updated api token', 'api_token_update_notification' => 'API anahtarı başarıyla güncellendi', - 'api_token_delete' => 'deleted api token', + 'api_token_delete' => 'silinen sayfa tokenı', 'api_token_delete_notification' => 'API anahtarı başarıyla silindi', // Roles - 'role_create' => 'created role', + 'role_create' => 'oluşturulan rol', 'role_create_notification' => 'Rol başarıyla oluşturuldu', - 'role_update' => 'updated role', + 'role_update' => 'güncellenmiş rol', 'role_update_notification' => 'Rol başarıyla güncellendi', 'role_delete' => 'deleted role', 'role_delete_notification' => 'Rol başarıyla silindi', diff --git a/lang/tr/entities.php b/lang/tr/entities.php index 07922b647..9cc6774ea 100644 --- a/lang/tr/entities.php +++ b/lang/tr/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => ':timeLength güncellendi', 'meta_updated_name' => ':user tarafından :timeLength güncellendi', 'meta_owned_name' => ':user kişisine ait', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Öge Seçimi', 'entity_select_lack_permission' => 'Bu öğeyi seçmek için gerekli izinlere sahip değilsiniz', 'images' => 'Görseller', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => ':bookName Kitabını Düzenle', 'books_form_book_name' => 'Kitap Adı', 'books_save' => 'Kitabı Kaydet', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Kitap İzinleri', 'books_permissions_updated' => 'Kitap İzinleri Güncellendi', 'books_empty_contents' => 'Bu kitaba ait sayfa veya bölüm oluşturulmamış.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Sayfa Taslağını Sil', 'pages_delete_success' => 'Sayfa silindi', 'pages_delete_draft_success' => 'Sayfa taslağı silindi', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Bu sayfayı silmek istediğinize emin misiniz?', 'pages_delete_draft_confirm' => 'Bu sayfa taslağını silmek istediğinize emin misiniz?', 'pages_editing_named' => ':pageName Sayfası Düzenleniyor', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Referanslar', 'references_none' => 'Bu öğeye ilişkin takip edilen bir referans bulunmamaktadır.', - 'references_to_desc' => 'Aşağıda, sistemde bu öğeye bağlantı veren bilinen tüm sayfalar gösterilmektedir.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/tr/errors.php b/lang/tr/errors.php index 3e301b34e..587ba9b4c 100644 --- a/lang/tr/errors.php +++ b/lang/tr/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'LDAP PHP eklentisi kurulu değil', 'ldap_cannot_connect' => 'LDAP sunucusuna bağlanılamadı, ilk bağlantı başarısız oldu', 'saml_already_logged_in' => 'Zaten giriş yapılmış', - 'saml_user_not_registered' => ':name adlı kullanıcı kayıtlı değil ve otomatik kaydolma devre dışı bırakılmış', 'saml_no_email_address' => 'Harici kimlik doğrulama sisteminden gelen veriler, bu kullanıcının e-posta adresini içermiyor', 'saml_invalid_response_id' => 'Harici doğrulama sistemi tarafından sağlanan bir veri talebi, bu uygulama tarafından başlatılan bir işlem tarafından tanınamadı. Giriş yaptıktan sonra geri dönmek bu soruna yol açmış olabilir.', 'saml_fail_authed' => ':system kullanarak giriş yapma başarısız oldu; sistem, başarılı bir kimlik doğrulama sağlayamadı', diff --git a/lang/tr/notifications.php b/lang/tr/notifications.php index 5539ae9a9..1afd23f1d 100644 --- a/lang/tr/notifications.php +++ b/lang/tr/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/tr/settings.php b/lang/tr/settings.php index 1f039b072..ce312f1c3 100644 --- a/lang/tr/settings.php +++ b/lang/tr/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'İbranice', 'hr' => 'Hrvatski', diff --git a/lang/uk/activities.php b/lang/uk/activities.php index ce952049e..1ab0eca61 100644 --- a/lang/uk/activities.php +++ b/lang/uk/activities.php @@ -25,7 +25,7 @@ return [ 'chapter_delete' => 'видалив розділ', 'chapter_delete_notification' => 'Розділ успішно видалено', 'chapter_move' => 'перемістив розділ', - 'chapter_move_notification' => 'Chapter successfully moved', + 'chapter_move_notification' => 'Розділ успішно перенесений', // Books 'book_create' => 'створив книгу', @@ -50,31 +50,31 @@ return [ 'bookshelf_delete_notification' => 'Полиця успішно видалена', // Revisions - 'revision_restore' => 'restored revision', - 'revision_delete' => 'deleted revision', - 'revision_delete_notification' => 'Revision successfully deleted', + 'revision_restore' => 'відновлено версію', + 'revision_delete' => 'видалена версія', + 'revision_delete_notification' => 'Версію успішно видалено', // Favourites 'favourite_add_notification' => '":ім\'я" було додане до ваших улюлених', 'favourite_remove_notification' => '":ім\'я" було видалено з ваших улюблених', // Watching - 'watch_update_level_notification' => 'Watch preferences successfully updated', + 'watch_update_level_notification' => 'Налаштування перегляду успішно оновлено', // Auth - 'auth_login' => 'logged in', - 'auth_register' => 'registered as new user', - 'auth_password_reset_request' => 'requested user password reset', - 'auth_password_reset_update' => 'reset user password', - 'mfa_setup_method' => 'configured MFA method', + 'auth_login' => 'ввійшли', + 'auth_register' => 'зареєстрований як новий користувач', + 'auth_password_reset_request' => 'запит на скидання пароля користувача', + 'auth_password_reset_update' => 'скинути пароль користувача', + 'mfa_setup_method' => 'налаштований метод MFA', 'mfa_setup_method_notification' => 'Багатофакторний метод успішно налаштований', - 'mfa_remove_method' => 'removed MFA method', + 'mfa_remove_method' => 'видалив метод MFA', 'mfa_remove_method_notification' => 'Багатофакторний метод успішно видалений', // Settings - 'settings_update' => 'updated settings', - 'settings_update_notification' => 'Settings successfully updated', - 'maintenance_action_run' => 'ran maintenance action', + 'settings_update' => 'оновлені налаштування', + 'settings_update_notification' => 'Налаштування успішно оновлено', + 'maintenance_action_run' => 'виконуються дії щодо обслуговування', // Webhooks 'webhook_create' => 'створений вебхук', @@ -85,38 +85,38 @@ return [ 'webhook_delete_notification' => 'Вебхуки успішно видалено', // Users - 'user_create' => 'created user', - 'user_create_notification' => 'User successfully created', - 'user_update' => 'updated user', + 'user_create' => 'створений користувач', + 'user_create_notification' => 'Користувач успішно створений', + 'user_update' => 'оновлений користувач', 'user_update_notification' => 'Користувача було успішно оновлено', - 'user_delete' => 'deleted user', + 'user_delete' => 'вилучений користувач', 'user_delete_notification' => 'Користувача успішно видалено', // API Tokens - 'api_token_create' => 'created api token', - 'api_token_create_notification' => 'API token successfully created', - 'api_token_update' => 'updated api token', - 'api_token_update_notification' => 'API token successfully updated', - 'api_token_delete' => 'deleted api token', - 'api_token_delete_notification' => 'API token successfully deleted', + 'api_token_create' => 'створений APi токен', + 'api_token_create_notification' => 'API токен успішно створений', + 'api_token_update' => 'оновлено API токен', + 'api_token_update_notification' => 'Токен API успішно оновлено', + 'api_token_delete' => 'видалено API токен', + 'api_token_delete_notification' => 'API-токен успішно видалено', // Roles - 'role_create' => 'created role', + 'role_create' => 'створену роль', 'role_create_notification' => 'Роль успішно створена', - 'role_update' => 'updated role', + 'role_update' => 'оновлена роль', 'role_update_notification' => 'Роль успішно оновлена', - 'role_delete' => 'deleted role', + 'role_delete' => 'видалена роль', 'role_delete_notification' => 'Роль успішно видалена', // Recycle Bin - 'recycle_bin_empty' => 'emptied recycle bin', - 'recycle_bin_restore' => 'restored from recycle bin', - 'recycle_bin_destroy' => 'removed from recycle bin', + 'recycle_bin_empty' => 'очищено кошик', + 'recycle_bin_restore' => 'відновлено із кошику', + 'recycle_bin_destroy' => 'видалено з кошика', // Comments 'commented_on' => 'прокоментував', - 'comment_create' => 'added comment', - 'comment_update' => 'updated comment', + 'comment_create' => 'додано коментар', + 'comment_update' => 'оновлено коментар', 'comment_delete' => 'видалений коментар', // Other diff --git a/lang/uk/common.php b/lang/uk/common.php index 4459e2cb4..7d79b53f2 100644 --- a/lang/uk/common.php +++ b/lang/uk/common.php @@ -6,7 +6,7 @@ return [ // Buttons 'cancel' => 'Скасувати', - 'close' => 'Close', + 'close' => 'Закрити', 'confirm' => 'Застосувати', 'back' => 'Назад', 'save' => 'Зберегти', @@ -42,7 +42,7 @@ return [ 'remove' => 'Видалити', 'add' => 'Додати', 'configure' => 'Налаштувати', - 'manage' => 'Manage', + 'manage' => 'Управління', 'fullscreen' => 'На весь екран', 'favourite' => 'Улюблене', 'unfavourite' => 'Прибрати з обраного', @@ -52,7 +52,7 @@ return [ 'filter_clear' => 'Очистити фільтр', 'download' => 'Завантажити', 'open_in_tab' => 'Відкрити в новій вкладці', - 'open' => 'Open', + 'open' => 'Відкрити', // Sort Options 'sort_options' => 'Параметри сортування', diff --git a/lang/uk/components.php b/lang/uk/components.php index 9276c6e2f..bbcfbfe1e 100644 --- a/lang/uk/components.php +++ b/lang/uk/components.php @@ -6,36 +6,36 @@ return [ // Image Manager 'image_select' => 'Вибрати зображення', - 'image_list' => 'Image List', - 'image_details' => 'Image Details', - 'image_upload' => 'Upload Image', - 'image_intro' => 'Here you can select and manage images that have been previously uploaded to the system.', - 'image_intro_upload' => 'Upload a new image by dragging an image file into this window, or by using the "Upload Image" button above.', + 'image_list' => 'Список зображень', + 'image_details' => 'Деталі зображення', + 'image_upload' => 'Завантажити зображення', + 'image_intro' => 'Тут ви можете вибрати і керувати зображеннями, які раніше були завантажені в систему.', + 'image_intro_upload' => 'Завантажте нове зображення, перетягуючи файл зображення до цього вікна або скориставшись кнопкою "Завантажити зображення" вище.', 'image_all' => 'Всі', 'image_all_title' => 'Переглянути всі зображення', 'image_book_title' => 'Переглянути зображення, завантажені в цю книгу', 'image_page_title' => 'Переглянути зображення, завантажені на цю сторінку', 'image_search_hint' => 'Пошук по імені зображення', 'image_uploaded' => 'Завантажено :uploadedDate', - 'image_uploaded_by' => 'Uploaded by :userName', - 'image_uploaded_to' => 'Uploaded to :pageLink', - 'image_updated' => 'Updated :updateDate', + 'image_uploaded_by' => 'Завантажено :userName', + 'image_uploaded_to' => 'Завантажено на :pageLink', + 'image_updated' => 'Оновлено :updateDate', 'image_load_more' => 'Завантажити ще', 'image_image_name' => 'Назва зображення', 'image_delete_used' => 'Це зображення використовується на наступних сторінках.', 'image_delete_confirm_text' => 'Ви дійсно хочете видалити це зображення?', 'image_select_image' => 'Вибрати зображення', 'image_dropzone' => 'Перетягніть зображення, або натисніть тут для завантаження', - 'image_dropzone_drop' => 'Drop images here to upload', + 'image_dropzone_drop' => 'Перетягніть зображення сюди для завантаження', 'images_deleted' => 'Зображень видалено', 'image_preview' => 'Попередній перегляд зображення', 'image_upload_success' => 'Зображення завантажено успішно', 'image_update_success' => 'Деталі зображення успішно оновлені', 'image_delete_success' => 'Зображення успішно видалено', - 'image_replace' => 'Replace Image', - 'image_replace_success' => 'Image file successfully updated', - 'image_rebuild_thumbs' => 'Regenerate Size Variations', - 'image_rebuild_thumbs_success' => 'Image size variations successfully rebuilt!', + 'image_replace' => 'Замінити зображення', + 'image_replace_success' => 'Файл зображення успішно оновлено', + 'image_rebuild_thumbs' => 'Оновити розмір', + 'image_rebuild_thumbs_success' => 'Варіації розміру зображень успішно перебудовані!', // Code Editor 'code_editor' => 'Редагувати код', diff --git a/lang/uk/entities.php b/lang/uk/entities.php index 88b7449c5..3e657cf1f 100644 --- a/lang/uk/entities.php +++ b/lang/uk/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Оновлено :timeLength', 'meta_updated_name' => ':user оновив :timeLength', 'meta_owned_name' => 'Власник :user', - 'meta_reference_page_count' => 'Посилання на :count сторінки|Посилання на :count сторінок', + 'meta_reference_count' => 'Посилання на :count елемент|Посилання – :count елементів', 'entity_select' => 'Вибір об\'єкта', 'entity_select_lack_permission' => 'У вас немає необхідних прав для вибору цього елемента', 'images' => 'Зображення', @@ -106,7 +106,7 @@ return [ 'shelves_permissions_updated' => 'Дозволи полиці оновлено', 'shelves_permissions_active' => 'Дозволи полиці активні', 'shelves_permissions_cascade_warning' => 'Дозволи на полицях не каскадують автоматично до вміщених книг. Це тому, що книга може стояти на кількох полицях. Однак дозволи можна скопіювати до дочірніх книг за допомогою наведеної нижче опції.', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', + 'shelves_permissions_create' => 'Створення привілеїв для копіювання дозволів для дочірніх книг, використовуючи наведену нижче дію. Вони не контролюють можливість створення книг.', 'shelves_copy_permissions_to_books' => 'Копіювати дозволи на книги', 'shelves_copy_permissions' => 'Копіювати дозволи', 'shelves_copy_permissions_explain' => 'Це застосує поточні налаштування дозволів цієї полиці до всіх книг, які містяться в ній. Перед активацією переконайтеся, що будь-які зміни в дозволах цієї полиці збережено.', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Редагувати книгу :bookName', 'books_form_book_name' => 'Назва книги', 'books_save' => 'Зберегти книгу', + 'books_default_template' => 'Типовий шаблон сторінки', + 'books_default_template_explain' => 'Призначити шаблон сторінки, який буде використовуватися як типовий вміст для всіх нових сторінок цієї книги. Майте на увазі, що це буде використано лише в тому випадку, якщо творець сторінки має доступ до обраної сторінки шаблону.', + 'books_default_template_select' => 'Виберіть сторінку шаблону', 'books_permissions' => 'Дозволи на книгу', 'books_permissions_updated' => 'Дозволи на книгу оновлено', 'books_empty_contents' => 'Для цієї книги не створено жодної сторінки або розділів.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Видалити чернетку', 'pages_delete_success' => 'Сторінка видалена', 'pages_delete_draft_success' => 'Чернетка видалена', + 'pages_delete_warning_template' => 'Ця сторінка використовується як шаблон сторінки за промовчанням. У цих книгах немає стандартного шаблону сторінки, призначеного після видалення цієї сторінки.', 'pages_delete_confirm' => 'Ви впевнені, що хочете видалити цю сторінку?', 'pages_delete_draft_confirm' => 'Ви впевнені, що хочете видалити цю чернетку?', 'pages_editing_named' => 'Редагування сторінки :pageName', @@ -214,7 +218,7 @@ return [ 'pages_editing_page' => 'Редагування сторінки', 'pages_edit_draft_save_at' => 'Чернетка збережена о ', 'pages_edit_delete_draft' => 'Видалити чернетку', - 'pages_edit_delete_draft_confirm' => 'Are you sure you want to delete your draft page changes? All of your changes, since the last full save, will be lost and the editor will be updated with the latest page non-draft save state.', + 'pages_edit_delete_draft_confirm' => 'Ви дійсно бажаєте видалити зміни у чернетці? Всі зміни, починаючи з останнього повного збереження, будуть втрачені і редактор буде оновлений з останньою сторінкою збереження без чернетки.', 'pages_edit_discard_draft' => 'Відхилити чернетку', 'pages_edit_switch_to_markdown' => 'Змінити редактор на Markdown', 'pages_edit_switch_to_markdown_clean' => '(Очистити вміст)', @@ -239,8 +243,8 @@ return [ 'pages_md_insert_drawing' => 'Вставити малюнок', 'pages_md_show_preview' => 'Показати попередній перегляд', 'pages_md_sync_scroll' => 'Синхронізація прокручування попереднього перегляду', - 'pages_drawing_unsaved' => 'Unsaved Drawing Found', - 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', + 'pages_drawing_unsaved' => 'Знайдено незбережену чернетку', + 'pages_drawing_unsaved_confirm' => 'Незбережені чернетки були знайдені з попередньої спроби зберегти звіт. Хочете відновити і продовжити редагування цієї чернетки?', 'pages_not_in_chapter' => 'Сторінка не знаходиться в розділі', 'pages_move' => 'Перемістити сторінку', 'pages_copy' => 'Копіювати сторінку', @@ -268,13 +272,13 @@ return [ 'pages_revisions_restore' => 'Відновити', 'pages_revisions_none' => 'Ця сторінка не має версій', 'pages_copy_link' => 'Копіювати посилання', - 'pages_edit_content_link' => 'Jump to section in editor', - 'pages_pointer_enter_mode' => 'Enter section select mode', - 'pages_pointer_label' => 'Page Section Options', - 'pages_pointer_permalink' => 'Page Section Permalink', - 'pages_pointer_include_tag' => 'Page Section Include Tag', - 'pages_pointer_toggle_link' => 'Permalink mode, Press to show include tag', - 'pages_pointer_toggle_include' => 'Include tag mode, Press to show permalink', + 'pages_edit_content_link' => 'Перейти до розділу в редакторі', + 'pages_pointer_enter_mode' => 'Введіть режим вибору розділу', + 'pages_pointer_label' => 'Параметри розділу сторінки', + 'pages_pointer_permalink' => 'Постійне посилання на секцію сторінок', + 'pages_pointer_include_tag' => ' Секція сторінки включаючи тег', + 'pages_pointer_toggle_link' => 'Постійне посилання, натисніть для включення тегу', + 'pages_pointer_toggle_include' => 'Включити режим тегів, натисніть для відображення постійного посилання', 'pages_permissions_active' => 'Активні дозволи сторінки', 'pages_initial_revision' => 'Початкова публікація', 'pages_references_update_revision' => 'Автоматичне оновлення системних посилань', @@ -289,13 +293,13 @@ return [ 'time_b' => 'за останні :minCount хвилин', 'message' => ':start :time. Будьте обережні, щоб не перезаписати оновлення інших!', ], - 'pages_draft_discarded' => 'Draft discarded! The editor has been updated with the current page content', - 'pages_draft_deleted' => 'Draft deleted! The editor has been updated with the current page content', + 'pages_draft_discarded' => 'Чернетку відкинуто! Редактор був оновлений з поточним вмістом сторінки', + 'pages_draft_deleted' => 'Чернетку видалено! Редактор був оновлений з поточною сторінкою вмісту', 'pages_specific' => 'Конкретна сторінка', 'pages_is_template' => 'Шаблон сторінки', // Editor Sidebar - 'toggle_sidebar' => 'Toggle Sidebar', + 'toggle_sidebar' => 'Перемикач бічної панелі', 'page_tags' => 'Теги сторінки', 'chapter_tags' => 'Теги розділів', 'book_tags' => 'Теги книг', @@ -323,10 +327,10 @@ return [ 'attachments_explain_instant_save' => 'Зміни тут зберігаються миттєво.', 'attachments_upload' => 'Завантажити файл', 'attachments_link' => 'Приєднати посилання', - 'attachments_upload_drop' => 'Alternatively you can drag and drop a file here to upload it as an attachment.', + 'attachments_upload_drop' => 'Крім того, ви можете перетягувати файл тут і завантажити його в якості вкладення.', 'attachments_set_link' => 'Встановити посилання', 'attachments_delete' => 'Дійсно хочете видалити це вкладення?', - 'attachments_dropzone' => 'Drop files here to upload', + 'attachments_dropzone' => 'Для завантаження перетягніть файли', 'attachments_no_files' => 'Файли не завантажені', 'attachments_explain_link' => 'Ви можете приєднати посилання, якщо не бажаєте завантажувати файл. Це може бути посилання на іншу сторінку або посилання на файл у хмарі.', 'attachments_link_name' => 'Назва посилання', @@ -369,13 +373,13 @@ return [ 'comment_new' => 'Новий коментар', 'comment_created' => 'прокоментував :createDiff', 'comment_updated' => 'Оновлено :updateDiff користувачем :username', - 'comment_updated_indicator' => 'Updated', + 'comment_updated_indicator' => 'Оновлено', 'comment_deleted_success' => 'Коментар видалено', 'comment_created_success' => 'Коментар додано', 'comment_updated_success' => 'Коментар оновлено', 'comment_delete_confirm' => 'Ви впевнені, що хочете видалити цей коментар?', 'comment_in_reply_to' => 'У відповідь на :commentId', - 'comment_editor_explain' => 'Here are the comments that have been left on this page. Comments can be added & managed when viewing the saved page.', + 'comment_editor_explain' => 'Ось коментарі, які залишилися на цій сторінці. Коментарі можна додати та керовані при перегляді збереженої сторінки.', // Revision 'revision_delete_confirm' => 'Ви впевнені, що хочете видалити цю версію?', @@ -405,29 +409,29 @@ return [ // References 'references' => 'Посилання', 'references_none' => 'Немає відслідковуваних посилань для цього елемента.', - 'references_to_desc' => 'Показані нижче всі відомі сторінки в системі, що посилаються на цей елемент.', + 'references_to_desc' => 'У списку наведений нижче всі відомі вміст системи, посилання на цей вузол.', // Watch Options - 'watch' => 'Watch', - 'watch_title_default' => 'Default Preferences', - 'watch_desc_default' => 'Revert watching to just your default notification preferences.', - 'watch_title_ignore' => 'Ignore', - 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', - 'watch_title_new' => 'New Pages', - 'watch_desc_new' => 'Notify when any new page is created within this item.', - 'watch_title_updates' => 'All Page Updates', - 'watch_desc_updates' => 'Notify upon all new pages and page changes.', - 'watch_desc_updates_page' => 'Notify upon all page changes.', - 'watch_title_comments' => 'All Page Updates & Comments', - 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', - 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', - 'watch_change_default' => 'Change default notification preferences', - 'watch_detail_ignore' => 'Ignoring notifications', - 'watch_detail_new' => 'Watching for new pages', - 'watch_detail_updates' => 'Watching new pages and updates', - 'watch_detail_comments' => 'Watching new pages, updates & comments', - 'watch_detail_parent_book' => 'Watching via parent book', - 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', - 'watch_detail_parent_chapter' => 'Watching via parent chapter', - 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', + 'watch' => 'Дивитися', + 'watch_title_default' => 'Властивості по замовчуванню', + 'watch_desc_default' => 'Відновити перегляд лише до типових налаштувань сповіщень.', + 'watch_title_ignore' => 'Ігнорувати', + 'watch_desc_ignore' => 'Ігнорувати всі повідомлення, у тому числі з налаштувань рівня користувача.', + 'watch_title_new' => 'Нові сторінки', + 'watch_desc_new' => 'Сповіщати при створенні нової сторінки у цьому елементі.', + 'watch_title_updates' => 'Оновлення всієї сторінки', + 'watch_desc_updates' => 'Сповіщати про всі нові сторінки і зміни сторінок.', + 'watch_desc_updates_page' => 'Сповіщати про зміни на всіх сторінках.', + 'watch_title_comments' => 'Всі оновлення та коментарі до сторінки', + 'watch_desc_comments' => 'Повідомляти про всі нові сторінки, зміни до сторінки і нові коментарі.', + 'watch_desc_comments_page' => 'Повідомляти про зміни сторінки і нові коментарі.', + 'watch_change_default' => 'Змінити налаштування сповіщень за замовчуванням', + 'watch_detail_ignore' => 'Ігнорування сповіщень', + 'watch_detail_new' => 'Перегляд нових сторінок', + 'watch_detail_updates' => 'Перегляд нових сторінок і оновлень', + 'watch_detail_comments' => 'Перегляд нових сторінок, оновлень та коментарів', + 'watch_detail_parent_book' => 'Перегляд за допомогою батьківської книги', + 'watch_detail_parent_book_ignore' => 'Ігнорування за допомогою батьківської книги', + 'watch_detail_parent_chapter' => 'Перегляд через батьківську главу', + 'watch_detail_parent_chapter_ignore' => 'Ігнорування через батьківську главу', ]; diff --git a/lang/uk/errors.php b/lang/uk/errors.php index e07e85668..533d3a1e2 100644 --- a/lang/uk/errors.php +++ b/lang/uk/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'Розширення PHP LDAP не встановлено', 'ldap_cannot_connect' => 'Неможливо підключитися до ldap-сервера, Помилка з\'єднання', 'saml_already_logged_in' => 'Вже увійшли', - 'saml_user_not_registered' => 'Користувач «:name» не зареєстрований, а автоматична реєстрація вимкнена', 'saml_no_email_address' => 'Не вдалося знайти електронну адресу для цього користувача у даних, наданих зовнішньою системою аутентифікації', 'saml_invalid_response_id' => 'Запит із зовнішньої системи аутентифікації не розпізнається процесом, розпочатим цим додатком. Повернення назад після входу могла спричинити цю проблему.', 'saml_fail_authed' => 'Вхід із використанням «:system» не вдався, система не здійснила успішну авторизацію', @@ -44,25 +43,25 @@ return [ 'cannot_get_image_from_url' => 'Неможливо отримати зображення з :url', 'cannot_create_thumbs' => 'Сервер не може створювати ескізи. Будь ласка, перевірте, чи встановлено розширення GD PHP.', 'server_upload_limit' => 'Сервер не дозволяє завантажувати файли такого розміру. Спробуйте менший розмір файлу.', - 'server_post_limit' => 'The server cannot receive the provided amount of data. Try again with less data or a smaller file.', + 'server_post_limit' => 'Сервер не може отримати вказаний обсяг даних. Спробуйте ще раз з меншими даними або меншим файлом.', 'uploaded' => 'Сервер не дозволяє завантажувати файли такого розміру. Спробуйте менший розмір файлу.', // Drawing & Images 'image_upload_error' => 'Виникла помилка під час завантаження зображення', 'image_upload_type_error' => 'Тип завантаженого зображення недійсний', - 'image_upload_replace_type' => 'Image file replacements must be of the same type', - 'image_upload_memory_limit' => 'Failed to handle image upload and/or create thumbnails due to system resource limits.', - 'image_thumbnail_memory_limit' => 'Failed to create image size variations due to system resource limits.', - 'image_gallery_thumbnail_memory_limit' => 'Failed to create gallery thumbnails due to system resource limits.', + 'image_upload_replace_type' => 'Замінники файлів зображень повинні мати однаковий тип', + 'image_upload_memory_limit' => 'Не вдалося завантажити зображення і/або створити ескізи через обмеження системних ресурсів.', + 'image_thumbnail_memory_limit' => 'Не вдалося створити варіації розміру зображення через обмеження системних ресурсів.', + 'image_gallery_thumbnail_memory_limit' => 'Не вдалося створити галерею через обмеження системних ресурсів.', 'drawing_data_not_found' => 'Не вдалося завантажити дані малюнка. Файл малюнка може більше не існувати або у вас немає дозволу на доступ до нього.', // Attachments 'attachment_not_found' => 'Вкладення не знайдено', - 'attachment_upload_error' => 'An error occurred uploading the attachment file', + 'attachment_upload_error' => 'Сталася помилка при завантаженні файлу', // Pages 'page_draft_autosave_fail' => 'Не вдалося зберегти чернетку. Перед збереженням цієї сторінки переконайтеся, що у вас є зв\'язок з сервером.', - 'page_draft_delete_fail' => 'Failed to delete page draft and fetch current page saved content', + 'page_draft_delete_fail' => 'Не вдалося видалити чернетку сторінки та отримати збережений вміст сторінки', 'page_custom_home_deletion' => 'Неможливо видалити сторінку, коли вона встановлена як домашня сторінка', // Entities @@ -116,5 +115,5 @@ return [ 'maintenance_test_email_failure' => 'Помилка під час надсилання тестового електронного листа:', // HTTP errors - 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', + 'http_ssr_url_no_match' => 'URL-адреса не відповідає налаштованим дозволеним SSR хостів', ]; diff --git a/lang/uk/notifications.php b/lang/uk/notifications.php index 5539ae9a9..a08b9a100 100644 --- a/lang/uk/notifications.php +++ b/lang/uk/notifications.php @@ -4,23 +4,24 @@ */ return [ - 'new_comment_subject' => 'New comment on page: :pageName', - 'new_comment_intro' => 'A user has commented on a page in :appName:', - 'new_page_subject' => 'New page: :pageName', - 'new_page_intro' => 'A new page has been created in :appName:', - 'updated_page_subject' => 'Updated page: :pageName', - 'updated_page_intro' => 'A page has been updated in :appName:', - 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'new_comment_subject' => 'Новий коментар на сторінці: :pageName', + 'new_comment_intro' => 'Користувач прокоментував на сторінці у :appName:', + 'new_page_subject' => 'Нова сторінка: :pageName', + 'new_page_intro' => 'Створено сторінку у :appName:', + 'updated_page_subject' => 'Оновлено сторінку: :pageName', + 'updated_page_intro' => 'Оновлено сторінку у :appName:', + 'updated_page_debounce' => 'Для запобігання кількості сповіщень, деякий час ви не будете відправлені повідомлення для подальших змін на цій сторінці тим самим редактором.', - 'detail_page_name' => 'Page Name:', - 'detail_commenter' => 'Commenter:', - 'detail_comment' => 'Comment:', - 'detail_created_by' => 'Created By:', - 'detail_updated_by' => 'Updated By:', + 'detail_page_name' => 'Назва сторінки:', + 'detail_page_path' => 'Шлях до сторінки:', + 'detail_commenter' => 'Коментатор:', + 'detail_comment' => 'Коментар:', + 'detail_created_by' => 'Створено:', + 'detail_updated_by' => 'Оновлено:', - 'action_view_comment' => 'View Comment', - 'action_view_page' => 'View Page', + 'action_view_comment' => 'Переглянути коментар', + 'action_view_page' => 'Дивитись сторінку', - 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', - 'footer_reason_link' => 'your notification preferences', + 'footer_reason' => 'Дане повідомлення було надіслано вам тому, що :link покриває цю діяльність для цього елемента.', + 'footer_reason_link' => 'ваші налаштування сповіщень', ]; diff --git a/lang/uk/preferences.php b/lang/uk/preferences.php index 25683e38f..2383c6793 100644 --- a/lang/uk/preferences.php +++ b/lang/uk/preferences.php @@ -5,10 +5,10 @@ */ return [ - 'my_account' => 'My Account', + 'my_account' => 'Мій аккаунт', 'shortcuts' => 'Ярлики', - 'shortcuts_interface' => 'UI Shortcut Preferences', + 'shortcuts_interface' => 'Налаштування ярлика інтерфейсу', 'shortcuts_toggle_desc' => 'Тут ви можете увімкнути або вимкнути ярлики інтерфейсу клавіатури, які використовуються для навігації та дій.', 'shortcuts_customize_desc' => 'Ви можете налаштувати кожен з ярликів нижче. Просто натисніть на комбінацію бажаного ключа після вибору вводу для ярлика.', 'shortcuts_toggle_label' => 'Клавіатурні скорочення увімкнено', @@ -17,35 +17,35 @@ return [ 'shortcuts_save' => 'Зберегти ярлики', 'shortcuts_overlay_desc' => 'Примітка: якщо ярлики ввімкнено, допоміжне накладання доступне, натиснувши "?" який виділить доступні ярлики для дій, які зараз видно на екрані.', 'shortcuts_update_success' => 'Налаштування ярликів оновлено!', - 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + 'shortcuts_overview_desc' => 'Керуйте комбінаціями клавіатур можна використовувати для навігації інтерфейсу системи користувача.', - 'notifications' => 'Notification Preferences', - 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', - 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', - 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', - 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', - 'notifications_save' => 'Save Preferences', - 'notifications_update_success' => 'Notification preferences have been updated!', - 'notifications_watched' => 'Watched & Ignored Items', - 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + 'notifications' => 'Налаштування сповіщень', + 'notifications_desc' => 'Контролюйте сповіщення по електронній пошті, які ви отримуєте, коли виконується певна активність у системі.', + 'notifications_opt_own_page_changes' => 'Повідомляти при змінах сторінок якими я володію', + 'notifications_opt_own_page_comments' => 'Повідомляти при коментарях на моїх сторінках', + 'notifications_opt_comment_replies' => 'Повідомляти про відповіді на мої коментарі', + 'notifications_save' => 'Зберегти налаштування', + 'notifications_update_success' => 'Налаштування сповіщень було оновлено!', + 'notifications_watched' => 'Переглянуті та ігноровані елементи', + 'notifications_watched_desc' => ' Нижче наведені предмети, які мають застосовані налаштування перегляду. Щоб оновити ваші налаштування для них, перегляньте елемент, а потім знайдіть параметри перегляду на бічній панелі.', - 'auth' => 'Access & Security', - 'auth_change_password' => 'Change Password', - 'auth_change_password_desc' => 'Change the password you use to log-in to the application. This must be at least 8 characters long.', - 'auth_change_password_success' => 'Password has been updated!', + 'auth' => 'Доступ і безпека', + 'auth_change_password' => 'Змінити пароль', + 'auth_change_password_desc' => 'Змініть пароль, який ви використовуєте для входу в програму. Це має бути щонайменше 8 символів.', + 'auth_change_password_success' => 'Пароль оновлено!', - 'profile' => 'Profile Details', - 'profile_desc' => 'Manage the details of your account which represents you to other users, in addition to details that are used for communication and system personalisation.', - 'profile_view_public' => 'View Public Profile', - 'profile_name_desc' => 'Configure your display name which will be visible to other users in the system through the activity you perform, and content you own.', - 'profile_email_desc' => 'This email will be used for notifications and, depending on active system authentication, system access.', - 'profile_email_no_permission' => 'Unfortunately you don\'t have permission to change your email address. If you want to change this, you\'d need to ask an administrator to change this for you.', - 'profile_avatar_desc' => 'Select an image which will be used to represent yourself to others in the system. Ideally this image should be square and about 256px in width and height.', - 'profile_admin_options' => 'Administrator Options', - 'profile_admin_options_desc' => 'Additional administrator-level options, like those to manage role assignments, can be found for your user account in the "Settings > Users" area of the application.', + 'profile' => 'Деталі профілю', + 'profile_desc' => 'Керуйте інформацією вашого облікового запису, що представляє вам інші користувачі, додатково до деталей, що використовуються для комунікації та особистості системи.', + 'profile_view_public' => 'Перегляд загального профілю', + 'profile_name_desc' => 'Налаштування відображуваного імена, які будуть видимі іншим користувачам в системі через здійснені вами дії та власний контент.', + 'profile_email_desc' => 'Цей email буде використовуватися для сповіщення і в залежності від активної автентифікації системи, доступу до системи.', + 'profile_email_no_permission' => 'На жаль, у вас немає дозволу на зміну адреси електронної пошти. Якщо ви хочете змінити це, ви маєте попросити адміністратора змінити це для вас.', + 'profile_avatar_desc' => 'Виберіть зображення, яке буде використовуватися для того, щоб представити себе іншим у системі. В ідеалі це зображення має бути квадратне і близько 256пікс в ширину і висоту.', + 'profile_admin_options' => 'Параметри адміністратора', + 'profile_admin_options_desc' => 'Додаткові параметри на рівні адміністратора, так як і для управління призначеннями роль, ви можете знайти для вашого облікового запису в області "Налаштування> Користувачі" додатку.', - 'delete_account' => 'Delete Account', - 'delete_my_account' => 'Delete My Account', - 'delete_my_account_desc' => 'This will fully delete your user account from the system. You will not be able to recover this account or revert this action. Content you\'ve created, such as created pages and uploaded images, will remain.', - 'delete_my_account_warning' => 'Are you sure you want to delete your account?', + 'delete_account' => 'Видалити обліковий запис', + 'delete_my_account' => 'Видалити мій обліковий запис', + 'delete_my_account_desc' => 'Це повністю видалить ваш обліковий запис з системи. Ви не зможете відновити або скасувати цю дію. Контент, який Ви створили, наприклад створені сторінки і вивантажені зображення, залишиться.', + 'delete_my_account_warning' => 'Ви впевнені, що хочете видалити свій обліковий запис?', ]; diff --git a/lang/uk/settings.php b/lang/uk/settings.php index b3323f33b..bd1a20ec8 100644 --- a/lang/uk/settings.php +++ b/lang/uk/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/uz/entities.php b/lang/uz/entities.php index 9385670a4..0effadf46 100644 --- a/lang/uz/entities.php +++ b/lang/uz/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => ':timeLength da yangilangan', 'meta_updated_name' => ':user tomonidan :timeLength da yangilangan', 'meta_owned_name' => 'Muallif: foydalanuvchi', - 'meta_reference_page_count' => ':count sahifasida havola qilingan :count sahifalarida havola qilingan', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Ob\'ektni tanlash', 'entity_select_lack_permission' => 'Sizda bu elementni tanlash uchun kerakli ruxsatlar yo‘q', 'images' => 'Rasmlar', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Kitobni tahrirlash: kitob nomi', 'books_form_book_name' => 'Kitob nomi', 'books_save' => 'Kitobni saqlash', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Kitob ruxsatnomalari', 'books_permissions_updated' => 'Kitob ruxsatnomalari yangilandi', 'books_empty_contents' => 'Ushbu kitob uchun hech qanday sahifa yoki bob yaratilmagan.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Qoralama sahifani oʻchirish', 'pages_delete_success' => 'Sahifa oʻchirildi', 'pages_delete_draft_success' => 'Qoralama sahifa oʻchirildi', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Haqiqatan ham bu sahifani oʻchirib tashlamoqchimisiz?', 'pages_delete_draft_confirm' => 'Haqiqatan ham bu qoralama sahifani oʻchirib tashlamoqchimisiz?', 'pages_editing_named' => 'Sahifani tahrirlash :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'Ma\'lumotnomalar', 'references_none' => 'Bu elementga kuzatilgan havolalar mavjud emas.', - 'references_to_desc' => 'Quyida ushbu elementga bog\'langan tizimdagi barcha ma\'lum sahifalar ko\'rsatilgan.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Tomosha qiling', diff --git a/lang/uz/errors.php b/lang/uz/errors.php index 3f1c11165..e37ca8bbe 100644 --- a/lang/uz/errors.php +++ b/lang/uz/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'LDAP PHP kengaytmasi oʻrnatilmagan', 'ldap_cannot_connect' => 'Ldap serveriga ulanib boʻlmadi, Dastlabki ulanish amalga oshmadi', 'saml_already_logged_in' => 'Allaqachon tizimga kirgan', - 'saml_user_not_registered' => 'Foydalanuvchi :name roʻyxatdan oʻtmagan va avtomatik roʻyxatdan oʻtish oʻchirilgan', 'saml_no_email_address' => 'Tashqi autentifikatsiya tizimi tomonidan taqdim etilgan maʼlumotlarda ushbu foydalanuvchi uchun elektron pochta manzili topilmadi', 'saml_invalid_response_id' => 'Tashqi autentifikatsiya tizimidagi so‘rov ushbu ilova tomonidan boshlangan jarayon tomonidan tan olinmaydi. Kirishdan keyin orqaga qaytish bu muammoga olib kelishi mumkin.', 'saml_fail_authed' => ':tizim yordamida tizimga kirish amalga oshmadi, tizim muvaffaqiyatli avtorizatsiyani taqdim etmadi', diff --git a/lang/uz/notifications.php b/lang/uz/notifications.php index 46e636c6f..bec9b3925 100644 --- a/lang/uz/notifications.php +++ b/lang/uz/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'Xabarnomalar koʻp boʻlishining oldini olish uchun bir muncha vaqt oʻsha muharrir tomonidan ushbu sahifaga keyingi tahrirlar haqida bildirishnomalar yuborilmaydi.', 'detail_page_name' => 'Sahifa nomi:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Izoh egasi:', 'detail_comment' => 'Izoh:', 'detail_created_by' => 'Tomonidan yaratildi:', diff --git a/lang/uz/settings.php b/lang/uz/settings.php index 2286ee14d..f0c23e445 100644 --- a/lang/uz/settings.php +++ b/lang/uz/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/vi/entities.php b/lang/vi/entities.php index b98361fd1..5740a3483 100644 --- a/lang/vi/entities.php +++ b/lang/vi/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Được cập nhật :timeLength', 'meta_updated_name' => 'Được cập nhật :timeLength bởi :user', 'meta_owned_name' => 'Được sở hữu bởi :user', - 'meta_reference_page_count' => 'Được tham chiếu trên :count page|Được tham chiếu trên :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => 'Chọn thực thể', 'entity_select_lack_permission' => 'Bạn không có quyền để chọn mục này', 'images' => 'Ảnh', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => 'Sửa sách :bookName', 'books_form_book_name' => 'Tên sách', 'books_save' => 'Lưu sách', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Các quyền của cuốn sách', 'books_permissions_updated' => 'Các quyền của cuốn sách đã được cập nhật', 'books_empty_contents' => 'Không có trang hay chương nào được tạo cho cuốn sách này.', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => 'Xóa Trang Nháp', 'pages_delete_success' => 'Đã xóa Trang', 'pages_delete_draft_success' => 'Đã xóa trang Nháp', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Bạn có chắc chắn muốn xóa trang này?', 'pages_delete_draft_confirm' => 'Bạn có chắc chắn muốn xóa trang nháp này?', 'pages_editing_named' => 'Đang chỉnh sửa Trang :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/vi/errors.php b/lang/vi/errors.php index 4fe7f3829..1b69b5818 100644 --- a/lang/vi/errors.php +++ b/lang/vi/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => 'Tiện ích mở rộng LDAP PHP chưa được cài đặt', 'ldap_cannot_connect' => 'Không thể kết nối đến máy chủ LDAP, mở đầu kết nối thất bại', 'saml_already_logged_in' => 'Đã đăng nhập', - 'saml_user_not_registered' => 'Người dùng :name chưa được đăng ký và tự động đăng ký đang bị tắt', 'saml_no_email_address' => 'Không tìm thấy địa chỉ email cho người dùng này trong dữ liệu được cung cấp bới hệ thống xác thực ngoài', 'saml_invalid_response_id' => 'Yêu cầu từ hệ thống xác thực bên ngoài không được nhận diện bởi quy trình chạy cho ứng dụng này. Điều hướng trở lại sau khi đăng nhập có thể đã gây ra vấn đề này.', 'saml_fail_authed' => 'Đăng nhập sử dụng :system thất bại, hệ thống không cung cấp được sự xác thực thành công', diff --git a/lang/vi/notifications.php b/lang/vi/notifications.php index 5539ae9a9..1afd23f1d 100644 --- a/lang/vi/notifications.php +++ b/lang/vi/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/vi/settings.php b/lang/vi/settings.php index 4bda6fcee..929f1f7b2 100644 --- a/lang/vi/settings.php +++ b/lang/vi/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/zh_CN/entities.php b/lang/zh_CN/entities.php index a4bd4664a..a0c32fa4f 100644 --- a/lang/zh_CN/entities.php +++ b/lang/zh_CN/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => '更新于 :timeLength', 'meta_updated_name' => '由 :user 更新于 :timeLength', 'meta_owned_name' => '拥有者 :user', - 'meta_reference_page_count' => '被 :count 个页面引用|被 :count 个页面引用', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => '选择项目', 'entity_select_lack_permission' => '您没有选择此项目所需的权限', 'images' => '图片', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => '编辑图书「:bookName」', 'books_form_book_name' => '书名', 'books_save' => '保存图书', + 'books_default_template' => '默认页面模板', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => '选择模板页面', 'books_permissions' => '图书权限', 'books_permissions_updated' => '图书权限已更新', 'books_empty_contents' => '本书目前没有页面或章节。', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => '删除草稿页面', 'pages_delete_success' => '页面已删除', 'pages_delete_draft_success' => '草稿页面已删除', + 'pages_delete_warning_template' => '此页面是当前的书籍默认页面模板。删除此页面后,将不再为这些书籍分配默认页面模板。', 'pages_delete_confirm' => '您确定要删除此页面吗?', 'pages_delete_draft_confirm' => '您确定要删除此草稿页面吗?', 'pages_editing_named' => '正在编辑页面“:pageName”', @@ -405,7 +409,7 @@ return [ // References 'references' => '引用', 'references_none' => '没有跟踪到对此项目的引用。', - 'references_to_desc' => '下面显示的是系统中所有已知链接到这个项目的页面。', + 'references_to_desc' => '下方列出了系统中链接到此项目的所有已知内容。', // Watch Options 'watch' => '关注', diff --git a/lang/zh_CN/errors.php b/lang/zh_CN/errors.php index 42e2378d9..3dbf3c490 100644 --- a/lang/zh_CN/errors.php +++ b/lang/zh_CN/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => '未安装LDAP PHP扩展程序', 'ldap_cannot_connect' => '无法连接到ldap服务器,初始连接失败', 'saml_already_logged_in' => '您已经登陆了', - 'saml_user_not_registered' => '用户 :name 未注册且自动注册功能已被禁用', 'saml_no_email_address' => '无法找到有效Email地址,此用户数据由外部身份验证系统托管', 'saml_invalid_response_id' => '来自外部身份验证系统的请求没有被本应用程序认证,在登录后返回上一页可能会导致此问题。', 'saml_fail_authed' => '使用 :system 登录失败,登录系统未返回成功登录授权信息。', diff --git a/lang/zh_CN/notifications.php b/lang/zh_CN/notifications.php index 363ba134b..52c9822bc 100644 --- a/lang/zh_CN/notifications.php +++ b/lang/zh_CN/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => '为了防止出现大量通知,一段时间内您不会收到同一编辑者再次编辑本页面的通知。', 'detail_page_name' => '页面名称:', + 'detail_page_path' => '页面路径:', 'detail_commenter' => '评论者:', 'detail_comment' => '评论:', 'detail_created_by' => '创建者:', diff --git a/lang/zh_CN/settings.php b/lang/zh_CN/settings.php index b6daf33f4..4bf868af6 100644 --- a/lang/zh_CN/settings.php +++ b/lang/zh_CN/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => 'עברית', 'hr' => 'Hrvatski', diff --git a/lang/zh_TW/entities.php b/lang/zh_TW/entities.php index 13d4acefd..a7eb9b151 100644 --- a/lang/zh_TW/entities.php +++ b/lang/zh_TW/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => '更新於 :timeLength', 'meta_updated_name' => '由 :user 更新於 :timeLength', 'meta_owned_name' => ':user 所擁有', - 'meta_reference_page_count' => 'Referenced on :count page|Referenced on :count pages', + 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', 'entity_select' => '選取項目', 'entity_select_lack_permission' => 'You don\'t have the required permissions to select this item', 'images' => '圖片', @@ -132,6 +132,9 @@ return [ 'books_edit_named' => '編輯書本「:bookName」', 'books_form_book_name' => '書本名稱', 'books_save' => '儲存書本', + 'books_default_template' => 'Default Page Template', + 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_select' => 'Select a template page', 'books_permissions' => '書本權限', 'books_permissions_updated' => '書本權限已更新', 'books_empty_contents' => '本書目前沒有頁面或章節。', @@ -204,6 +207,7 @@ return [ 'pages_delete_draft' => '刪除草稿頁面', 'pages_delete_success' => '頁面已刪除', 'pages_delete_draft_success' => '草稿頁面已刪除', + 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => '您確定要刪除此頁面嗎?', 'pages_delete_draft_confirm' => '您確定要刪除此草稿頁面嗎?', 'pages_editing_named' => '正在編輯頁面 :pageName', @@ -405,7 +409,7 @@ return [ // References 'references' => 'References', 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Shown below are all the known pages in the system that link to this item.', + 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options 'watch' => 'Watch', diff --git a/lang/zh_TW/errors.php b/lang/zh_TW/errors.php index 19ae6dda0..044377a75 100644 --- a/lang/zh_TW/errors.php +++ b/lang/zh_TW/errors.php @@ -19,7 +19,6 @@ return [ 'ldap_extension_not_installed' => '未安裝 PHP 的 LDAP 擴充程式', 'ldap_cannot_connect' => '無法連線至 LDAP 伺服器,初始化連線失敗', 'saml_already_logged_in' => '已登入', - 'saml_user_not_registered' => '使用者 :name 未註冊,並已停用自動註冊', 'saml_no_email_address' => '在外部認證系統提供的資料中找不到該使用者的電子郵件地址', 'saml_invalid_response_id' => '此應用程式啟動的處理程序無法識別來自外部認證系統的請求。登入後回上一頁可能會造成此問題。', 'saml_fail_authed' => '使用 :system 登入失敗,系統未提供成功的授權', diff --git a/lang/zh_TW/notifications.php b/lang/zh_TW/notifications.php index 5539ae9a9..1afd23f1d 100644 --- a/lang/zh_TW/notifications.php +++ b/lang/zh_TW/notifications.php @@ -13,6 +13,7 @@ return [ 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', 'detail_page_name' => 'Page Name:', + 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', 'detail_comment' => 'Comment:', 'detail_created_by' => 'Created By:', diff --git a/lang/zh_TW/settings.php b/lang/zh_TW/settings.php index fd1656bee..ee05b2024 100644 --- a/lang/zh_TW/settings.php +++ b/lang/zh_TW/settings.php @@ -296,6 +296,7 @@ return [ 'et' => 'Eesti keel', 'eu' => 'Euskara', 'fa' => 'فارسی', + 'fi' => 'Suomi', 'fr' => 'Français', 'he' => '希伯來語', 'hr' => 'Hrvatski', From b191d8f99fdb9998cf07a27f355c1595ca6f7340 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Fri, 29 Dec 2023 12:08:39 +0000 Subject: [PATCH 022/124] Updated translator attribution before release v23.12 --- .github/translators.txt | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/translators.txt b/.github/translators.txt index 6bb9de259..a27717f94 100644 --- a/.github/translators.txt +++ b/.github/translators.txt @@ -177,7 +177,7 @@ Alexander Predl (Harveyhase68) :: German Rem (Rem9000) :: Dutch Michał Stelmach (stelmach-web) :: Polish arniom :: French -REMOVED_USER :: French; Dutch; Turkish; +REMOVED_USER :: French; Dutch; Portuguese, Brazilian; Portuguese; Turkish; 林祖年 (contagion) :: Chinese Traditional Siamak Guodarzi (siamakgoudarzi88) :: Persian Lis Maestrelo (lismtrl) :: Portuguese, Brazilian @@ -371,3 +371,18 @@ LameeQS :: Latvian Sorin T. (trimbitassorin) :: Romanian poesty :: Chinese Simplified balmag :: Hungarian +Antti-Jussi Nygård (ajnyga) :: Finnish +Eduard Ereza Martínez (Ereza) :: Catalan +Jabir Lang (amar.almrad) :: Arabic +Jaroslav Koblizek (foretix) :: Czech; French +Wiktor Adamczyk (adamczyk.wiktor) :: Polish +Abdulmajeed Alshuaibi (4Majeed) :: Arabic +NotSmartZakk :: Czech +HyoungMin Lee (ddokkaebi) :: Korean +Dasferco :: Chinese Simplified +Marcus Teräs (mteras) :: Finnish +Serkan Yardim (serkanzz) :: Turkish +Y (cnsr) :: Ukrainian +ZY ZV (vy0b0x) :: Chinese Simplified +diegobenitez :: Spanish +Marc Hagen (MarcHagen) :: Dutch From 70bfebcd7c9396c9d17757ec57a584371dce2e9f Mon Sep 17 00:00:00 2001 From: Sascha Date: Mon, 1 Jan 2024 21:58:49 +0100 Subject: [PATCH 023/124] Added Default Templates for Chapters --- .../Controllers/ChapterController.php | 14 ++++---- app/Entities/Controllers/PageController.php | 9 +++-- app/Entities/Models/Chapter.php | 11 ++++++ app/Entities/Repos/ChapterRepo.php | 34 +++++++++++++++++++ app/Entities/Repos/PageRepo.php | 8 ++++- app/Entities/Tools/TrashCan.php | 6 ++++ ...04542_add_default_template_to_chapters.php | 32 +++++++++++++++++ lang/en/entities.php | 3 ++ resources/views/chapters/parts/form.blade.php | 23 +++++++++++++ 9 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 database/migrations/2024_01_01_104542_add_default_template_to_chapters.php diff --git a/app/Entities/Controllers/ChapterController.php b/app/Entities/Controllers/ChapterController.php index 28ad35fa4..00616888a 100644 --- a/app/Entities/Controllers/ChapterController.php +++ b/app/Entities/Controllers/ChapterController.php @@ -49,9 +49,10 @@ class ChapterController extends Controller public function store(Request $request, string $bookSlug) { $validated = $this->validate($request, [ - 'name' => ['required', 'string', 'max:255'], - 'description_html' => ['string', 'max:2000'], - 'tags' => ['array'], + 'name' => ['required', 'string', 'max:255'], + 'description_html' => ['string', 'max:2000'], + 'tags' => ['array'], + 'default_template_id' => ['nullable', 'integer'], ]); $book = Book::visible()->where('slug', '=', $bookSlug)->firstOrFail(); @@ -111,9 +112,10 @@ class ChapterController extends Controller public function update(Request $request, string $bookSlug, string $chapterSlug) { $validated = $this->validate($request, [ - 'name' => ['required', 'string', 'max:255'], - 'description_html' => ['string', 'max:2000'], - 'tags' => ['array'], + 'name' => ['required', 'string', 'max:255'], + 'description_html' => ['string', 'max:2000'], + 'tags' => ['array'], + 'default_template_id' => ['nullable', 'integer'], ]); $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); diff --git a/app/Entities/Controllers/PageController.php b/app/Entities/Controllers/PageController.php index adafcdc7b..74dd4f531 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -6,6 +6,7 @@ use BookStack\Activity\Models\View; use BookStack\Activity\Tools\CommentTree; use BookStack\Activity\Tools\UserEntityWatchOptions; use BookStack\Entities\Models\Book; +use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Page; use BookStack\Entities\Repos\PageRepo; use BookStack\Entities\Tools\BookContents; @@ -259,7 +260,9 @@ class PageController extends Controller $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); $this->checkOwnablePermission('page-delete', $page); $this->setPageTitle(trans('entities.pages_delete_named', ['pageName' => $page->getShortName()])); - $usedAsTemplate = Book::query()->where('default_template_id', '=', $page->id)->count() > 0; + $usedAsTemplate = + Book::query()->where('default_template_id', '=', $page->id)->count() > 0 || + Chapter::query()->where('default_template_id', '=', $page->id)->count() > 0; return view('pages.delete', [ 'book' => $page->book, @@ -279,7 +282,9 @@ class PageController extends Controller $page = $this->pageRepo->getById($pageId); $this->checkOwnablePermission('page-update', $page); $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName' => $page->getShortName()])); - $usedAsTemplate = Book::query()->where('default_template_id', '=', $page->id)->count() > 0; + $usedAsTemplate = + Book::query()->where('default_template_id', '=', $page->id)->count() > 0 || + Chapter::query()->where('default_template_id', '=', $page->id)->count() > 0; return view('pages.delete', [ 'book' => $page->book, diff --git a/app/Entities/Models/Chapter.php b/app/Entities/Models/Chapter.php index f30d77b5c..d3a710111 100644 --- a/app/Entities/Models/Chapter.php +++ b/app/Entities/Models/Chapter.php @@ -2,6 +2,7 @@ namespace BookStack\Entities\Models; +use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Support\Collection; @@ -11,6 +12,8 @@ use Illuminate\Support\Collection; * * @property Collection $pages * @property string $description + * @property ?int $default_template_id + * @property ?Page $defaultTemplate */ class Chapter extends BookChild { @@ -48,6 +51,14 @@ class Chapter extends BookChild return url('/' . implode('/', $parts)); } + /** + * Get the Page that is used as default template for newly created pages within this Chapter. + */ + public function defaultTemplate(): BelongsTo + { + return $this->belongsTo(Page::class, 'default_template_id'); + } + /** * Get the visible pages in this chapter. */ diff --git a/app/Entities/Repos/ChapterRepo.php b/app/Entities/Repos/ChapterRepo.php index 977193d85..9534a4060 100644 --- a/app/Entities/Repos/ChapterRepo.php +++ b/app/Entities/Repos/ChapterRepo.php @@ -4,6 +4,7 @@ namespace BookStack\Entities\Repos; use BookStack\Activity\ActivityType; use BookStack\Entities\Models\Book; +use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Tools\BookContents; @@ -46,6 +47,7 @@ class ChapterRepo $chapter->book_id = $parentBook->id; $chapter->priority = (new BookContents($parentBook))->getLastPriority() + 1; $this->baseRepo->create($chapter, $input); + $this->updateChapterDefaultTemplate($chapter, intval($input['default_template_id'] ?? null)); Activity::add(ActivityType::CHAPTER_CREATE, $chapter); return $chapter; @@ -57,6 +59,11 @@ class ChapterRepo public function update(Chapter $chapter, array $input): Chapter { $this->baseRepo->update($chapter, $input); + + if (array_key_exists('default_template_id', $input)) { + $this->updateChapterDefaultTemplate($chapter, intval($input['default_template_id'])); + } + Activity::add(ActivityType::CHAPTER_UPDATE, $chapter); return $chapter; @@ -101,6 +108,33 @@ class ChapterRepo return $parent; } + /** + * Update the default page template used for this chapter. + * Checks that, if changing, the provided value is a valid template and the user + * has visibility of the provided page template id. + */ + protected function updateChapterDefaultTemplate(Chapter $chapter, int $templateId): void + { + $changing = $templateId !== intval($chapter->default_template_id); + if (!$changing) { + return; + } + + if ($templateId === 0) { + $chapter->default_template_id = null; + $chapter->save(); + return; + } + + $templateExists = Page::query()->visible() + ->where('template', '=', true) + ->where('id', '=', $templateId) + ->exists(); + + $chapter->default_template_id = $templateExists ? $templateId : null; + $chapter->save(); + } + /** * Find a page parent entity via an identifier string in the format: * {type}:{id} diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index 7b14ea7d2..67c4b2225 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -136,7 +136,13 @@ class PageRepo $page->book_id = $parent->id; } - $defaultTemplate = $page->book->defaultTemplate; + // check for chapter + if ($page->chapter_id) { + $defaultTemplate = $page->chapter->defaultTemplate; + } else { + $defaultTemplate = $page->book->defaultTemplate; + } + if ($defaultTemplate && userCan('view', $defaultTemplate)) { $page->forceFill([ 'html' => $defaultTemplate->html, diff --git a/app/Entities/Tools/TrashCan.php b/app/Entities/Tools/TrashCan.php index b25103985..e5bcfe71a 100644 --- a/app/Entities/Tools/TrashCan.php +++ b/app/Entities/Tools/TrashCan.php @@ -208,6 +208,12 @@ class TrashCan $page->forceDelete(); + // Remove chapter template usages + Chapter::query()->where('default_template_id', '=', $page->id) + ->update(['default_template_id' => null]); + + $page->forceDelete(); + return 1; } diff --git a/database/migrations/2024_01_01_104542_add_default_template_to_chapters.php b/database/migrations/2024_01_01_104542_add_default_template_to_chapters.php new file mode 100644 index 000000000..5e9ea1de3 --- /dev/null +++ b/database/migrations/2024_01_01_104542_add_default_template_to_chapters.php @@ -0,0 +1,32 @@ +integer('default_template_id')->nullable()->default(null); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('chapters', function (Blueprint $table) { + $table->dropColumn('default_template_id'); + }); + } +} diff --git a/lang/en/entities.php b/lang/en/entities.php index f1f915544..4ab9de47d 100644 --- a/lang/en/entities.php +++ b/lang/en/entities.php @@ -192,6 +192,9 @@ return [ 'chapters_permissions_success' => 'Chapter Permissions Updated', 'chapters_search_this' => 'Search this chapter', 'chapter_sort_book' => 'Sort Book', + 'chapter_default_template' => 'Default Page Template', + 'chapter_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this chapter. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'chapter_default_template_select' => 'Select a template page', // Pages 'page' => 'Page', diff --git a/resources/views/chapters/parts/form.blade.php b/resources/views/chapters/parts/form.blade.php index c6052c93a..ea7f84bc8 100644 --- a/resources/views/chapters/parts/form.blade.php +++ b/resources/views/chapters/parts/form.blade.php @@ -22,6 +22,29 @@
+
+ +
+
+

+ {{ trans('entities.chapter_default_template_explain') }} +

+ +
+ @include('form.page-picker', [ + 'name' => 'default_template_id', + 'placeholder' => trans('entities.chapter_default_template_select'), + 'value' => $chapter->default_template_id ?? null, + 'selectorEndpoint' => '/search/entity-selector-templates', + ]) +
+
+ +
+
+
{{ trans('common.cancel') }} From b4d9029dc301f73f0e87026b8eff78cf2cda6164 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 7 Jan 2024 14:03:13 +0000 Subject: [PATCH 024/124] Range requests: Extracted stream output handling to new class --- app/Http/DownloadResponseFactory.php | 36 ++++-------- app/Http/RangeSupportedStream.php | 57 +++++++++++++++++++ app/Uploads/AttachmentService.php | 10 +++- .../Controllers/AttachmentController.php | 5 +- 4 files changed, 80 insertions(+), 28 deletions(-) create mode 100644 app/Http/RangeSupportedStream.php diff --git a/app/Http/DownloadResponseFactory.php b/app/Http/DownloadResponseFactory.php index 20032f525..f8c10165c 100644 --- a/app/Http/DownloadResponseFactory.php +++ b/app/Http/DownloadResponseFactory.php @@ -9,11 +9,9 @@ use Symfony\Component\HttpFoundation\StreamedResponse; class DownloadResponseFactory { - protected Request $request; - - public function __construct(Request $request) - { - $this->request = $request; + public function __construct( + protected Request $request + ) { } /** @@ -27,19 +25,11 @@ class DownloadResponseFactory /** * Create a response that forces a download, from a given stream of content. */ - public function streamedDirectly($stream, string $fileName): StreamedResponse + public function streamedDirectly($stream, string $fileName, int $fileSize): StreamedResponse { - return response()->stream(function () use ($stream) { - - // End & flush the output buffer, if we're in one, otherwise we still use memory. - // Output buffer may or may not exist depending on PHP `output_buffering` setting. - // Ignore in testing since output buffers are used to gather a response. - if (!empty(ob_get_status()) && !app()->runningUnitTests()) { - ob_end_clean(); - } - - fpassthru($stream); - fclose($stream); + $rangeStream = new RangeSupportedStream($stream, $fileSize, $this->request->headers); + return response()->stream(function () use ($rangeStream) { + $rangeStream->outputAndClose(); }, 200, $this->getHeaders($fileName)); } @@ -48,15 +38,13 @@ class DownloadResponseFactory * correct for the file, in a way so the browser can show the content in browser, * for a given content stream. */ - public function streamedInline($stream, string $fileName): StreamedResponse + public function streamedInline($stream, string $fileName, int $fileSize): StreamedResponse { - $sniffContent = fread($stream, 2000); - $mime = (new WebSafeMimeSniffer())->sniff($sniffContent); + $rangeStream = new RangeSupportedStream($stream, $fileSize, $this->request->headers); + $mime = $rangeStream->sniffMime(); - return response()->stream(function () use ($sniffContent, $stream) { - echo $sniffContent; - fpassthru($stream); - fclose($stream); + return response()->stream(function () use ($rangeStream) { + $rangeStream->outputAndClose(); }, 200, $this->getHeaders($fileName, $mime)); } diff --git a/app/Http/RangeSupportedStream.php b/app/Http/RangeSupportedStream.php new file mode 100644 index 000000000..dc3105035 --- /dev/null +++ b/app/Http/RangeSupportedStream.php @@ -0,0 +1,57 @@ +fileSize); + $this->sniffContent = fread($this->stream, $offset); + + return (new WebSafeMimeSniffer())->sniff($this->sniffContent); + } + + /** + * Output the current stream to stdout before closing out the stream. + */ + public function outputAndClose(): void + { + // End & flush the output buffer, if we're in one, otherwise we still use memory. + // Output buffer may or may not exist depending on PHP `output_buffering` setting. + // Ignore in testing since output buffers are used to gather a response. + if (!empty(ob_get_status()) && !app()->runningUnitTests()) { + ob_end_clean(); + } + + $outStream = fopen('php://output', 'w'); + $offset = 0; + + if (!empty($this->sniffContent)) { + fwrite($outStream, $this->sniffContent); + $offset = strlen($this->sniffContent); + } + + $toWrite = $this->fileSize - $offset; + stream_copy_to_stream($this->stream, $outStream, $toWrite); + fpassthru($this->stream); + + fclose($this->stream); + fclose($outStream); + } +} diff --git a/app/Uploads/AttachmentService.php b/app/Uploads/AttachmentService.php index ddabec09f..72f78e347 100644 --- a/app/Uploads/AttachmentService.php +++ b/app/Uploads/AttachmentService.php @@ -66,8 +66,6 @@ class AttachmentService /** * Stream an attachment from storage. * - * @throws FileNotFoundException - * * @return resource|null */ public function streamAttachmentFromStorage(Attachment $attachment) @@ -75,6 +73,14 @@ class AttachmentService return $this->getStorageDisk()->readStream($this->adjustPathForStorageDisk($attachment->path)); } + /** + * Read the file size of an attachment from storage, in bytes. + */ + public function getAttachmentFileSize(Attachment $attachment): int + { + return $this->getStorageDisk()->size($this->adjustPathForStorageDisk($attachment->path)); + } + /** * Store a new attachment upon user upload. * diff --git a/app/Uploads/Controllers/AttachmentController.php b/app/Uploads/Controllers/AttachmentController.php index 92f23465d..e61c10338 100644 --- a/app/Uploads/Controllers/AttachmentController.php +++ b/app/Uploads/Controllers/AttachmentController.php @@ -226,12 +226,13 @@ class AttachmentController extends Controller $fileName = $attachment->getFileName(); $attachmentStream = $this->attachmentService->streamAttachmentFromStorage($attachment); + $attachmentSize = $this->attachmentService->getAttachmentFileSize($attachment); if ($request->get('open') === 'true') { - return $this->download()->streamedInline($attachmentStream, $fileName); + return $this->download()->streamedInline($attachmentStream, $fileName, $attachmentSize); } - return $this->download()->streamedDirectly($attachmentStream, $fileName); + return $this->download()->streamedDirectly($attachmentStream, $fileName, $attachmentSize); } /** From d94762549a3ef364d4486a27f1585122f60c10ec Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 7 Jan 2024 20:34:03 +0000 Subject: [PATCH 025/124] Range requests: Added basic HTTP range support --- app/Http/DownloadResponseFactory.php | 28 ++++---- app/Http/RangeSupportedStream.php | 95 +++++++++++++++++++++++++--- 2 files changed, 103 insertions(+), 20 deletions(-) diff --git a/app/Http/DownloadResponseFactory.php b/app/Http/DownloadResponseFactory.php index f8c10165c..f29aaa2e4 100644 --- a/app/Http/DownloadResponseFactory.php +++ b/app/Http/DownloadResponseFactory.php @@ -2,7 +2,6 @@ namespace BookStack\Http; -use BookStack\Util\WebSafeMimeSniffer; use Illuminate\Http\Request; use Illuminate\Http\Response; use Symfony\Component\HttpFoundation\StreamedResponse; @@ -19,7 +18,7 @@ class DownloadResponseFactory */ public function directly(string $content, string $fileName): Response { - return response()->make($content, 200, $this->getHeaders($fileName)); + return response()->make($content, 200, $this->getHeaders($fileName, strlen($content))); } /** @@ -27,10 +26,13 @@ class DownloadResponseFactory */ public function streamedDirectly($stream, string $fileName, int $fileSize): StreamedResponse { - $rangeStream = new RangeSupportedStream($stream, $fileSize, $this->request->headers); - return response()->stream(function () use ($rangeStream) { - $rangeStream->outputAndClose(); - }, 200, $this->getHeaders($fileName)); + $rangeStream = new RangeSupportedStream($stream, $fileSize, $this->request); + $headers = array_merge($this->getHeaders($fileName, $fileSize), $rangeStream->getResponseHeaders()); + return response()->stream( + fn() => $rangeStream->outputAndClose(), + $rangeStream->getResponseStatus(), + $headers, + ); } /** @@ -40,24 +42,28 @@ class DownloadResponseFactory */ public function streamedInline($stream, string $fileName, int $fileSize): StreamedResponse { - $rangeStream = new RangeSupportedStream($stream, $fileSize, $this->request->headers); + $rangeStream = new RangeSupportedStream($stream, $fileSize, $this->request); $mime = $rangeStream->sniffMime(); + $headers = array_merge($this->getHeaders($fileName, $fileSize, $mime), $rangeStream->getResponseHeaders()); - return response()->stream(function () use ($rangeStream) { - $rangeStream->outputAndClose(); - }, 200, $this->getHeaders($fileName, $mime)); + return response()->stream( + fn() => $rangeStream->outputAndClose(), + $rangeStream->getResponseStatus(), + $headers, + ); } /** * Get the common headers to provide for a download response. */ - protected function getHeaders(string $fileName, string $mime = 'application/octet-stream'): array + protected function getHeaders(string $fileName, int $fileSize, string $mime = 'application/octet-stream'): array { $disposition = ($mime === 'application/octet-stream') ? 'attachment' : 'inline'; $downloadName = str_replace('"', '', $fileName); return [ 'Content-Type' => $mime, + 'Content-Length' => $fileSize, 'Content-Disposition' => "{$disposition}; filename=\"{$downloadName}\"", 'X-Content-Type-Options' => 'nosniff', ]; diff --git a/app/Http/RangeSupportedStream.php b/app/Http/RangeSupportedStream.php index dc3105035..b51d4a549 100644 --- a/app/Http/RangeSupportedStream.php +++ b/app/Http/RangeSupportedStream.php @@ -3,17 +3,30 @@ namespace BookStack\Http; use BookStack\Util\WebSafeMimeSniffer; -use Symfony\Component\HttpFoundation\HeaderBag; +use Illuminate\Http\Request; +/** + * Helper wrapper for range-based stream response handling. + * Much of this used symfony/http-foundation as a reference during build. + * URL: https://github.com/symfony/http-foundation/blob/v6.0.20/BinaryFileResponse.php + * License: MIT license, Copyright (c) Fabien Potencier. + */ class RangeSupportedStream { protected string $sniffContent; + protected array $responseHeaders; + protected int $responseStatus = 200; + + protected int $responseLength = 0; + protected int $responseOffset = 0; public function __construct( protected $stream, protected int $fileSize, - protected HeaderBag $requestHeaders, + Request $request, ) { + $this->responseLength = $this->fileSize; + $this->parseRequest($request); } /** @@ -40,18 +53,82 @@ class RangeSupportedStream } $outStream = fopen('php://output', 'w'); - $offset = 0; + $sniffOffset = strlen($this->sniffContent); - if (!empty($this->sniffContent)) { - fwrite($outStream, $this->sniffContent); - $offset = strlen($this->sniffContent); + if (!empty($this->sniffContent) && $this->responseOffset < $sniffOffset) { + $sniffOutput = substr($this->sniffContent, $this->responseOffset, min($sniffOffset, $this->responseLength)); + fwrite($outStream, $sniffOutput); + } else if ($this->responseOffset !== 0) { + fseek($this->stream, $this->responseOffset); } - $toWrite = $this->fileSize - $offset; - stream_copy_to_stream($this->stream, $outStream, $toWrite); - fpassthru($this->stream); + stream_copy_to_stream($this->stream, $outStream, $this->responseLength); fclose($this->stream); fclose($outStream); } + + public function getResponseHeaders(): array + { + return $this->responseHeaders; + } + + public function getResponseStatus(): int + { + return $this->responseStatus; + } + + protected function parseRequest(Request $request): void + { + $this->responseHeaders['Accept-Ranges'] = $request->isMethodSafe() ? 'bytes' : 'none'; + + $range = $this->getRangeFromRequest($request); + if ($range) { + [$start, $end] = $range; + if ($start < 0 || $start > $end) { + $this->responseStatus = 416; + $this->responseHeaders['Content-Range'] = sprintf('bytes */%s', $this->fileSize); + } elseif ($end - $start < $this->fileSize - 1) { + $this->responseLength = $end < $this->fileSize ? $end - $start + 1 : -1; + $this->responseOffset = $start; + $this->responseStatus = 206; + $this->responseHeaders['Content-Range'] = sprintf('bytes %s-%s/%s', $start, $end, $this->fileSize); + $this->responseHeaders['Content-Length'] = $end - $start + 1; + } + } + + if ($request->isMethod('HEAD')) { + $this->responseLength = 0; + } + } + + protected function getRangeFromRequest(Request $request): ?array + { + $range = $request->headers->get('Range'); + if (!$range || !$request->isMethod('GET') || !str_starts_with($range, 'bytes=')) { + return null; + } + + if ($request->headers->has('If-Range')) { + return null; + } + + [$start, $end] = explode('-', substr($range, 6), 2) + [0]; + + $end = ('' === $end) ? $this->fileSize - 1 : (int) $end; + + if ('' === $start) { + $start = $this->fileSize - $end; + $end = $this->fileSize - 1; + } else { + $start = (int) $start; + } + + if ($start > $end) { + return null; + } + + $end = min($end, $this->fileSize - 1); + return [$start, $end]; + } } From afbbcafd44839e09ae9464ce6f98a04664b82006 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 10 Jan 2024 14:33:49 +0000 Subject: [PATCH 026/124] Readme: Updates sponsor list --- readme.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/readme.md b/readme.md index b6174f412..a32e22931 100644 --- a/readme.md +++ b/readme.md @@ -43,29 +43,29 @@ Big thanks to these companies for supporting the project. [Project donation details](https://www.bookstackapp.com/donate/) - [GitHub Sponsors Page](https://github.com/sponsors/ssddanbrown) - [Ko-fi Page](https://ko-fi.com/ssddanbrown) -#### Silver Sponsors +#### Gold Sponsor -
- Diagrams.net + + Diagrams.net
#### Bronze Sponsors - - - - From 91d8d6eaaa5ae42fc9872901d6fcbcae8e19236e Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 14 Jan 2024 15:50:00 +0000 Subject: [PATCH 027/124] Range requests: Added test cases to cover functionality Fixed some found issues in the process. --- app/Http/RangeSupportedStream.php | 20 +++--- tests/Uploads/AttachmentTest.php | 101 ++++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 10 deletions(-) diff --git a/app/Http/RangeSupportedStream.php b/app/Http/RangeSupportedStream.php index b51d4a549..300f4488c 100644 --- a/app/Http/RangeSupportedStream.php +++ b/app/Http/RangeSupportedStream.php @@ -13,8 +13,8 @@ use Illuminate\Http\Request; */ class RangeSupportedStream { - protected string $sniffContent; - protected array $responseHeaders; + protected string $sniffContent = ''; + protected array $responseHeaders = []; protected int $responseStatus = 200; protected int $responseLength = 0; @@ -53,16 +53,20 @@ class RangeSupportedStream } $outStream = fopen('php://output', 'w'); - $sniffOffset = strlen($this->sniffContent); + $sniffLength = strlen($this->sniffContent); + $bytesToWrite = $this->responseLength; - if (!empty($this->sniffContent) && $this->responseOffset < $sniffOffset) { - $sniffOutput = substr($this->sniffContent, $this->responseOffset, min($sniffOffset, $this->responseLength)); + if ($sniffLength > 0 && $this->responseOffset < $sniffLength) { + $sniffEnd = min($sniffLength, $bytesToWrite + $this->responseOffset); + $sniffOutLength = $sniffEnd - $this->responseOffset; + $sniffOutput = substr($this->sniffContent, $this->responseOffset, $sniffOutLength); fwrite($outStream, $sniffOutput); + $bytesToWrite -= $sniffOutLength; } else if ($this->responseOffset !== 0) { fseek($this->stream, $this->responseOffset); } - stream_copy_to_stream($this->stream, $outStream, $this->responseLength); + stream_copy_to_stream($this->stream, $outStream, $bytesToWrite); fclose($this->stream); fclose($outStream); @@ -124,10 +128,6 @@ class RangeSupportedStream $start = (int) $start; } - if ($start > $end) { - return null; - } - $end = min($end, $this->fileSize - 1); return [$start, $end]; } diff --git a/tests/Uploads/AttachmentTest.php b/tests/Uploads/AttachmentTest.php index bd03c339c..2e1a7b339 100644 --- a/tests/Uploads/AttachmentTest.php +++ b/tests/Uploads/AttachmentTest.php @@ -316,4 +316,105 @@ class AttachmentTest extends TestCase $this->assertFileExists(storage_path($attachment->path)); $this->files->deleteAllAttachmentFiles(); } + + public function test_file_get_range_access() + { + $page = $this->entities->page(); + $this->asAdmin(); + $attachment = $this->files->uploadAttachmentDataToPage($this, $page, 'my_text.txt', 'abc123456', 'text/plain'); + + // Download access + $resp = $this->get($attachment->getUrl(), ['Range' => 'bytes=3-5']); + $resp->assertStatus(206); + $resp->assertStreamedContent('123'); + $resp->assertHeader('Content-Length', '3'); + $resp->assertHeader('Content-Range', 'bytes 3-5/9'); + + // Inline access + $resp = $this->get($attachment->getUrl(true), ['Range' => 'bytes=5-7']); + $resp->assertStatus(206); + $resp->assertStreamedContent('345'); + $resp->assertHeader('Content-Length', '3'); + $resp->assertHeader('Content-Range', 'bytes 5-7/9'); + + $this->files->deleteAllAttachmentFiles(); + } + + public function test_file_head_range_returns_no_content() + { + $page = $this->entities->page(); + $this->asAdmin(); + $attachment = $this->files->uploadAttachmentDataToPage($this, $page, 'my_text.txt', 'abc123456', 'text/plain'); + + $resp = $this->head($attachment->getUrl(), ['Range' => 'bytes=0-9']); + $resp->assertStreamedContent(''); + $resp->assertHeader('Content-Length', '9'); + $resp->assertStatus(200); + + $this->files->deleteAllAttachmentFiles(); + } + + public function test_file_head_range_edge_cases() + { + $page = $this->entities->page(); + $this->asAdmin(); + + // Mime-type "sniffing" happens on first 2k bytes, hence this content (2005 bytes) + $content = '01234' . str_repeat('a', 1990) . '0123456789'; + $attachment = $this->files->uploadAttachmentDataToPage($this, $page, 'my_text.txt', $content, 'text/plain'); + + // Test for both inline and download attachment serving + foreach ([true, false] as $isInline) { + // No end range + $resp = $this->get($attachment->getUrl($isInline), ['Range' => 'bytes=5-']); + $resp->assertStreamedContent(substr($content, 5)); + $resp->assertHeader('Content-Length', '2000'); + $resp->assertHeader('Content-Range', 'bytes 5-2004/2005'); + $resp->assertStatus(206); + + // End only range + $resp = $this->get($attachment->getUrl($isInline), ['Range' => 'bytes=-10']); + $resp->assertStreamedContent('0123456789'); + $resp->assertHeader('Content-Length', '10'); + $resp->assertHeader('Content-Range', 'bytes 1995-2004/2005'); + $resp->assertStatus(206); + + // Range across sniff point + $resp = $this->get($attachment->getUrl($isInline), ['Range' => 'bytes=1997-2002']); + $resp->assertStreamedContent('234567'); + $resp->assertHeader('Content-Length', '6'); + $resp->assertHeader('Content-Range', 'bytes 1997-2002/2005'); + $resp->assertStatus(206); + + // Range up to sniff point + $resp = $this->get($attachment->getUrl($isInline), ['Range' => 'bytes=0-1997']); + $resp->assertHeader('Content-Length', '1998'); + $resp->assertHeader('Content-Range', 'bytes 0-1997/2005'); + $resp->assertStreamedContent(substr($content, 0, 1998)); + $resp->assertStatus(206); + + // Range beyond sniff point + $resp = $this->get($attachment->getUrl($isInline), ['Range' => 'bytes=2001-2003']); + $resp->assertStreamedContent('678'); + $resp->assertHeader('Content-Length', '3'); + $resp->assertHeader('Content-Range', 'bytes 2001-2003/2005'); + $resp->assertStatus(206); + + // Range beyond content + $resp = $this->get($attachment->getUrl($isInline), ['Range' => 'bytes=0-2010']); + $resp->assertStreamedContent($content); + $resp->assertHeader('Content-Length', '2005'); + $resp->assertHeaderMissing('Content-Range'); + $resp->assertStatus(200); + + // Range start before end + $resp = $this->get($attachment->getUrl($isInline), ['Range' => 'bytes=50-10']); + $resp->assertStreamedContent($content); + $resp->assertHeader('Content-Length', '2005'); + $resp->assertHeader('Content-Range', 'bytes */2005'); + $resp->assertStatus(416); + } + + $this->files->deleteAllAttachmentFiles(); + } } From c1552fb799cda7fbb6de27ebcb01aa2580fd5330 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 15 Jan 2024 11:50:05 +0000 Subject: [PATCH 028/124] Attachments: Drag and drop video support Supports dragging and dropping video attahchments to embed them in the editor as HTML video tags. --- app/Uploads/Attachment.php | 19 +++++++++++++++++-- .../views/attachments/manager-list.blade.php | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/app/Uploads/Attachment.php b/app/Uploads/Attachment.php index 4fd6d4cdc..57d7cb334 100644 --- a/app/Uploads/Attachment.php +++ b/app/Uploads/Attachment.php @@ -77,7 +77,22 @@ class Attachment extends Model } /** - * Generate a HTML link to this attachment. + * Get the representation of this attachment in a format suitable for the page editors. + * Detects and adapts video content to use an inline video embed. + */ + public function editorContent(): array + { + $videoExtensions = ['mp4', 'webm', 'mkv', 'ogg', 'avi']; + if (in_array(strtolower($this->extension), $videoExtensions)) { + $html = ''; + return ['text/html' => $html, 'text/plain' => $html]; + } + + return ['text/html' => $this->htmlLink(), 'text/plain' => $this->markdownLink()]; + } + + /** + * Generate the HTML link to this attachment. */ public function htmlLink(): string { @@ -85,7 +100,7 @@ class Attachment extends Model } /** - * Generate a markdown link to this attachment. + * Generate a MarkDown link to this attachment. */ public function markdownLink(): string { diff --git a/resources/views/attachments/manager-list.blade.php b/resources/views/attachments/manager-list.blade.php index 342b46dca..0e841a042 100644 --- a/resources/views/attachments/manager-list.blade.php +++ b/resources/views/attachments/manager-list.blade.php @@ -4,7 +4,7 @@
@icon('grip')
From 2dc454d206b518804aecd0551a71d338cf889c08 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 15 Jan 2024 13:36:04 +0000 Subject: [PATCH 029/124] Uploads: Explicitly disabled s3 streaming in config This was the default option anyway, just adding here for better visibility of this being set. Can't enable without issues as the app will attempt to seek which does not work for these streams. Also have not tested on non-s3, s3-like systems. --- app/Config/filesystems.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Config/filesystems.php b/app/Config/filesystems.php index e6ae0fed3..1319c8886 100644 --- a/app/Config/filesystems.php +++ b/app/Config/filesystems.php @@ -58,6 +58,7 @@ return [ 'endpoint' => env('STORAGE_S3_ENDPOINT', null), 'use_path_style_endpoint' => env('STORAGE_S3_ENDPOINT', null) !== null, 'throw' => true, + 'stream_reads' => false, ], ], From adf1806feaa541f7ff627ba83278ea7dd2fd7a04 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 16 Jan 2024 12:06:13 +0000 Subject: [PATCH 030/124] Chapters API: Added missing book_slug field Was removed during previous changes, but reflected in response examples. This adds into all standard single chapter responses. For #4765 --- app/Entities/Controllers/ChapterApiController.php | 5 +++-- dev/api/responses/chapters-create.json | 1 + dev/api/responses/chapters-update.json | 1 + tests/Api/ChaptersApiTest.php | 13 ++++++++----- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/app/Entities/Controllers/ChapterApiController.php b/app/Entities/Controllers/ChapterApiController.php index c21323262..85c81c248 100644 --- a/app/Entities/Controllers/ChapterApiController.php +++ b/app/Entities/Controllers/ChapterApiController.php @@ -134,8 +134,9 @@ class ChapterApiController extends ApiController $chapter->unsetRelations()->refresh(); $chapter->load(['tags']); - $chapter->makeVisible('description_html') - ->setAttribute('description_html', $chapter->descriptionHtml()); + $chapter->makeVisible('description_html'); + $chapter->setAttribute('description_html', $chapter->descriptionHtml()); + $chapter->setAttribute('book_slug', $chapter->book()->first()->slug); return $chapter; } diff --git a/dev/api/responses/chapters-create.json b/dev/api/responses/chapters-create.json index 183186b0b..2d4044405 100644 --- a/dev/api/responses/chapters-create.json +++ b/dev/api/responses/chapters-create.json @@ -11,6 +11,7 @@ "updated_by": 1, "owned_by": 1, "description_html": "

This is a great new chapter<\/strong> that I've created via the API<\/p>", + "book_slug": "example-book", "tags": [ { "name": "Category", diff --git a/dev/api/responses/chapters-update.json b/dev/api/responses/chapters-update.json index 5ac3c64c1..3dad6aa0c 100644 --- a/dev/api/responses/chapters-update.json +++ b/dev/api/responses/chapters-update.json @@ -11,6 +11,7 @@ "updated_by": 1, "owned_by": 1, "description_html": "

This is an updated chapter<\/strong> that I've altered via the API<\/p>", + "book_slug": "example-book", "tags": [ { "name": "Category", diff --git a/tests/Api/ChaptersApiTest.php b/tests/Api/ChaptersApiTest.php index 81a918877..002046c3a 100644 --- a/tests/Api/ChaptersApiTest.php +++ b/tests/Api/ChaptersApiTest.php @@ -22,11 +22,12 @@ class ChaptersApiTest extends TestCase $resp = $this->getJson($this->baseEndpoint . '?count=1&sort=+id'); $resp->assertJson(['data' => [ [ - 'id' => $firstChapter->id, - 'name' => $firstChapter->name, - 'slug' => $firstChapter->slug, - 'book_id' => $firstChapter->book->id, - 'priority' => $firstChapter->priority, + 'id' => $firstChapter->id, + 'name' => $firstChapter->name, + 'slug' => $firstChapter->slug, + 'book_id' => $firstChapter->book->id, + 'priority' => $firstChapter->priority, + 'book_slug' => $firstChapter->book->slug, ], ]]); } @@ -130,6 +131,7 @@ class ChaptersApiTest extends TestCase $resp->assertJson([ 'id' => $chapter->id, 'slug' => $chapter->slug, + 'book_slug' => $chapter->book->slug, 'created_by' => [ 'name' => $chapter->createdBy->name, ], @@ -148,6 +150,7 @@ class ChaptersApiTest extends TestCase ], ], ]); + $resp->assertJsonMissingPath('book'); $resp->assertJsonCount($chapter->pages()->count(), 'pages'); } From 57284bb869bcf8e1f9aced986a72c386fee5d086 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 16 Jan 2024 12:10:22 +0000 Subject: [PATCH 031/124] Updated translations with latest Crowdin changes (#4747) --- lang/ca/entities.php | 4 ++-- lang/cs/entities.php | 4 ++-- lang/da/notifications.php | 14 +++++++------- lang/de/entities.php | 4 ++-- lang/de_informal/entities.php | 4 ++-- lang/fa/notifications.php | 6 +++--- lang/fa/settings.php | 6 +++--- lang/ja/entities.php | 12 ++++++------ lang/ja/notifications.php | 2 +- lang/nn/editor.php | 28 ++++++++++++++-------------- lang/nn/entities.php | 12 ++++++------ lang/nn/notifications.php | 22 +++++++++++----------- lang/nn/pagination.php | 2 +- lang/zh_CN/entities.php | 4 ++-- lang/zh_TW/common.php | 6 +++--- lang/zh_TW/components.php | 14 +++++++------- lang/zh_TW/settings.php | 6 +++--- 17 files changed, 75 insertions(+), 75 deletions(-) diff --git a/lang/ca/entities.php b/lang/ca/entities.php index 9bffe493c..53b766b42 100644 --- a/lang/ca/entities.php +++ b/lang/ca/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Actualitzat :timeLength', 'meta_updated_name' => 'Actualitzat :timeLength per :user', 'meta_owned_name' => 'Propietat de :user', - 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', + 'meta_reference_count' => 'Hi fa referència :count element|Hi fan referència :count elements', 'entity_select' => 'Selecciona una entitat', 'entity_select_lack_permission' => 'No teniu els permisos necessaris per a seleccionar aquest element', 'images' => 'Imatges', @@ -409,7 +409,7 @@ return [ // References 'references' => 'Referències', 'references_none' => 'Ni hi ha cap referència detectada a aquest element.', - 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', + 'references_to_desc' => 'A continuació es llisten tots els continguts coneguts del sistema que enllacen a aquest element.', // Watch Options 'watch' => 'Segueix', diff --git a/lang/cs/entities.php b/lang/cs/entities.php index 55b2e71de..4471c4b37 100644 --- a/lang/cs/entities.php +++ b/lang/cs/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Aktualizováno :timeLength', 'meta_updated_name' => 'Aktualizováno :timeLength uživatelem :user', 'meta_owned_name' => 'Vlastník :user', - 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', + 'meta_reference_count' => 'Odkazováno :count položkou|Odkazováno :count položkami', 'entity_select' => 'Výběr entity', 'entity_select_lack_permission' => 'Nemáte dostatečná oprávnění k výběru této položky', 'images' => 'Obrázky', @@ -409,7 +409,7 @@ return [ // References 'references' => 'Odkazy', 'references_none' => 'Nebyly nalezeny žádné odkazy na tuto položku.', - 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', + 'references_to_desc' => 'Níže je uveden veškerý obsah o kterém systém ví, že odkazuje na tuto položku.', // Watch Options 'watch' => 'Sledovat', diff --git a/lang/da/notifications.php b/lang/da/notifications.php index 1afd23f1d..61a44f1f0 100644 --- a/lang/da/notifications.php +++ b/lang/da/notifications.php @@ -4,13 +4,13 @@ */ return [ - 'new_comment_subject' => 'New comment on page: :pageName', - 'new_comment_intro' => 'A user has commented on a page in :appName:', - 'new_page_subject' => 'New page: :pageName', - 'new_page_intro' => 'A new page has been created in :appName:', - 'updated_page_subject' => 'Updated page: :pageName', - 'updated_page_intro' => 'A page has been updated in :appName:', - 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'new_comment_subject' => 'Ny kommentar på siden: :pageName', + 'new_comment_intro' => 'En bruger har kommenteret på en side i :appName:', + 'new_page_subject' => 'Ny side: :pageName', + 'new_page_intro' => 'En ny side er blevet oprettet i :appName:', + 'updated_page_subject' => 'Opdateret side: :pageName', + 'updated_page_intro' => 'En side er blevet opdateret i :appName:', + 'updated_page_debounce' => 'For at forhindre en masse af notifikationer, i et stykke tid vil du ikke blive sendt notifikationer for yderligere redigeringer til denne side af den samme editor.', 'detail_page_name' => 'Page Name:', 'detail_page_path' => 'Page Path:', diff --git a/lang/de/entities.php b/lang/de/entities.php index 21a0d3bef..0b46b41f3 100644 --- a/lang/de/entities.php +++ b/lang/de/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Zuletzt aktualisiert: :timeLength', 'meta_updated_name' => 'Zuletzt aktualisiert: :timeLength von :user', 'meta_owned_name' => 'Im Besitz von :user', - 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', + 'meta_reference_count' => 'Referenziert von :count Element|Referenziert von :count Elementen', 'entity_select' => 'Eintrag auswählen', 'entity_select_lack_permission' => 'Sie haben nicht die benötigte Berechtigung, um dieses Element auszuwählen', 'images' => 'Bilder', @@ -409,7 +409,7 @@ return [ // References 'references' => 'Verweise', 'references_none' => 'Es gibt keine nachverfolgten Referenzen zu diesem Element.', - 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', + 'references_to_desc' => 'Unten sind alle bekannten Inhalte im System aufgelistet, die auf diesen Eintrag verweisen.', // Watch Options 'watch' => 'Beobachten', diff --git a/lang/de_informal/entities.php b/lang/de_informal/entities.php index 2da655c1f..43842a55d 100644 --- a/lang/de_informal/entities.php +++ b/lang/de_informal/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Zuletzt aktualisiert: :timeLength', 'meta_updated_name' => 'Zuletzt aktualisiert: :timeLength von :user', 'meta_owned_name' => 'Im Besitz von :user', - 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', + 'meta_reference_count' => 'Referenziert von :count Element|Referenziert von :count Elementen', 'entity_select' => 'Eintrag auswählen', 'entity_select_lack_permission' => 'Du hast nicht die benötigte Berechtigung, um dieses Element auszuwählen', 'images' => 'Bilder', @@ -409,7 +409,7 @@ return [ // References 'references' => 'Verweise', 'references_none' => 'Es gibt keine nachverfolgten Referenzen zu diesem Element.', - 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', + 'references_to_desc' => 'Unten sind alle bekannten Inhalte im System aufgelistet, die auf diesen Eintrag verweisen.', // Watch Options 'watch' => 'Beobachten', diff --git a/lang/fa/notifications.php b/lang/fa/notifications.php index 675eaa762..12f4230a1 100644 --- a/lang/fa/notifications.php +++ b/lang/fa/notifications.php @@ -5,7 +5,7 @@ return [ 'new_comment_subject' => 'نظر جدید در صفحه: :pageName', - 'new_comment_intro' => 'یک کاربر در بر روی صفحه‌ای نظر ثبت کرده است :appName:', + 'new_comment_intro' => 'یک کاربر در صفحه نظر ارایه کرده است :appName:', 'new_page_subject' => 'صفحه جدید: :pageName', 'new_page_intro' => 'یک صفحه جدید ایجاد شده است در :appName:', 'updated_page_subject' => 'صفحه جدید: :pageName', @@ -13,14 +13,14 @@ return [ 'updated_page_debounce' => 'برای جلوگیری از انبوه اعلان‌ها، برای مدتی اعلان‌ ویرایش‌هایی که توسط همان ویرایشگر در این صفحه انجام می‌شود، ارسال نخواهد شد.', 'detail_page_name' => 'نام صفحه:', - 'detail_page_path' => 'Page Path:', + 'detail_page_path' => 'نام میسر صفحه:', 'detail_commenter' => 'نظر دهنده:', 'detail_comment' => 'نظر:', 'detail_created_by' => 'ایجاد شده توسط:', 'detail_updated_by' => 'به روزرسانی شده توسط:', 'action_view_comment' => 'مشاهده نظر', - 'action_view_page' => 'View Page', + 'action_view_page' => 'مشاهده صفحه', 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', 'footer_reason_link' => 'تنظیمات اطلاع‌رسانی شما', diff --git a/lang/fa/settings.php b/lang/fa/settings.php index 0659cde46..c48f4d8e9 100644 --- a/lang/fa/settings.php +++ b/lang/fa/settings.php @@ -194,7 +194,7 @@ return [ 'users_send_invite_option' => 'ارسال ایمیل دعوت کاربر', 'users_external_auth_id' => 'شناسه احراز هویت خارجی', 'users_external_auth_id_desc' => 'When an external authentication system is in use (such as SAML2, OIDC or LDAP) this is the ID which links this BookStack user to the authentication system account. You can ignore this field if using the default email-based authentication.', - 'users_password_warning' => 'Only fill the below if you would like to change the password for this user.', + 'users_password_warning' => 'فقط در صورتی که مایل به تغییر رمز عبور این کاربر هستید، موارد زیر را پر کنید.', 'users_system_public' => 'این کاربر نماینده هر کاربر مهمانی است که از نمونه شما بازدید می کند. نمی توان از آن برای ورود استفاده کرد اما به طور خودکار اختصاص داده می شود.', 'users_delete' => 'حذف کاربر', 'users_delete_named' => 'حذف :userName', @@ -210,12 +210,12 @@ return [ 'users_preferred_language' => 'زبان ترجیحی', 'users_preferred_language_desc' => 'این گزینه زبان مورد استفاده برای رابط کاربری برنامه را تغییر می دهد. این روی محتوای ایجاد شده توسط کاربر تأثیری نخواهد داشت.', 'users_social_accounts' => 'حساب های اجتماعی', - 'users_social_accounts_desc' => 'View the status of the connected social accounts for this user. Social accounts can be used in addition to the primary authentication system for system access.', + 'users_social_accounts_desc' => 'مشاهده وضعیت حساب‌های اجتماعی متصل به این کاربر. حساب‌های اجتماعی می‌توانند به عنوان تکمیلی به سیستم اصلی احراز هویت برای دسترسی به سیستم استفاده شوند.', 'users_social_accounts_info' => 'در اینجا می‌توانید حساب‌های دیگر خود را برای ورود سریع‌تر و آسان‌تر متصل کنید. قطع ارتباط حساب در اینجا، دسترسی مجاز قبلی را لغو نمی کند. دسترسی را از تنظیمات نمایه خود در حساب اجتماعی متصل لغو کنید.', 'users_social_connect' => 'اتصال حساب کاربری', 'users_social_disconnect' => 'قطع حساب', 'users_social_status_connected' => 'Connected', - 'users_social_status_disconnected' => 'Disconnected', + 'users_social_status_disconnected' => 'قطع اتصال', 'users_social_connected' => 'حساب :socialAccount با موفقیت به نمایه شما پیوست شد.', 'users_social_disconnected' => 'حساب :socialAccount با موفقیت از نمایه شما قطع شد.', 'users_api_tokens' => 'توکن‌های API', diff --git a/lang/ja/entities.php b/lang/ja/entities.php index dd4c27469..0b39c2c27 100644 --- a/lang/ja/entities.php +++ b/lang/ja/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => '更新: :timeLength', 'meta_updated_name' => '更新: :timeLength (:user)', 'meta_owned_name' => '所有者: :user', - 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', + 'meta_reference_count' => ':count 項目から参照|:count 項目から参照', 'entity_select' => 'エンティティ選択', 'entity_select_lack_permission' => 'この項目を選択するために必要な権限がありません', 'images' => '画像', @@ -132,9 +132,9 @@ return [ 'books_edit_named' => 'ブック「:bookName」を編集', 'books_form_book_name' => 'ブック名', 'books_save' => 'ブックを保存', - 'books_default_template' => 'Default Page Template', - 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', - 'books_default_template_select' => 'Select a template page', + 'books_default_template' => 'デフォルトページテンプレート', + 'books_default_template_explain' => 'このブックに新しいページを作成する際にデフォルトコンテンツとして使用されるページテンプレートを割り当てます。 これはページ作成者が選択したテンプレートページへのアクセス権を持つ場合にのみ使用されることに注意してください。', + 'books_default_template_select' => 'テンプレートページを選択', 'books_permissions' => 'ブックの権限', 'books_permissions_updated' => 'ブックの権限を更新しました', 'books_empty_contents' => 'まだページまたはチャプターが作成されていません。', @@ -207,7 +207,7 @@ return [ 'pages_delete_draft' => 'ページの下書きを削除', 'pages_delete_success' => 'ページを削除しました', 'pages_delete_draft_success' => 'ページの下書きを削除しました', - 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', + 'pages_delete_warning_template' => 'このページは現在ブックのデフォルトページテンプレートとして使用されています。 このページが削除されると、それらのブックでデフォルトのページテンプレートが割り当てられなくなります。', 'pages_delete_confirm' => 'このページを削除してもよろしいですか?', 'pages_delete_draft_confirm' => 'このページの下書きを削除してもよろしいですか?', 'pages_editing_named' => 'ページ :pageName を編集', @@ -410,7 +410,7 @@ return [ // References 'references' => '参照', 'references_none' => 'この項目への追跡された参照はありません。', - 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', + 'references_to_desc' => 'この項目はシステム内の以下のコンテンツからリンクされています。', // Watch Options 'watch' => 'ウォッチ', diff --git a/lang/ja/notifications.php b/lang/ja/notifications.php index 364b4a3aa..6fc0321d3 100644 --- a/lang/ja/notifications.php +++ b/lang/ja/notifications.php @@ -13,7 +13,7 @@ return [ 'updated_page_debounce' => '大量の通知を防ぐために、しばらくの間は同じユーザがこのページをさらに編集しても通知は送信されません。', 'detail_page_name' => 'ページ名:', - 'detail_page_path' => 'Page Path:', + 'detail_page_path' => 'ページパス:', 'detail_commenter' => 'コメントユーザ:', 'detail_comment' => 'コメント:', 'detail_created_by' => '作成ユーザ:', diff --git a/lang/nn/editor.php b/lang/nn/editor.php index 2f3b22d8b..4eef9d28a 100644 --- a/lang/nn/editor.php +++ b/lang/nn/editor.php @@ -14,24 +14,24 @@ return [ 'save' => 'Lagre', 'close' => 'Lukk', 'undo' => 'Angre', - 'redo' => 'Gjør om', + 'redo' => 'Gjer om', 'left' => 'Venstre', 'center' => 'Sentrert', - 'right' => 'Høyre', + 'right' => 'Høgre', 'top' => 'Topp', 'middle' => 'Sentrert', - 'bottom' => 'Bunn', - 'width' => 'Bredde', - 'height' => 'Høyde', - 'More' => 'Mer', + 'bottom' => 'Botn', + 'width' => 'Breidde', + 'height' => 'Høgde', + 'More' => 'Meir', 'select' => 'Velg …', // Toolbar 'formats' => 'Formater', 'header_large' => 'Stor overskrift', 'header_medium' => 'Medium overskrift', - 'header_small' => 'Liten overskrift', - 'header_tiny' => 'Bitteliten overskrift', + 'header_small' => 'Lita overskrift', + 'header_tiny' => 'Bittelita overskrift', 'paragraph' => 'Avsnitt', 'blockquote' => 'Blokksitat', 'inline_code' => 'Kodesetning', @@ -40,24 +40,24 @@ return [ 'callout_success' => 'Positiv', 'callout_warning' => 'Advarsel', 'callout_danger' => 'Negativ', - 'bold' => 'Fet', + 'bold' => 'Feit', 'italic' => 'Kursiv', 'underline' => 'Understrek', 'strikethrough' => 'Strek over', - 'superscript' => 'Hevet skrift', - 'subscript' => 'Senket skrift', + 'superscript' => 'Heva skrift', + 'subscript' => 'Senka skrift', 'text_color' => 'Tekstfarge', - 'custom_color' => 'Egenvalgt farge', + 'custom_color' => 'Eigenvalgt farge', 'remove_color' => 'Fjern farge', 'background_color' => 'Bakgrunnsfarge', 'align_left' => 'Venstrejustering', 'align_center' => 'Midtstilling', - 'align_right' => 'Høyrejustering', + 'align_right' => 'Høgrejustering', 'align_justify' => 'Blokkjustering', 'list_bullet' => 'Punktliste', 'list_numbered' => 'Nummerert liste', 'list_task' => 'Oppgaveliste', - 'indent_increase' => 'Øk innrykk', + 'indent_increase' => 'Auk innrykk', 'indent_decrease' => 'Redusér innrykk', 'table' => 'Tabell', 'insert_image' => 'Sett inn bilde', diff --git a/lang/nn/entities.php b/lang/nn/entities.php index f2ad69677..21ab84310 100644 --- a/lang/nn/entities.php +++ b/lang/nn/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Oppdatert :timeLength', 'meta_updated_name' => 'Oppdatert :timeLength av :user', 'meta_owned_name' => 'Eigd av :user', - 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', + 'meta_reference_count' => 'Sitert på :count side|Sitert på :count sider', 'entity_select' => 'Velg entitet', 'entity_select_lack_permission' => 'Du har ikkje tilgang til å velge dette elementet', 'images' => 'Bilete', @@ -132,9 +132,9 @@ return [ 'books_edit_named' => 'Endre boken :bookName', 'books_form_book_name' => 'Boktittel', 'books_save' => 'Lagre bok', - 'books_default_template' => 'Default Page Template', - 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', - 'books_default_template_select' => 'Select a template page', + 'books_default_template' => 'Standard sidemal', + 'books_default_template_explain' => 'Tildel ein sidemal som vil bli brukt som standardinnhold for alle nye sider i denne boka. Hugs at dette berre vil bli brukt om sideskaperen har tilgang til den valgte malsida.', + 'books_default_template_select' => 'Velg ei malside', 'books_permissions' => 'Boktilganger', 'books_permissions_updated' => 'Boktilganger oppdatert', 'books_empty_contents' => 'Ingen sider eller kapittel finst i denne boka.', @@ -207,7 +207,7 @@ return [ 'pages_delete_draft' => 'Slett utkastet', 'pages_delete_success' => 'Siden er slettet', 'pages_delete_draft_success' => 'Sideutkastet vart sletta', - 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', + 'pages_delete_warning_template' => 'Denne siden er i aktiv bruk som en standard sidemal for bok. Desse bøkene vil ikkje lenger ha ein standard sidemal som er tilordnet etter at denne sida vert sletta.', 'pages_delete_confirm' => 'Er du sikker på at du vil slette siden?', 'pages_delete_draft_confirm' => 'Er du sikker på at du vil slette utkastet?', 'pages_editing_named' => 'Redigerer :pageName (side)', @@ -409,7 +409,7 @@ return [ // References 'references' => 'Referanser', 'references_none' => 'Det er ingen sporede referanser til dette elementet.', - 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', + 'references_to_desc' => 'Nedanfor vises alle dei kjente sidene i systemet som lenker til denne oppføringa.', // Watch Options 'watch' => 'Overvåk', diff --git a/lang/nn/notifications.php b/lang/nn/notifications.php index 98c686e44..247d8d105 100644 --- a/lang/nn/notifications.php +++ b/lang/nn/notifications.php @@ -4,24 +4,24 @@ */ return [ - 'new_comment_subject' => 'Ny kommentar på siden: :pageName', - 'new_comment_intro' => 'En bruker har kommentert en side i :appName:', + 'new_comment_subject' => 'Ny kommentar på sida: :pageName', + 'new_comment_intro' => 'Ein brukar har kommentert ei side i :appName:', 'new_page_subject' => 'Ny side: :pageName', - 'new_page_intro' => 'En ny side er opprettet i :appName:', + 'new_page_intro' => 'Ei ny side vart oppretta i :appName:', 'updated_page_subject' => 'Oppdatert side: :pageName', - 'updated_page_intro' => 'En side er oppdatert i :appName:', - 'updated_page_debounce' => 'For å forhindre mange varslinger, vil du ikke få nye varslinger for endringer på denne siden fra samme forfatter.', + 'updated_page_intro' => 'Ei side vart oppdatert i :appName:', + 'updated_page_debounce' => 'For å forhindre mange varslingar, vil du ikkje få nye varslinger for endringar på denne siden frå same forfattar.', - 'detail_page_name' => 'Sidenavn:', + 'detail_page_name' => 'Sidenamn:', 'detail_page_path' => 'Sidenamn:', - 'detail_commenter' => 'Kommentar fra:', + 'detail_commenter' => 'Kommentar frå:', 'detail_comment' => 'Kommentar:', - 'detail_created_by' => 'Opprettet av:', + 'detail_created_by' => 'Oppretta av:', 'detail_updated_by' => 'Oppdatert av:', 'action_view_comment' => 'Vis kommentar', - 'action_view_page' => 'Se side', + 'action_view_page' => 'Sjå side', - 'footer_reason' => 'Denne meldingen ble sendt til deg fordi :link dekker denne typen aktivitet for dette elementet.', - 'footer_reason_link' => 'dine varslingsinnstillinger', + 'footer_reason' => 'Denne meldinga vart sendt til deg fordi :link dekker denne typen aktivitet for dette elementet.', + 'footer_reason_link' => 'dine varslingsinnstillingar', ]; diff --git a/lang/nn/pagination.php b/lang/nn/pagination.php index d910da124..15529d12a 100644 --- a/lang/nn/pagination.php +++ b/lang/nn/pagination.php @@ -6,7 +6,7 @@ */ return [ - 'previous' => '« Forrige', + 'previous' => '« Førre', 'next' => 'Neste »', ]; diff --git a/lang/zh_CN/entities.php b/lang/zh_CN/entities.php index a0c32fa4f..e12f6051a 100644 --- a/lang/zh_CN/entities.php +++ b/lang/zh_CN/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => '更新于 :timeLength', 'meta_updated_name' => '由 :user 更新于 :timeLength', 'meta_owned_name' => '拥有者 :user', - 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', + 'meta_reference_count' => '被 :count 个页面引用|被 :count 个页面引用', 'entity_select' => '选择项目', 'entity_select_lack_permission' => '您没有选择此项目所需的权限', 'images' => '图片', @@ -133,7 +133,7 @@ return [ 'books_form_book_name' => '书名', 'books_save' => '保存图书', 'books_default_template' => '默认页面模板', - 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'books_default_template_explain' => '指定一个页面模板,该模板将用作本书中所有新页面的默认内容。请注意,仅当页面创建者具有对所选页面模板的查看访问权限时,此功能才会生效。', 'books_default_template_select' => '选择模板页面', 'books_permissions' => '图书权限', 'books_permissions_updated' => '图书权限已更新', diff --git a/lang/zh_TW/common.php b/lang/zh_TW/common.php index 22bde3f08..53b2a5310 100644 --- a/lang/zh_TW/common.php +++ b/lang/zh_TW/common.php @@ -6,7 +6,7 @@ return [ // Buttons 'cancel' => '取消', - 'close' => 'Close', + 'close' => '關閉', 'confirm' => '確認', 'back' => '返回', 'save' => '儲存', @@ -42,7 +42,7 @@ return [ 'remove' => '移除', 'add' => '新增', 'configure' => '配置', - 'manage' => 'Manage', + 'manage' => '管理', 'fullscreen' => '全螢幕', 'favourite' => '最愛', 'unfavourite' => '取消最愛', @@ -52,7 +52,7 @@ return [ 'filter_clear' => '清理過濾', 'download' => '下載', 'open_in_tab' => '在新分頁中開啟', - 'open' => 'Open', + 'open' => '開啟', // Sort Options 'sort_options' => '排序選項', diff --git a/lang/zh_TW/components.php b/lang/zh_TW/components.php index 36f2265d1..682a8ed4d 100644 --- a/lang/zh_TW/components.php +++ b/lang/zh_TW/components.php @@ -6,10 +6,10 @@ return [ // Image Manager 'image_select' => '選取圖片', - 'image_list' => 'Image List', - 'image_details' => 'Image Details', - 'image_upload' => 'Upload Image', - 'image_intro' => 'Here you can select and manage images that have been previously uploaded to the system.', + 'image_list' => '圖片列表', + 'image_details' => '圖片詳細資訊', + 'image_upload' => '上傳圖片', + 'image_intro' => '您可以在這裡選取和管理上傳到系統的圖片。', 'image_intro_upload' => 'Upload a new image by dragging an image file into this window, or by using the "Upload Image" button above.', 'image_all' => '全部', 'image_all_title' => '檢視所有圖片', @@ -26,14 +26,14 @@ return [ 'image_delete_confirm_text' => '您確認想要刪除這個圖片?', 'image_select_image' => '選取圖片', 'image_dropzone' => '拖曳圖片或點擊此處上傳', - 'image_dropzone_drop' => 'Drop images here to upload', + 'image_dropzone_drop' => '將圖片拖放到此處上傳', 'images_deleted' => '圖片已刪除', 'image_preview' => '圖片預覽', 'image_upload_success' => '圖片上傳成功', 'image_update_success' => '圖片詳細資訊更新成功', 'image_delete_success' => '圖片刪除成功', - 'image_replace' => 'Replace Image', - 'image_replace_success' => 'Image file successfully updated', + 'image_replace' => '替換圖片', + 'image_replace_success' => '圖片更新成功', 'image_rebuild_thumbs' => 'Regenerate Size Variations', 'image_rebuild_thumbs_success' => 'Image size variations successfully rebuilt!', diff --git a/lang/zh_TW/settings.php b/lang/zh_TW/settings.php index ee05b2024..c3c861770 100644 --- a/lang/zh_TW/settings.php +++ b/lang/zh_TW/settings.php @@ -32,7 +32,7 @@ return [ 'app_custom_html_desc' => '此處加入的任何內容都將插入到每個頁面的 部分的底部,這對於覆蓋樣式或加入分析程式碼很方便。', 'app_custom_html_disabled_notice' => '在此設定頁面上停用了自訂 HTML 標題內容,以確保任何重大變更都能被還原。', 'app_logo' => '應用程式圖示', - 'app_logo_desc' => 'This is used in the application header bar, among other areas. This image should be 86px in height. Large images will be scaled down.', + 'app_logo_desc' => '這個設定會被使用在應用程式標題欄等區域;圖片的高度應為 86 像素,大型圖片將會按比例縮小。', 'app_icon' => '應用程式圖示', 'app_icon_desc' => 'This icon is used for browser tabs and shortcut icons. This should be a 256px square PNG image.', 'app_homepage' => '應用程式首頁', @@ -53,7 +53,7 @@ return [ 'ui_colors_desc' => 'Set the application primary color and default link color. The primary color is mainly used for the header banner, buttons and interface decorations. The default link color is used for text-based links and actions, both within written content and in the application interface.', 'app_color' => '主要顏色', 'link_color' => '連結預設顏色', - 'content_colors_desc' => 'Set colors for all elements in the page organisation hierarchy. Choosing colors with a similar brightness to the default colors is recommended for readability.', + 'content_colors_desc' => '設定頁面層次結構中的元素顏色;為了提高可讀性,建議選擇亮度與預設顏色相似的顏色。', 'bookshelf_color' => '書架顔色', 'book_color' => '書本顔色', 'chapter_color' => '章節顔色', @@ -269,7 +269,7 @@ return [ 'webhooks_delete' => '刪除 Webhook', 'webhooks_delete_warning' => 'This will fully delete this webhook, with the name \':webhookName\', from the system.', 'webhooks_delete_confirm' => 'Are you sure you want to delete this webhook?', - 'webhooks_format_example' => 'Webhook Format Example', + 'webhooks_format_example' => 'Webhook 格式範例', 'webhooks_format_example_desc' => 'Webhook data is sent as a POST request to the configured endpoint as JSON following the format below. The "related_item" and "url" properties are optional and will depend on the type of event triggered.', 'webhooks_status' => 'Webhook 狀態', 'webhooks_last_called' => 'Last Called:', From 496b4264d9df114a0bc4bde8fba2ff4bd9d4c266 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 16 Jan 2024 12:14:25 +0000 Subject: [PATCH 032/124] Updated translator attribution --- .github/translators.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/translators.txt b/.github/translators.txt index a27717f94..ef16da257 100644 --- a/.github/translators.txt +++ b/.github/translators.txt @@ -324,7 +324,7 @@ Robin Flikkema (RobinFlikkema) :: Dutch Michal Gurcik (mgurcik) :: Slovak Pooyan Arab (pooyanarab) :: Persian Ochi Darma Putra (troke12) :: Indonesian -H.-H. Peng (Hsins) :: Chinese Traditional +Hsin-Hsiang Peng (Hsins) :: Chinese Traditional Mosi Wang (mosiwang) :: Chinese Traditional 骆言 (LawssssCat) :: Chinese Simplified Stickers Gaming Shøw (StickerSGSHOW) :: French @@ -386,3 +386,5 @@ Y (cnsr) :: Ukrainian ZY ZV (vy0b0x) :: Chinese Simplified diegobenitez :: Spanish Marc Hagen (MarcHagen) :: Dutch +Kasper Alsøe (zeonos) :: Danish +sultani :: Persian From 655ae5ecaeba3eaea73bd20c970387f1fa993e6a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 23 Jan 2024 12:31:44 +0000 Subject: [PATCH 033/124] Text: Tweaks to EN text for consistency/readability As suggested by Tim in discord chat. --- lang/en/activities.php | 6 +++--- lang/en/settings.php | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lang/en/activities.php b/lang/en/activities.php index d5b55c03d..092398ef0 100644 --- a/lang/en/activities.php +++ b/lang/en/activities.php @@ -93,11 +93,11 @@ return [ 'user_delete_notification' => 'User successfully removed', // API Tokens - 'api_token_create' => 'created api token', + 'api_token_create' => 'created API token', 'api_token_create_notification' => 'API token successfully created', - 'api_token_update' => 'updated api token', + 'api_token_update' => 'updated API token', 'api_token_update_notification' => 'API token successfully updated', - 'api_token_delete' => 'deleted api token', + 'api_token_delete' => 'deleted API token', 'api_token_delete_notification' => 'API token successfully deleted', // Roles diff --git a/lang/en/settings.php b/lang/en/settings.php index 03e9bf462..7b7f5d2a2 100644 --- a/lang/en/settings.php +++ b/lang/en/settings.php @@ -109,7 +109,7 @@ return [ 'recycle_bin_contents_empty' => 'The recycle bin is currently empty', 'recycle_bin_empty' => 'Empty Recycle Bin', 'recycle_bin_empty_confirm' => 'This will permanently destroy all items in the recycle bin including content contained within each item. Are you sure you want to empty the recycle bin?', - 'recycle_bin_destroy_confirm' => 'This action will permanently delete this item, along with any child elements listed below, from the system and you will not be able to restore this content. Are you sure you want to permanently delete this item?', + 'recycle_bin_destroy_confirm' => 'This action will permanently delete this item from the system, along with any child elements listed below, and you will not be able to restore this content. Are you sure you want to permanently delete this item?', 'recycle_bin_destroy_list' => 'Items to be Destroyed', 'recycle_bin_restore_list' => 'Items to be Restored', 'recycle_bin_restore_confirm' => 'This action will restore the deleted item, including any child elements, to their original location. If the original location has since been deleted, and is now in the recycle bin, the parent item will also need to be restored.', From 788327fffb044a03ccc490be0bcddd48e3551b01 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 23 Jan 2024 15:01:07 +0000 Subject: [PATCH 034/124] Attachment List: Fixed broken ctrl-click functionality Fixes #4782 --- resources/js/components/attachments-list.js | 8 ++++---- resources/views/attachments/list.blade.php | 4 +++- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/resources/js/components/attachments-list.js b/resources/js/components/attachments-list.js index 4db09977f..665904f86 100644 --- a/resources/js/components/attachments-list.js +++ b/resources/js/components/attachments-list.js @@ -9,6 +9,8 @@ export class AttachmentsList extends Component { setup() { this.container = this.$el; + this.fileLinks = this.$manyRefs.linkTypeFile; + this.setupListeners(); } @@ -27,8 +29,7 @@ export class AttachmentsList extends Component { } addOpenQueryToLinks() { - const links = this.container.querySelectorAll('a.attachment-file'); - for (const link of links) { + for (const link of this.fileLinks) { if (link.href.split('?')[1] !== 'open=true') { link.href += '?open=true'; link.setAttribute('target', '_blank'); @@ -37,8 +38,7 @@ export class AttachmentsList extends Component { } removeOpenQueryFromLinks() { - const links = this.container.querySelectorAll('a.attachment-file'); - for (const link of links) { + for (const link of this.fileLinks) { link.href = link.href.split('?')[0]; link.removeAttribute('target'); } diff --git a/resources/views/attachments/list.blade.php b/resources/views/attachments/list.blade.php index a6ffb709b..71197cc19 100644 --- a/resources/views/attachments/list.blade.php +++ b/resources/views/attachments/list.blade.php @@ -2,7 +2,9 @@ @foreach($attachments as $attachment)

- external) target="_blank" @endif> + external) target="_blank" @endif>
@icon($attachment->external ? 'export' : 'file')
{{ $attachment->name }}
From 69c8ff5c2d491d8674e2671a912a025cf16f86d3 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 23 Jan 2024 15:39:09 +0000 Subject: [PATCH 035/124] Entity selector: Fixed initial load overwriting initial search This changes how initial searches can be handled via config rather than specific action so they can be considered in how the initial data load is done, to prevent the default empty state loading and overwriting the search data if it lands later (which was commonly likely). For #4778 --- resources/js/components/entity-selector-popup.js | 7 +------ resources/js/components/entity-selector.js | 13 ++++++++----- resources/js/components/page-picker.js | 3 ++- resources/js/markdown/actions.js | 3 ++- resources/js/wysiwyg/config.js | 3 ++- resources/js/wysiwyg/shortcuts.js | 3 ++- 6 files changed, 17 insertions(+), 15 deletions(-) diff --git a/resources/js/components/entity-selector-popup.js b/resources/js/components/entity-selector-popup.js index 6fb461968..29c06e909 100644 --- a/resources/js/components/entity-selector-popup.js +++ b/resources/js/components/entity-selector-popup.js @@ -18,18 +18,13 @@ export class EntitySelectorPopup extends Component { /** * Show the selector popup. * @param {Function} callback - * @param {String} searchText * @param {EntitySelectorSearchOptions} searchOptions */ - show(callback, searchText = '', searchOptions = {}) { + show(callback, searchOptions = {}) { this.callback = callback; this.getSelector().configureSearchOptions(searchOptions); this.getPopup().show(); - if (searchText) { - this.getSelector().searchText(searchText); - } - this.getSelector().focusSearch(); } diff --git a/resources/js/components/entity-selector.js b/resources/js/components/entity-selector.js index 5ad991437..561370d7a 100644 --- a/resources/js/components/entity-selector.js +++ b/resources/js/components/entity-selector.js @@ -6,6 +6,7 @@ import {Component} from './component'; * @property entityTypes string * @property entityPermission string * @property searchEndpoint string + * @property initialValue string */ /** @@ -25,6 +26,7 @@ export class EntitySelector extends Component { entityTypes: this.$opts.entityTypes || 'page,book,chapter', entityPermission: this.$opts.entityPermission || 'view', searchEndpoint: this.$opts.searchEndpoint || '', + initialValue: this.searchInput.value || '', }; this.search = ''; @@ -44,6 +46,7 @@ export class EntitySelector extends Component { configureSearchOptions(options) { Object.assign(this.searchOptions, options); this.reset(); + this.searchInput.value = this.searchOptions.initialValue; } setupListeners() { @@ -108,11 +111,6 @@ export class EntitySelector extends Component { this.searchInput.focus(); } - searchText(queryText) { - this.searchInput.value = queryText; - this.searchEntities(queryText); - } - showLoading() { this.loading.style.display = 'block'; this.resultsContainer.style.display = 'none'; @@ -128,6 +126,11 @@ export class EntitySelector extends Component { throw new Error('Search endpoint not set for entity-selector load'); } + if (this.searchOptions.initialValue) { + this.searchEntities(this.searchOptions.initialValue); + return; + } + window.$http.get(this.searchUrl()).then(resp => { this.resultsContainer.innerHTML = resp.data; this.hideLoading(); diff --git a/resources/js/components/page-picker.js b/resources/js/components/page-picker.js index 39af67229..5ab511595 100644 --- a/resources/js/components/page-picker.js +++ b/resources/js/components/page-picker.js @@ -35,7 +35,8 @@ export class PagePicker extends Component { const selectorPopup = window.$components.first('entity-selector-popup'); selectorPopup.show(entity => { this.setValue(entity.id, entity.name); - }, '', { + }, { + initialValue: '', searchEndpoint: this.selectorEndpoint, entityTypes: 'page', entityPermission: 'view', diff --git a/resources/js/markdown/actions.js b/resources/js/markdown/actions.js index 511f1ebda..73040a57b 100644 --- a/resources/js/markdown/actions.js +++ b/resources/js/markdown/actions.js @@ -73,7 +73,8 @@ export class Actions { const selectedText = selectionText || entity.name; const newText = `[${selectedText}](${entity.link})`; this.#replaceSelection(newText, newText.length, selectionRange); - }, selectionText, { + }, { + initialValue: selectionText, searchEndpoint: '/search/entity-selector', entityTypes: 'page,book,chapter,bookshelf', entityPermission: 'view', diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index 963e2970d..6c96e47e9 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -85,7 +85,8 @@ function filePickerCallback(callback, value, meta) { text: entity.name, title: entity.name, }); - }, selectionText, { + }, { + initialValue: selectionText, searchEndpoint: '/search/entity-selector', entityTypes: 'page,book,chapter,bookshelf', entityPermission: 'view', diff --git a/resources/js/wysiwyg/shortcuts.js b/resources/js/wysiwyg/shortcuts.js index da9e02270..dbc725b1d 100644 --- a/resources/js/wysiwyg/shortcuts.js +++ b/resources/js/wysiwyg/shortcuts.js @@ -58,7 +58,8 @@ export function register(editor) { editor.selection.collapse(false); editor.focus(); - }, selectionText, { + }, { + initialValue: selectionText, searchEndpoint: '/search/entity-selector', entityTypes: 'page,book,chapter,bookshelf', entityPermission: 'view', From 8c6b1164724fffd1766792fb3d6fef4972ba1b9e Mon Sep 17 00:00:00 2001 From: Sascha Date: Tue, 23 Jan 2024 21:37:00 +0100 Subject: [PATCH 036/124] Update TrashCan.php remove duplicate call of $page->forceDelete(); --- app/Entities/Tools/TrashCan.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/Entities/Tools/TrashCan.php b/app/Entities/Tools/TrashCan.php index e5bcfe71a..8e9f010df 100644 --- a/app/Entities/Tools/TrashCan.php +++ b/app/Entities/Tools/TrashCan.php @@ -206,8 +206,6 @@ class TrashCan Book::query()->where('default_template_id', '=', $page->id) ->update(['default_template_id' => null]); - $page->forceDelete(); - // Remove chapter template usages Chapter::query()->where('default_template_id', '=', $page->id) ->update(['default_template_id' => null]); From 0fc02a2532fd33c443838de77f5700df220b79c9 Mon Sep 17 00:00:00 2001 From: Sascha Date: Tue, 23 Jan 2024 22:37:15 +0100 Subject: [PATCH 037/124] fixed error from phpcs --- app/Entities/Controllers/PageController.php | 4 ++-- app/Entities/Repos/PageRepo.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Entities/Controllers/PageController.php b/app/Entities/Controllers/PageController.php index 74dd4f531..eaad3c0b7 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -260,7 +260,7 @@ class PageController extends Controller $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); $this->checkOwnablePermission('page-delete', $page); $this->setPageTitle(trans('entities.pages_delete_named', ['pageName' => $page->getShortName()])); - $usedAsTemplate = + $usedAsTemplate = Book::query()->where('default_template_id', '=', $page->id)->count() > 0 || Chapter::query()->where('default_template_id', '=', $page->id)->count() > 0; @@ -282,7 +282,7 @@ class PageController extends Controller $page = $this->pageRepo->getById($pageId); $this->checkOwnablePermission('page-update', $page); $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName' => $page->getShortName()])); - $usedAsTemplate = + $usedAsTemplate = Book::query()->where('default_template_id', '=', $page->id)->count() > 0 || Chapter::query()->where('default_template_id', '=', $page->id)->count() > 0; diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index 67c4b2225..d9bda0198 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -142,7 +142,7 @@ class PageRepo } else { $defaultTemplate = $page->book->defaultTemplate; } - + if ($defaultTemplate && userCan('view', $defaultTemplate)) { $page->forceFill([ 'html' => $defaultTemplate->html, From 14ecb19b054e6b915f8f37104793fb98cd962b13 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 24 Jan 2024 10:22:13 +0000 Subject: [PATCH 038/124] Merged l10n_development into v23-12 Squash merge Closes #4779 --- lang/cy/activities.php | 62 ++++++------ lang/cy/auth.php | 22 ++--- lang/cy/common.php | 98 +++++++++--------- lang/cy/components.php | 16 +-- lang/cy/editor.php | 76 +++++++------- lang/cy/notifications.php | 20 ++-- lang/cy/pagination.php | 4 +- lang/fr/entities.php | 12 +-- lang/fr/notifications.php | 2 +- lang/he/common.php | 4 +- lang/he/notifications.php | 34 +++---- lang/ko/activities.php | 102 +++++++++---------- lang/ko/auth.php | 2 +- lang/ko/common.php | 8 +- lang/ko/components.php | 18 ++-- lang/ko/editor.php | 6 +- lang/ko/entities.php | 202 +++++++++++++++++++------------------- lang/ko/errors.php | 100 +++++++++---------- lang/ko/notifications.php | 34 +++---- lang/ko/preferences.php | 58 +++++------ lang/ko/settings.php | 62 ++++++------ lang/ko/validation.php | 2 +- lang/lv/activities.php | 52 +++++----- lang/lv/components.php | 18 ++-- lang/lv/entities.php | 24 ++--- lang/lv/errors.php | 10 +- lang/lv/preferences.php | 42 ++++---- lang/lv/settings.php | 16 +-- lang/nl/activities.php | 78 +++++++-------- lang/nl/auth.php | 14 +-- lang/nl/entities.php | 122 +++++++++++------------ lang/nl/errors.php | 6 +- lang/nl/settings.php | 14 +-- lang/sq/activities.php | 170 ++++++++++++++++---------------- lang/sq/auth.php | 48 ++++----- 35 files changed, 779 insertions(+), 779 deletions(-) diff --git a/lang/cy/activities.php b/lang/cy/activities.php index 2c474c673..b3301d757 100644 --- a/lang/cy/activities.php +++ b/lang/cy/activities.php @@ -6,26 +6,26 @@ return [ // Pages - 'page_create' => 'tudalen wedi\'i chreu', + 'page_create' => 'creodd dudalen', 'page_create_notification' => 'Tudalen wedi\'i chreu\'n llwyddiannus', - 'page_update' => 'tudalen wedi\'i diweddaru', + 'page_update' => 'diweddarodd dudalen', 'page_update_notification' => 'Tudalen wedi\'i diweddaru\'n llwyddiannus', - 'page_delete' => 'tudalen wedi\'i dileu', - 'page_delete_notification' => 'Cafodd y dudalen ei dileu yn llwyddiannus', - 'page_restore' => 'tudalen wedi\'i hadfer', - 'page_restore_notification' => 'Cafodd y dudalen ei hadfer yn llwyddiannus', - 'page_move' => 'symwyd tudalen', - 'page_move_notification' => 'Page successfully moved', + 'page_delete' => 'dileodd dudalen', + 'page_delete_notification' => 'Tudalen wedi\'i dileu\'n llwyddiannus', + 'page_restore' => 'adferodd dudalen', + 'page_restore_notification' => 'Tudalen wedi\'i hadfer yn llwyddiannus', + 'page_move' => 'symydodd dudalen', + 'page_move_notification' => 'Tudalen wedi\'i symud yn llwyddianus', // Chapters - 'chapter_create' => 'pennod creu', + 'chapter_create' => 'creodd bennod', 'chapter_create_notification' => 'Pennod wedi\'i chreu\'n llwyddiannus', 'chapter_update' => 'pennod wedi diweddaru', 'chapter_update_notification' => 'Pennod wedi\'i diweddaru\'n llwyddiannus', 'chapter_delete' => 'pennod wedi dileu', 'chapter_delete_notification' => 'Pennod wedi\'i dileu\'n llwyddiannus', 'chapter_move' => 'pennod wedi symud', - 'chapter_move_notification' => 'Chapter successfully moved', + 'chapter_move_notification' => 'Pennod wedi\'i symud yn llwyddianus', // Books 'book_create' => 'llyfr wedi creu', @@ -40,14 +40,14 @@ return [ 'book_sort_notification' => 'Ail-archebwyd y llyfr yn llwyddiannus', // Bookshelves - 'bookshelf_create' => 'created shelf', - 'bookshelf_create_notification' => 'Shelf successfully created', + 'bookshelf_create' => 'creodd silff', + 'bookshelf_create_notification' => 'Silff wedi\'i chreu\'n llwyddiannus', 'bookshelf_create_from_book' => 'converted book to shelf', 'bookshelf_create_from_book_notification' => 'Book successfully converted to a shelf', - 'bookshelf_update' => 'updated shelf', - 'bookshelf_update_notification' => 'Shelf successfully updated', - 'bookshelf_delete' => 'deleted shelf', - 'bookshelf_delete_notification' => 'Shelf successfully deleted', + 'bookshelf_update' => 'diweddarodd silff', + 'bookshelf_update_notification' => 'Silff wedi\'i diweddaru\'n llwyddiannus', + 'bookshelf_delete' => 'dileodd silff', + 'bookshelf_delete_notification' => 'Silff wedi\'i dileu\'n llwyddiannus', // Revisions 'revision_restore' => 'restored revision', @@ -62,7 +62,7 @@ return [ 'watch_update_level_notification' => 'Watch preferences successfully updated', // Auth - 'auth_login' => 'logged in', + 'auth_login' => 'wedi\'u mewngofnodi', 'auth_register' => 'registered as new user', 'auth_password_reset_request' => 'requested user password reset', 'auth_password_reset_update' => 'reset user password', @@ -85,20 +85,20 @@ return [ 'webhook_delete_notification' => 'Webhook wedi\'i dileu\'n llwyddiannus', // Users - 'user_create' => 'created user', - 'user_create_notification' => 'User successfully created', - 'user_update' => 'updated user', + 'user_create' => 'creodd ddefnyddiwr', + 'user_create_notification' => 'Defnyddiwr wedi\'i greu\'n llwyddiannus', + 'user_update' => 'diweddarodd ddefnyddiwr', 'user_update_notification' => 'Diweddarwyd y defnyddiwr yn llwyddiannus', - 'user_delete' => 'deleted user', + 'user_delete' => 'dileodd ddefnyddiwr', 'user_delete_notification' => 'Tynnwyd y defnyddiwr yn llwyddiannus', // API Tokens - 'api_token_create' => 'created api token', - 'api_token_create_notification' => 'API token successfully created', - 'api_token_update' => 'updated api token', - 'api_token_update_notification' => 'API token successfully updated', - 'api_token_delete' => 'deleted api token', - 'api_token_delete_notification' => 'API token successfully deleted', + 'api_token_create' => 'creodd tocyn api', + 'api_token_create_notification' => 'Tocyn API wedi\'i greu\'n llwyddiannus', + 'api_token_update' => 'diweddarodd docyn api', + 'api_token_update_notification' => 'Tocyn API wedi\'i ddiweddaru\'n llwyddiannus', + 'api_token_delete' => 'dileodd docyn api', + 'api_token_delete_notification' => 'Tocyn API wedi\'i ddileu\'n llwyddiannus', // Roles 'role_create' => 'created role', @@ -109,15 +109,15 @@ return [ 'role_delete_notification' => 'Role successfully deleted', // Recycle Bin - 'recycle_bin_empty' => 'emptied recycle bin', + 'recycle_bin_empty' => 'gwagodd fin ailgylchu', 'recycle_bin_restore' => 'restored from recycle bin', 'recycle_bin_destroy' => 'removed from recycle bin', // Comments 'commented_on' => 'gwnaeth sylwadau ar', - 'comment_create' => 'added comment', - 'comment_update' => 'updated comment', - 'comment_delete' => 'deleted comment', + 'comment_create' => 'ychwanegodd sylw', + 'comment_update' => 'diweddarodd sylw', + 'comment_delete' => 'dileodd sylw', // Other 'permissions_update' => 'caniatadau wedi\'u diweddaru', diff --git a/lang/cy/auth.php b/lang/cy/auth.php index 6be963080..94fc0b6b5 100644 --- a/lang/cy/auth.php +++ b/lang/cy/auth.php @@ -54,30 +54,30 @@ return [ 'email_reset_not_requested' => 'If you did not request a password reset, no further action is required.', // Email Confirmation - 'email_confirm_subject' => 'Confirm your email on :appName', - 'email_confirm_greeting' => 'Thanks for joining :appName!', - 'email_confirm_text' => 'Please confirm your email address by clicking the button below:', - 'email_confirm_action' => 'Confirm Email', + 'email_confirm_subject' => 'Cadarnhewch eich e-bost chi a :appName', + 'email_confirm_greeting' => 'Diolch am ymuno â :appName!', + 'email_confirm_text' => 'Os gwelwch yn dda cadarnhewch eich e-bost chi gan clicio ar y botwm isod:', + 'email_confirm_action' => 'Cadarnhau E-bost', 'email_confirm_send_error' => 'Email confirmation required but the system could not send the email. Contact the admin to ensure email is set up correctly.', 'email_confirm_success' => 'Your email has been confirmed! You should now be able to login using this email address.', 'email_confirm_resent' => 'Confirmation email resent, Please check your inbox.', - 'email_confirm_thanks' => 'Thanks for confirming!', + 'email_confirm_thanks' => 'Diolch am gadarnhau!', 'email_confirm_thanks_desc' => 'Please wait a moment while your confirmation is handled. If you are not redirected after 3 seconds press the "Continue" link below to proceed.', - 'email_not_confirmed' => 'Email Address Not Confirmed', - 'email_not_confirmed_text' => 'Your email address has not yet been confirmed.', + 'email_not_confirmed' => 'Cyfeiriad E-bost heb ei Gadarnhau', + 'email_not_confirmed_text' => 'Dyw eich cyfeiriad e-bost chi ddim wedi cael ei gadarnhau eto.', 'email_not_confirmed_click_link' => 'Please click the link in the email that was sent shortly after you registered.', 'email_not_confirmed_resend' => 'If you cannot find the email you can re-send the confirmation email by submitting the form below.', 'email_not_confirmed_resend_button' => 'Resend Confirmation Email', // User Invite 'user_invite_email_subject' => 'You have been invited to join :appName!', - 'user_invite_email_greeting' => 'An account has been created for you on :appName.', + 'user_invite_email_greeting' => 'Mae cyfrif wedi cae ei greu i chi ar :appName.', 'user_invite_email_text' => 'Click the button below to set an account password and gain access:', 'user_invite_email_action' => 'Set Account Password', - 'user_invite_page_welcome' => 'Welcome to :appName!', + 'user_invite_page_welcome' => 'Croeso i :appName!', 'user_invite_page_text' => 'To finalise your account and gain access you need to set a password which will be used to log-in to :appName on future visits.', - 'user_invite_page_confirm_button' => 'Confirm Password', + 'user_invite_page_confirm_button' => 'Cadarnhau cyfrinair', 'user_invite_success_login' => 'Password set, you should now be able to login using your set password to access :appName!', // Multi-factor Authentication @@ -88,7 +88,7 @@ return [ 'mfa_setup_remove_confirmation' => 'Are you sure you want to remove this multi-factor authentication method?', 'mfa_setup_action' => 'Setup', 'mfa_backup_codes_usage_limit_warning' => 'You have less than 5 backup codes remaining, Please generate and store a new set before you run out of codes to prevent being locked out of your account.', - 'mfa_option_totp_title' => 'Mobile App', + 'mfa_option_totp_title' => 'Ap Ffôn Symudol', 'mfa_option_totp_desc' => 'To use multi-factor authentication you\'ll need a mobile application that supports TOTP such as Google Authenticator, Authy or Microsoft Authenticator.', 'mfa_option_backup_codes_title' => 'Backup Codes', 'mfa_option_backup_codes_desc' => 'Securely store a set of one-time-use backup codes which you can enter to verify your identity.', diff --git a/lang/cy/common.php b/lang/cy/common.php index 27037babe..e359e8ba3 100644 --- a/lang/cy/common.php +++ b/lang/cy/common.php @@ -5,61 +5,61 @@ return [ // Buttons - 'cancel' => 'Cancel', - 'close' => 'Close', - 'confirm' => 'Confirm', - 'back' => 'Back', - 'save' => 'Save', - 'continue' => 'Continue', - 'select' => 'Select', + 'cancel' => 'Canslo', + 'close' => 'Cau', + 'confirm' => 'Cadarnhau', + 'back' => 'Yn ôl', + 'save' => 'Cadw', + 'continue' => 'Parhau', + 'select' => 'Dewis', 'toggle_all' => 'Toggle All', - 'more' => 'More', + 'more' => 'Mwy', // Form Labels - 'name' => 'Name', - 'description' => 'Description', + 'name' => 'Enw', + 'description' => 'Disgrifiad', 'role' => 'Role', 'cover_image' => 'Cover image', 'cover_image_description' => 'This image should be approx 440x250px.', // Actions - 'actions' => 'Actions', - 'view' => 'View', - 'view_all' => 'View All', - 'new' => 'New', - 'create' => 'Create', - 'update' => 'Update', - 'edit' => 'Edit', - 'sort' => 'Sort', - 'move' => 'Move', - 'copy' => 'Copy', - 'reply' => 'Reply', - 'delete' => 'Delete', - 'delete_confirm' => 'Confirm Deletion', - 'search' => 'Search', - 'search_clear' => 'Clear Search', - 'reset' => 'Reset', - 'remove' => 'Remove', - 'add' => 'Add', - 'configure' => 'Configure', - 'manage' => 'Manage', + 'actions' => 'Gweithredoedd', + 'view' => 'Gweld', + 'view_all' => 'Gweld popeth', + 'new' => 'Newydd', + 'create' => 'Creu', + 'update' => 'Diweddaru', + 'edit' => 'Golygu', + 'sort' => 'Trefnu', + 'move' => 'Symud', + 'copy' => 'Copïo', + 'reply' => 'Ateb', + 'delete' => 'Dileu', + 'delete_confirm' => 'Cadarnhau\'r dilead', + 'search' => 'Chwilio', + 'search_clear' => 'Clirio\'r chwiliad', + 'reset' => 'Ailosod', + 'remove' => 'Diddymu', + 'add' => 'Ychwanegu', + 'configure' => 'Ffurfweddu', + 'manage' => 'Rheoli', 'fullscreen' => 'Fullscreen', 'favourite' => 'Favourite', 'unfavourite' => 'Unfavourite', - 'next' => 'Next', - 'previous' => 'Previous', - 'filter_active' => 'Active Filter:', - 'filter_clear' => 'Clear Filter', - 'download' => 'Download', - 'open_in_tab' => 'Open in Tab', - 'open' => 'Open', + 'next' => 'Nesa', + 'previous' => 'Cynt', + 'filter_active' => 'Hidl weithredol:', + 'filter_clear' => 'Clirio\'r hidl', + 'download' => 'Llwytho i lawr', + 'open_in_tab' => 'Agor mewn Tab', + 'open' => 'Agor', // Sort Options - 'sort_options' => 'Sort Options', + 'sort_options' => 'Trefnu\'r opsiynau', 'sort_direction_toggle' => 'Sort Direction Toggle', - 'sort_ascending' => 'Sort Ascending', - 'sort_descending' => 'Sort Descending', - 'sort_name' => 'Name', + 'sort_ascending' => 'Trefnu\'n esgynnol', + 'sort_descending' => 'Trefnu\'n ddisgynnol', + 'sort_name' => 'Enw', 'sort_default' => 'Default', 'sort_created_at' => 'Created Date', 'sort_updated_at' => 'Updated Date', @@ -67,8 +67,8 @@ return [ // Misc 'deleted_user' => 'Deleted User', 'no_activity' => 'No activity to show', - 'no_items' => 'No items available', - 'back_to_top' => 'Back to top', + 'no_items' => 'Dim eitemau ar gael', + 'back_to_top' => 'Yn ôl i\'r brig', 'skip_to_main_content' => 'Skip to main content', 'toggle_details' => 'Toggle Details', 'toggle_thumbnails' => 'Toggle Thumbnails', @@ -78,10 +78,10 @@ return [ 'default' => 'Default', 'breadcrumb' => 'Breadcrumb', 'status' => 'Status', - 'status_active' => 'Active', - 'status_inactive' => 'Inactive', - 'never' => 'Never', - 'none' => 'None', + 'status_active' => 'Gweithredol', + 'status_inactive' => 'Anweithredol', + 'never' => 'Byth', + 'none' => 'Dim un', // Header 'homepage' => 'Homepage', @@ -94,9 +94,9 @@ return [ 'global_search' => 'Global Search', // Layout tabs - 'tab_info' => 'Info', + 'tab_info' => 'Gwybodaeth', 'tab_info_label' => 'Tab: Show Secondary Information', - 'tab_content' => 'Content', + 'tab_content' => 'Cynnwys', 'tab_content_label' => 'Tab: Show Primary Content', // Email Content diff --git a/lang/cy/components.php b/lang/cy/components.php index c33b1d0b7..9286c4563 100644 --- a/lang/cy/components.php +++ b/lang/cy/components.php @@ -6,28 +6,28 @@ return [ // Image Manager 'image_select' => 'Image Select', - 'image_list' => 'Image List', - 'image_details' => 'Image Details', + 'image_list' => 'Rhestr o Ddelweddau', + 'image_details' => 'Manylion Delwedd', 'image_upload' => 'Upload Image', 'image_intro' => 'Here you can select and manage images that have been previously uploaded to the system.', 'image_intro_upload' => 'Upload a new image by dragging an image file into this window, or by using the "Upload Image" button above.', - 'image_all' => 'All', - 'image_all_title' => 'View all images', + 'image_all' => 'Popeth', + 'image_all_title' => 'Gweld holl ddelweddau', 'image_book_title' => 'View images uploaded to this book', 'image_page_title' => 'View images uploaded to this page', - 'image_search_hint' => 'Search by image name', + 'image_search_hint' => 'Cwilio gan enw delwedd', 'image_uploaded' => 'Uploaded :uploadedDate', 'image_uploaded_by' => 'Uploaded by :userName', 'image_uploaded_to' => 'Uploaded to :pageLink', 'image_updated' => 'Updated :updateDate', 'image_load_more' => 'Load More', - 'image_image_name' => 'Image Name', + 'image_image_name' => 'Enw Delwedd', 'image_delete_used' => 'This image is used in the pages below.', - 'image_delete_confirm_text' => 'Are you sure you want to delete this image?', + 'image_delete_confirm_text' => 'Wyt ti\'n bendant eisiau dileu\'r ddelwedd hwn?', 'image_select_image' => 'Select Image', 'image_dropzone' => 'Drop images or click here to upload', 'image_dropzone_drop' => 'Drop images here to upload', - 'images_deleted' => 'Images Deleted', + 'images_deleted' => 'Delweddau wedi\'u Dileu', 'image_preview' => 'Image Preview', 'image_upload_success' => 'Image uploaded successfully', 'image_update_success' => 'Image details successfully updated', diff --git a/lang/cy/editor.php b/lang/cy/editor.php index 670c1c5e1..5ad569a8e 100644 --- a/lang/cy/editor.php +++ b/lang/cy/editor.php @@ -9,47 +9,47 @@ return [ // General editor terms 'general' => 'General', 'advanced' => 'Advanced', - 'none' => 'None', - 'cancel' => 'Cancel', - 'save' => 'Save', - 'close' => 'Close', - 'undo' => 'Undo', - 'redo' => 'Redo', - 'left' => 'Left', - 'center' => 'Center', - 'right' => 'Right', - 'top' => 'Top', - 'middle' => 'Middle', - 'bottom' => 'Bottom', - 'width' => 'Width', - 'height' => 'Height', - 'More' => 'More', - 'select' => 'Select...', + 'none' => 'Dim un', + 'cancel' => 'Canslo', + 'save' => 'Cadw', + 'close' => 'Cau', + 'undo' => 'Dadwneud', + 'redo' => 'Ail-wneud', + 'left' => 'Chwith', + 'center' => 'Canol', + 'right' => 'Dde', + 'top' => 'Pen', + 'middle' => 'Canol', + 'bottom' => 'Gwaelod', + 'width' => 'Lled', + 'height' => 'Uchder', + 'More' => 'Mwy', + 'select' => 'Dewis...', // Toolbar - 'formats' => 'Formats', - 'header_large' => 'Large Header', - 'header_medium' => 'Medium Header', - 'header_small' => 'Small Header', - 'header_tiny' => 'Tiny Header', - 'paragraph' => 'Paragraph', + 'formats' => 'Fformatau', + 'header_large' => 'Pennyn Mawr', + 'header_medium' => 'Pennyn Canolig', + 'header_small' => 'Pennyn Bach', + 'header_tiny' => 'Pennyn Mân', + 'paragraph' => 'Paragraff', 'blockquote' => 'Blockquote', 'inline_code' => 'Inline code', 'callouts' => 'Callouts', - 'callout_information' => 'Information', - 'callout_success' => 'Success', - 'callout_warning' => 'Warning', - 'callout_danger' => 'Danger', - 'bold' => 'Bold', - 'italic' => 'Italic', + 'callout_information' => 'Gwybodaeth', + 'callout_success' => 'Llwyddiant', + 'callout_warning' => 'Rhybudd', + 'callout_danger' => 'Perygl', + 'bold' => 'Trwm', + 'italic' => 'Italig', 'underline' => 'Underline', 'strikethrough' => 'Strikethrough', 'superscript' => 'Superscript', 'subscript' => 'Subscript', - 'text_color' => 'Text color', - 'custom_color' => 'Custom color', + 'text_color' => 'Lliw testun', + 'custom_color' => 'Lliw addasu', 'remove_color' => 'Remove color', - 'background_color' => 'Background color', + 'background_color' => 'Lliw cefnder', 'align_left' => 'Align left', 'align_center' => 'Align center', 'align_right' => 'Align right', @@ -112,18 +112,18 @@ return [ 'paste_row_before' => 'Paste row before', 'paste_row_after' => 'Paste row after', 'row_type' => 'Row type', - 'row_type_header' => 'Header', + 'row_type_header' => 'Pennyn', 'row_type_body' => 'Body', - 'row_type_footer' => 'Footer', - 'alignment' => 'Alignment', + 'row_type_footer' => 'Troedyn', + 'alignment' => 'Aliniad', 'cut_column' => 'Cut column', 'copy_column' => 'Copy column', 'paste_column_before' => 'Paste column before', 'paste_column_after' => 'Paste column after', 'cell_padding' => 'Cell padding', 'cell_spacing' => 'Cell spacing', - 'caption' => 'Caption', - 'show_caption' => 'Show caption', + 'caption' => 'Pennawd', + 'show_caption' => 'Dangos pennawd', 'constrain' => 'Constrain proportions', 'cell_border_solid' => 'Solid', 'cell_border_dotted' => 'Dotted', @@ -143,7 +143,7 @@ return [ 'paste_embed' => 'Paste your embed code below:', 'url' => 'URL', 'text_to_display' => 'Text to display', - 'title' => 'Title', + 'title' => 'Teitl', 'open_link' => 'Open link', 'open_link_in' => 'Open link in...', 'open_link_current' => 'Current window', @@ -170,5 +170,5 @@ return [ 'shortcuts_intro' => 'The following shortcuts are available in the editor:', 'windows_linux' => '(Windows/Linux)', 'mac' => '(Mac)', - 'description' => 'Description', + 'description' => 'Disgrifiad', ]; diff --git a/lang/cy/notifications.php b/lang/cy/notifications.php index 1afd23f1d..b9f58ac25 100644 --- a/lang/cy/notifications.php +++ b/lang/cy/notifications.php @@ -4,23 +4,23 @@ */ return [ - 'new_comment_subject' => 'New comment on page: :pageName', - 'new_comment_intro' => 'A user has commented on a page in :appName:', - 'new_page_subject' => 'New page: :pageName', - 'new_page_intro' => 'A new page has been created in :appName:', - 'updated_page_subject' => 'Updated page: :pageName', - 'updated_page_intro' => 'A page has been updated in :appName:', + 'new_comment_subject' => 'Sylw newydd ar dudalen :pageName', + 'new_comment_intro' => 'Mae defnyddiwr wedi sylw ar dudalen yn :appName:', + 'new_page_subject' => 'Tudalen newydd :pageName', + 'new_page_intro' => 'Mae tudalen newydd wedi cael ei chreu yn :appName:', + 'updated_page_subject' => 'Tudalen wedi\'i diweddaru :pageName', + 'updated_page_intro' => 'Mae tudalen newydd wedi cael ei diweddaru yn :appName:', 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', - 'detail_page_name' => 'Page Name:', + 'detail_page_name' => 'Enw\'r dudalen:', 'detail_page_path' => 'Page Path:', 'detail_commenter' => 'Commenter:', - 'detail_comment' => 'Comment:', + 'detail_comment' => 'Sylw:', 'detail_created_by' => 'Created By:', 'detail_updated_by' => 'Updated By:', - 'action_view_comment' => 'View Comment', - 'action_view_page' => 'View Page', + 'action_view_comment' => 'Gweld y sylw', + 'action_view_page' => 'Gweld y dudalen', 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', 'footer_reason_link' => 'your notification preferences', diff --git a/lang/cy/pagination.php b/lang/cy/pagination.php index 85bd12fc3..9cd781925 100644 --- a/lang/cy/pagination.php +++ b/lang/cy/pagination.php @@ -6,7 +6,7 @@ */ return [ - 'previous' => '« Previous', - 'next' => 'Next »', + 'previous' => '« Cynt', + 'next' => 'Nesa »', ]; diff --git a/lang/fr/entities.php b/lang/fr/entities.php index e890c2787..32351548b 100644 --- a/lang/fr/entities.php +++ b/lang/fr/entities.php @@ -23,7 +23,7 @@ return [ 'meta_updated' => 'Mis à jour :timeLength', 'meta_updated_name' => 'Mis à jour :timeLength par :user', 'meta_owned_name' => 'Appartient à :user', - 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', + 'meta_reference_count' => 'Référencé sur :count élément|Référencé sur :count éléments', 'entity_select' => 'Sélectionner l\'entité', 'entity_select_lack_permission' => 'Vous n\'avez pas les permissions requises pour sélectionner cet élément', 'images' => 'Images', @@ -132,9 +132,9 @@ return [ 'books_edit_named' => 'Modifier le livre :bookName', 'books_form_book_name' => 'Nom du livre', 'books_save' => 'Enregistrer le livre', - 'books_default_template' => 'Default Page Template', - 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', - 'books_default_template_select' => 'Select a template page', + 'books_default_template' => 'Modèle de page par défaut', + 'books_default_template_explain' => 'Sélectionnez un modèle de page qui sera utilisé comme contenu par défaut pour les nouvelles pages créées dans ce livre. Gardez à l\'esprit que le modèle ne sera utilisé que si le créateur de la page a accès au modèle sélectionné.', + 'books_default_template_select' => 'Sélectionnez un modèle de page', 'books_permissions' => 'Permissions du livre', 'books_permissions_updated' => 'Permissions du livre mises à jour', 'books_empty_contents' => 'Aucune page ou chapitre n\'a été ajouté à ce livre.', @@ -207,7 +207,7 @@ return [ 'pages_delete_draft' => 'Supprimer le brouillon', 'pages_delete_success' => 'Page supprimée', 'pages_delete_draft_success' => 'Brouillon supprimé', - 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', + 'pages_delete_warning_template' => 'Cette page est utilisée comme modèle de page par défaut dans un livre. Ces livres n\'utiliseront plus ce modèle après la suppression de cette page.', 'pages_delete_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer cette page ?', 'pages_delete_draft_confirm' => 'Êtes-vous sûr(e) de vouloir supprimer ce brouillon ?', 'pages_editing_named' => 'Modification de la page :pageName', @@ -409,7 +409,7 @@ return [ // References 'references' => 'Références', 'references_none' => 'Il n\'y a pas de références suivies à cet élément.', - 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', + 'references_to_desc' => 'Vous trouverez ci-dessous le contenu connu du système qui a un lien vers cet élément.', // Watch Options 'watch' => 'Suivre', diff --git a/lang/fr/notifications.php b/lang/fr/notifications.php index eb97ddfda..5d7ae88f7 100644 --- a/lang/fr/notifications.php +++ b/lang/fr/notifications.php @@ -13,7 +13,7 @@ return [ 'updated_page_debounce' => 'Pour éviter de nombreuses notifications, pendant un certain temps, vous ne recevrez pas de notifications pour d\'autres modifications de cette page par le même éditeur.', 'detail_page_name' => 'Nom de la page :', - 'detail_page_path' => 'Page Path:', + 'detail_page_path' => 'Chemin de la page :', 'detail_commenter' => 'Commenta·teur·trice :', 'detail_comment' => 'Commentaire :', 'detail_created_by' => 'Créé par :', diff --git a/lang/he/common.php b/lang/he/common.php index 75dbd4493..dafafe6ed 100644 --- a/lang/he/common.php +++ b/lang/he/common.php @@ -6,7 +6,7 @@ return [ // Buttons 'cancel' => 'ביטול', - 'close' => 'Close', + 'close' => 'סגור', 'confirm' => 'אישור', 'back' => 'אחורה', 'save' => 'שמור', @@ -42,7 +42,7 @@ return [ 'remove' => 'הסר', 'add' => 'הוסף', 'configure' => 'הגדרות', - 'manage' => 'Manage', + 'manage' => 'נהל', 'fullscreen' => 'מסך מלא', 'favourite' => 'מועדף', 'unfavourite' => 'בטל מועדף', diff --git a/lang/he/notifications.php b/lang/he/notifications.php index 1afd23f1d..8385c0a6d 100644 --- a/lang/he/notifications.php +++ b/lang/he/notifications.php @@ -4,24 +4,24 @@ */ return [ - 'new_comment_subject' => 'New comment on page: :pageName', - 'new_comment_intro' => 'A user has commented on a page in :appName:', - 'new_page_subject' => 'New page: :pageName', - 'new_page_intro' => 'A new page has been created in :appName:', - 'updated_page_subject' => 'Updated page: :pageName', - 'updated_page_intro' => 'A page has been updated in :appName:', - 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'new_comment_subject' => 'תגובה חדשה בדף: :pageName', + 'new_comment_intro' => 'משתמש רשם על עמוד ב :appName:', + 'new_page_subject' => 'עמוד חדש: PageName', + 'new_page_intro' => 'עמוד חדש נפתח ב:appName:', + 'updated_page_subject' => 'עמוד עודכן :pageName', + 'updated_page_intro' => 'דף עודכן ב:appName:', + 'updated_page_debounce' => 'על מנת לעצור הצפת התראות, לזמן מסוים אתה לא תקבל התראות על שינויים עתידיים בדף זה על ידי אותו עורך.', - 'detail_page_name' => 'Page Name:', - 'detail_page_path' => 'Page Path:', - 'detail_commenter' => 'Commenter:', - 'detail_comment' => 'Comment:', - 'detail_created_by' => 'Created By:', - 'detail_updated_by' => 'Updated By:', + 'detail_page_name' => 'שם עמוד:', + 'detail_page_path' => 'נתיב לעמוד:', + 'detail_commenter' => 'יוצר התגובה:', + 'detail_comment' => 'תגובה:', + 'detail_created_by' => 'נוצר על ידי:', + 'detail_updated_by' => 'עודכן על ידי:', - 'action_view_comment' => 'View Comment', - 'action_view_page' => 'View Page', + 'action_view_comment' => 'צפה בתגובה', + 'action_view_page' => 'הצג דף', - 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', - 'footer_reason_link' => 'your notification preferences', + 'footer_reason' => 'ההתראה נשלחה אליך בגלל :link לכסות סוג זה של פעילות עבור פריט זה.', + 'footer_reason_link' => 'העדפות ההתראות שלך', ]; diff --git a/lang/ko/activities.php b/lang/ko/activities.php index f9002431b..4cb03f90c 100644 --- a/lang/ko/activities.php +++ b/lang/ko/activities.php @@ -6,75 +6,75 @@ return [ // Pages - 'page_create' => '문서 만들기', - 'page_create_notification' => '페이지를 생성했습니다', - 'page_update' => '문서 수정', - 'page_update_notification' => '페이지를 수정했습니다', - 'page_delete' => '삭제 된 페이지', - 'page_delete_notification' => '페이지를 삭제했습니다', - 'page_restore' => '문서 복원', - 'page_restore_notification' => '페이지가 복원되었습니다', - 'page_move' => '문서 이동', - 'page_move_notification' => 'Page successfully moved', + 'page_create' => '생성된 페이지', + 'page_create_notification' => '페이지가 성공적으로 생성되었습니다.', + 'page_update' => '페이지 업데이트됨', + 'page_update_notification' => '페이지가 성공적으로 업데이트되었습니다.', + 'page_delete' => '삭제된 페이지', + 'page_delete_notification' => '페이지가 성공적으로 삭제되었습니다.', + 'page_restore' => '복구된 페이지', + 'page_restore_notification' => '페이지가 성공적으로 복원되었습니다.', + 'page_move' => '이동된 페이지', + 'page_move_notification' => '페이지가 성공적으로 이동되었습니다.', // Chapters 'chapter_create' => '챕터 만들기', - 'chapter_create_notification' => '챕터 생성함', - 'chapter_update' => '챕터 수정', - 'chapter_update_notification' => '챕터 수정함', + 'chapter_create_notification' => '챕터가 성공적으로 생성되었습니다.', + 'chapter_update' => '업데이트된 챕터', + 'chapter_update_notification' => '챕터가 성공적으로 업데이트되었습니다.', 'chapter_delete' => '삭제된 챕터', - 'chapter_delete_notification' => '챕터 삭제함', - 'chapter_move' => '챕터 이동된', - 'chapter_move_notification' => 'Chapter successfully moved', + 'chapter_delete_notification' => '챕터가 성공적으로 삭제되었습니다.', + 'chapter_move' => '이동된 챕터', + 'chapter_move_notification' => '챕터를 성공적으로 이동했습니다.', // Books - 'book_create' => '책자 만들기', - 'book_create_notification' => '책 생성함', + 'book_create' => '생성된 책', + 'book_create_notification' => '책이 성공적으로 생성되었습니다.', 'book_create_from_chapter' => '챕터를 책으로 변환', - 'book_create_from_chapter_notification' => '챕터를 책으로 변환했습니다.', - 'book_update' => '책 수정', - 'book_update_notification' => '책 수정함', - 'book_delete' => '책 지우기', - 'book_delete_notification' => '책 삭제함', - 'book_sort' => '책자 정렬', - 'book_sort_notification' => '책 정렬 바꿈', + 'book_create_from_chapter_notification' => '챕터가 책으로 성공적으로 변환되었습니다.', + 'book_update' => '업데이트된 책', + 'book_update_notification' => '책이 성공적으로 업데이트되었습니다.', + 'book_delete' => '삭제된 책', + 'book_delete_notification' => '책이 성공적으로 삭제되었습니다.', + 'book_sort' => '정렬된 책', + 'book_sort_notification' => '책이 성공적으로 재정렬되었습니다.', // Bookshelves - 'bookshelf_create' => '책꽃이 만들기', - 'bookshelf_create_notification' => '책꽃이 생성 완료', - 'bookshelf_create_from_book' => '책을 책꽃이로 변환', - 'bookshelf_create_from_book_notification' => '책을 책꽂이로 변환했습니다.', - 'bookshelf_update' => '책꽃이 업데이트', - 'bookshelf_update_notification' => '책꽃이가 업데이트되었습니다', - 'bookshelf_delete' => '선반 삭제', - 'bookshelf_delete_notification' => '책꽃이가 삭제되었습니다.', + 'bookshelf_create' => '책장 만들기', + 'bookshelf_create_notification' => '책장을 성공적으로 생성했습니다.', + 'bookshelf_create_from_book' => '책을 책장으로 변환함', + 'bookshelf_create_from_book_notification' => '책을 성공적으로 책장으로 변환하였습니다.', + 'bookshelf_update' => '책장 업데이트됨', + 'bookshelf_update_notification' => '책장이 성공적으로 업데이트 되었습니다.', + 'bookshelf_delete' => '삭제된 책장', + 'bookshelf_delete_notification' => '책장이 성공적으로 삭제되었습니다.', // Revisions - 'revision_restore' => '버전 복원', - 'revision_delete' => '버전 삭제', - 'revision_delete_notification' => '버전 삭제 성공', + 'revision_restore' => '복구된 리비전', + 'revision_delete' => '삭제된 리비전', + 'revision_delete_notification' => '리비전을 성공적으로 삭제하였습니다.', // Favourites - 'favourite_add_notification' => '":name" 북마크에 추가함', - 'favourite_remove_notification' => '":name" 북마크에서 삭제함', + 'favourite_add_notification' => '":name" 을 북마크에 추가하였습니다.', + 'favourite_remove_notification' => '":name" 가 북마크에서 삭제되었습니다.', // Watching - 'watch_update_level_notification' => 'Watch preferences successfully updated', + 'watch_update_level_notification' => '주시 환경설정이 성공적으로 업데이트되었습니다.', // Auth - 'auth_login' => 'logged in', + 'auth_login' => '로그인 됨', 'auth_register' => '신규 사용자 등록', 'auth_password_reset_request' => '사용자 비밀번호 초기화 요청', 'auth_password_reset_update' => '사용자 비밀번호 초기화', - 'mfa_setup_method' => 'configured MFA method', + 'mfa_setup_method' => '구성된 MFA 방법', 'mfa_setup_method_notification' => '다중 인증 설정함', - 'mfa_remove_method' => 'removed MFA method', + 'mfa_remove_method' => 'MFA 메서드 제거', 'mfa_remove_method_notification' => '다중 인증 해제함', // Settings 'settings_update' => '설정 변경', 'settings_update_notification' => '설졍 변경 성공', - 'maintenance_action_run' => 'ran maintenance action', + 'maintenance_action_run' => '유지 관리 작업 실행', // Webhooks 'webhook_create' => '웹 훅 만들기', @@ -94,11 +94,11 @@ return [ // API Tokens 'api_token_create' => 'aPI 토큰 생성', - 'api_token_create_notification' => 'API token successfully created', - 'api_token_update' => 'updated api token', - 'api_token_update_notification' => 'API token successfully updated', - 'api_token_delete' => 'deleted api token', - 'api_token_delete_notification' => 'API token successfully deleted', + 'api_token_create_notification' => 'API 토큰이 성공적으로 생성되었습니다.', + 'api_token_update' => '업데이트된 API 토큰', + 'api_token_update_notification' => 'API 토큰이 성공적으로 업데이트되었습니다.', + 'api_token_delete' => '삭제된 API 토큰', + 'api_token_delete_notification' => 'API 토큰이 성공적으로 삭제되었습니다.', // Roles 'role_create' => '역활 생성', @@ -109,9 +109,9 @@ return [ 'role_delete_notification' => '역할이 삭제되었습니다', // Recycle Bin - 'recycle_bin_empty' => 'emptied recycle bin', - 'recycle_bin_restore' => 'restored from recycle bin', - 'recycle_bin_destroy' => 'removed from recycle bin', + 'recycle_bin_empty' => '비운 휴지통', + 'recycle_bin_restore' => '휴지통에서 복원됨', + 'recycle_bin_destroy' => '휴지통에서 제거됨', // Comments 'commented_on' => '댓글 쓰기', diff --git a/lang/ko/auth.php b/lang/ko/auth.php index 60a54dabd..a29f9b9ad 100644 --- a/lang/ko/auth.php +++ b/lang/ko/auth.php @@ -29,7 +29,7 @@ return [ 'already_have_account' => '계정이 있나요?', 'dont_have_account' => '계정이 없나요?', 'social_login' => '소셜 로그인', - 'social_registration' => '소셜 가입', + 'social_registration' => '소셜 계정으로 가입', 'social_registration_text' => '소셜 계정으로 가입하고 로그인합니다.', 'register_thanks' => '가입해 주셔서 감사합니다!', diff --git a/lang/ko/common.php b/lang/ko/common.php index 89ae78bd3..794fdc6cd 100644 --- a/lang/ko/common.php +++ b/lang/ko/common.php @@ -6,7 +6,7 @@ return [ // Buttons 'cancel' => '취소', - 'close' => 'Close', + 'close' => '닫기', 'confirm' => '확인', 'back' => '뒤로', 'save' => '저장', @@ -26,7 +26,7 @@ return [ 'actions' => '활동', 'view' => '보기', 'view_all' => '모두 보기', - 'new' => 'New', + 'new' => '신규', 'create' => '만들기', 'update' => '바꾸기', 'edit' => '수정', @@ -42,7 +42,7 @@ return [ 'remove' => '제거', 'add' => '추가', 'configure' => '설정', - 'manage' => 'Manage', + 'manage' => '관리', 'fullscreen' => '전체화면', 'favourite' => '즐겨찾기', 'unfavourite' => '즐겨찾기 해제', @@ -52,7 +52,7 @@ return [ 'filter_clear' => '모든 필터 해제', 'download' => '내려받기', 'open_in_tab' => '탭에서 열기', - 'open' => 'Open', + 'open' => '열기 ', // Sort Options 'sort_options' => '정렬 기준', diff --git a/lang/ko/components.php b/lang/ko/components.php index 66e1bde18..c81497e03 100644 --- a/lang/ko/components.php +++ b/lang/ko/components.php @@ -9,18 +9,18 @@ return [ 'image_list' => '이미지 목록', 'image_details' => '이미지 상세정보', 'image_upload' => '이미지 업로드', - 'image_intro' => 'Here you can select and manage images that have been previously uploaded to the system.', - 'image_intro_upload' => 'Upload a new image by dragging an image file into this window, or by using the "Upload Image" button above.', + 'image_intro' => '여기에서 이전에 시스템에 업로드한 이미지를 선택하고 관리할 수 있습니다.', + 'image_intro_upload' => '이미지 파일을 이 창으로 끌어다 놓거나 위의 \'이미지 업로드\' 버튼을 사용하여 새 이미지를 업로드합니다.', 'image_all' => '모든 이미지', 'image_all_title' => '모든 이미지', 'image_book_title' => '이 책에서 쓰고 있는 이미지', 'image_page_title' => '이 문서에서 쓰고 있는 이미지', 'image_search_hint' => '이미지 이름 검색', 'image_uploaded' => '올림 :uploadedDate', - 'image_uploaded_by' => '업로드:유저 닉네임', - 'image_uploaded_to' => '업로드:링크', - 'image_updated' => '갱신:갱신일', - 'image_load_more' => '더 로드하기', + 'image_uploaded_by' => '업로드 :userName', + 'image_uploaded_to' => ':pageLink 로 업로드됨', + 'image_updated' => '갱신일 :updateDate', + 'image_load_more' => '더 보기', 'image_image_name' => '이미지 이름', 'image_delete_used' => '이 이미지는 다음 문서들이 쓰고 있습니다.', 'image_delete_confirm_text' => '이 이미지를 지울 건가요?', @@ -33,9 +33,9 @@ return [ 'image_update_success' => '이미지 정보 수정함', 'image_delete_success' => '이미지 삭제함', 'image_replace' => '이미지 교체', - 'image_replace_success' => 'Image file successfully updated', - 'image_rebuild_thumbs' => 'Regenerate Size Variations', - 'image_rebuild_thumbs_success' => 'Image size variations successfully rebuilt!', + 'image_replace_success' => '이미지 파일 업데이트 성공', + 'image_rebuild_thumbs' => '사이즈 변경 재생성하기', + 'image_rebuild_thumbs_success' => '이미지 크기 변경이 성공적으로 완료되었습니다!', // Code Editor 'code_editor' => '코드 수정', diff --git a/lang/ko/editor.php b/lang/ko/editor.php index 9764c7d35..7959bc81d 100644 --- a/lang/ko/editor.php +++ b/lang/ko/editor.php @@ -150,7 +150,7 @@ return [ 'open_link_new' => '새 창', 'remove_link' => '링크 제거', 'insert_collapsible' => '접을 수 있는 블록 삽입', - 'collapsible_unwrap' => 'Unwrap', + 'collapsible_unwrap' => '언래핑', 'edit_label' => '레이블 수정', 'toggle_open_closed' => '열림/닫힘 전환', 'collapsible_edit' => '접을 수 있는 블록 편집', @@ -163,8 +163,8 @@ return [ 'editor_tiny_license' => '이 편집기는 MIT 라이선스에 따라 제공되는 :tinyLink를 사용하여 제작되었습니다.', 'editor_tiny_license_link' => 'TinyMCE의 저작권 및 라이선스 세부 정보는 여기에서 확인할 수 있습니다.', 'save_continue' => '저장하고 계속하기', - 'callouts_cycle' => '(Keep pressing to toggle through types)', - 'link_selector' => 'Link to content', + 'callouts_cycle' => '(계속 누르면 유형이 전환됩니다.)', + 'link_selector' => '콘텐츠에 연결', 'shortcuts' => '단축키', 'shortcut' => '단축키', 'shortcuts_intro' => '편집기에서 사용할 수 있는 바로 가기는 다음과 같습니다:', diff --git a/lang/ko/entities.php b/lang/ko/entities.php index cd56d3ed8..934d7201e 100644 --- a/lang/ko/entities.php +++ b/lang/ko/entities.php @@ -11,7 +11,7 @@ return [ 'recently_updated_pages' => '최근에 수정한 문서', 'recently_created_chapters' => '최근에 만든 챕터', 'recently_created_books' => '최근에 만든 책', - 'recently_created_shelves' => '최근에 만든 책꽂이', + 'recently_created_shelves' => '최근에 만든 책장', 'recently_update' => '최근에 수정함', 'recently_viewed' => '최근에 읽음', 'recent_activity' => '최근에 활동함', @@ -23,34 +23,34 @@ return [ 'meta_updated' => '수정함 :timeLength', 'meta_updated_name' => '수정함 :timeLength, :user', 'meta_owned_name' => '소유함 :user', - 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', + 'meta_reference_count' => '참조 대상 :count item|참조 대상 :count items', 'entity_select' => '항목 선택', 'entity_select_lack_permission' => '이 항목을 선택하기 위해 필요한 권한이 없습니다', 'images' => '이미지', 'my_recent_drafts' => '내 최근의 초안 문서', 'my_recently_viewed' => '내가 읽은 문서', - 'my_most_viewed_favourites' => '많이 본 북마크', - 'my_favourites' => '북마크', - 'no_pages_viewed' => '문서 없음', - 'no_pages_recently_created' => '문서 없음', - 'no_pages_recently_updated' => '문서 없음', - 'export' => '파일로 받기', - 'export_html' => 'Contained Web(.html) 파일', + 'my_most_viewed_favourites' => '내가 가장 많이 본 즐겨찾기', + 'my_favourites' => '나의 즐겨찾기', + 'no_pages_viewed' => '본 페이지가 없습니다.', + 'no_pages_recently_created' => '최근에 생성된 페이지가 없습니다.', + 'no_pages_recently_updated' => '최근 업데이트된 페이지가 없습니다.', + 'export' => '내보내기', + 'export_html' => '포함된 웹 파일', 'export_pdf' => 'PDF 파일', - 'export_text' => 'Plain Text(.txt) 파일', - 'export_md' => 'Markdown(.md) 파일', + 'export_text' => '일반 텍스트 파일', + 'export_md' => '마크다운 파일', // Permissions and restrictions 'permissions' => '권한', - 'permissions_desc' => 'Set permissions here to override the default permissions provided by user roles.', - 'permissions_book_cascade' => 'Permissions set on books will automatically cascade to child chapters and pages, unless they have their own permissions defined.', - 'permissions_chapter_cascade' => 'Permissions set on chapters will automatically cascade to child pages, unless they have their own permissions defined.', + 'permissions_desc' => '여기에서 권한을 설정하여 사용자 역할에서 제공하는 기본 권한을 재정의합니다.', + 'permissions_book_cascade' => '책에 설정된 권한은 자체 권한이 정의되어 있지 않은 한 하위 챕터와 페이지에 자동으로 계단식으로 적용됩니다.', + 'permissions_chapter_cascade' => '챕터에 설정된 권한은 하위 페이지에 자체 권한이 정의되어 있지 않는 한 자동으로 계단식으로 적용됩니다.', 'permissions_save' => '권한 저장', 'permissions_owner' => '소유자', - 'permissions_role_everyone_else' => 'Everyone Else', - 'permissions_role_everyone_else_desc' => 'Set permissions for all roles not specifically overridden.', - 'permissions_role_override' => 'Override permissions for role', - 'permissions_inherit_defaults' => 'Inherit defaults', + 'permissions_role_everyone_else' => '그 외 모든 사용자', + 'permissions_role_everyone_else_desc' => '특별히 재정의되지 않은 모든 역할에 대한 권한을 설정.', + 'permissions_role_override' => '역할에 대한 권한 재정의하기', + 'permissions_inherit_defaults' => '기본값 상속', // Search 'search_results' => '검색 결과', @@ -96,21 +96,21 @@ return [ 'shelves_drag_books' => '책을 이 책장에 추가하려면 아래로 드래그하세요', 'shelves_empty_contents' => '이 책꽂이에 책이 없습니다.', 'shelves_edit_and_assign' => '책꽂이 바꾸기로 책을 추가하세요.', - 'shelves_edit_named' => 'Edit Shelf :name', - 'shelves_edit' => 'Edit Shelf', - 'shelves_delete' => 'Delete Shelf', - 'shelves_delete_named' => 'Delete Shelf :name', - 'shelves_delete_explain' => "This will delete the shelf with the name ':name'. Contained books will not be deleted.", - 'shelves_delete_confirmation' => 'Are you sure you want to delete this shelf?', - 'shelves_permissions' => 'Shelf Permissions', - 'shelves_permissions_updated' => 'Shelf Permissions Updated', - 'shelves_permissions_active' => 'Shelf Permissions Active', - 'shelves_permissions_cascade_warning' => 'Permissions on shelves do not automatically cascade to contained books. This is because a book can exist on multiple shelves. Permissions can however be copied down to child books using the option found below.', - 'shelves_permissions_create' => 'Shelf create permissions are only used for copying permissions to child books using the action below. They do not control the ability to create books.', + 'shelves_edit_named' => '책꽂이 편집 :name', + 'shelves_edit' => '책꽂이 편집', + 'shelves_delete' => '책꽂이 삭제', + 'shelves_delete_named' => '책꽂이 삭제 :이름', + 'shelves_delete_explain' => "그러면 ':name'이라는 이름의 서가가 삭제됩니다. 포함된 책은 삭제되지 않습니다.", + 'shelves_delete_confirmation' => '이 책꽂이를 삭제하시겠습니까?', + 'shelves_permissions' => '책꽂이 권한', + 'shelves_permissions_updated' => '책꽂이 권한 업데이트됨', + 'shelves_permissions_active' => '책꽂이 권한 활성화', + 'shelves_permissions_cascade_warning' => '책꽂이에 대한 권한은 포함된 책에 자동으로 계단식으로 부여되지 않습니다. 한 권의 책이 여러 개의 책꽂이에 존재할 수 있기 때문입니다. 그러나 아래 옵션을 사용하여 권한을 하위 책으로 복사할 수 있습니다.', + 'shelves_permissions_create' => '책꽂이 만들기 권한은 아래 작업을 사용하여 하위 책에 대한 권한을 복사하는 데만 사용됩니다. 책을 만드는 기능은 제어하지 않습니다.', 'shelves_copy_permissions_to_books' => '권한 맞춤', 'shelves_copy_permissions' => '실행', - 'shelves_copy_permissions_explain' => 'This will apply the current permission settings of this shelf to all books contained within. Before activating, ensure any changes to the permissions of this shelf have been saved.', - 'shelves_copy_permission_success' => 'Shelf permissions copied to :count books', + 'shelves_copy_permissions_explain' => '그러면 이 책꽂이의 현재 권한 설정이 이 책꽂이에 포함된 모든 책에 적용됩니다. 활성화하기 전에 이 책꽂이의 권한에 대한 변경 사항이 모두 저장되었는지 확인하세요.', + 'shelves_copy_permission_success' => '책꽂이 권한이 복사됨 :count books', // Books 'book' => '책', @@ -132,9 +132,9 @@ return [ 'books_edit_named' => ':bookName(을)를 바꿉니다.', 'books_form_book_name' => '책 이름', 'books_save' => '저장', - 'books_default_template' => 'Default Page Template', - 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', - 'books_default_template_select' => 'Select a template page', + 'books_default_template' => '기본 페이지 템플릿', + 'books_default_template_explain' => '이 책에 있는 모든 새 페이지의 기본 콘텐츠로 사용할 페이지 템플릿을 지정합니다. 페이지 작성자에게 선택한 템플릿 페이지에 대한 보기 액세스 권한이 있는 경우에만 이 템플릿이 사용된다는 점에 유의하세요.', + 'books_default_template_select' => '템플릿 페이지 선택', 'books_permissions' => '책 권한', 'books_permissions_updated' => '권한 저장함', 'books_empty_contents' => '이 책에 챕터나 문서가 없습니다.', @@ -145,7 +145,7 @@ return [ 'books_search_this' => '이 책에서 검색', 'books_navigation' => '목차', 'books_sort' => '다른 책들', - 'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books.', + 'books_sort_desc' => '책 내에서 챕터와 페이지를 이동하여 내용을 재구성할 수 있습니다. 다른 책을 추가하여 책 간에 챕터와 페이지를 쉽게 이동할 수 있습니다.', 'books_sort_named' => ':bookName 정렬', 'books_sort_name' => '제목', 'books_sort_created' => '만든 날짜', @@ -154,17 +154,17 @@ return [ 'books_sort_chapters_last' => '문서 우선', 'books_sort_show_other' => '다른 책들', 'books_sort_save' => '적용', - 'books_sort_show_other_desc' => 'Add other books here to include them in the sort operation, and allow easy cross-book reorganisation.', - 'books_sort_move_up' => 'Move Up', - 'books_sort_move_down' => 'Move Down', - 'books_sort_move_prev_book' => 'Move to Previous Book', - 'books_sort_move_next_book' => 'Move to Next Book', - 'books_sort_move_prev_chapter' => 'Move Into Previous Chapter', - 'books_sort_move_next_chapter' => 'Move Into Next Chapter', - 'books_sort_move_book_start' => 'Move to Start of Book', - 'books_sort_move_book_end' => 'Move to End of Book', - 'books_sort_move_before_chapter' => 'Move to Before Chapter', - 'books_sort_move_after_chapter' => 'Move to After Chapter', + 'books_sort_show_other_desc' => '여기에 다른 책을 추가하여 정렬 작업에 포함시키고 책 간 재구성을 쉽게 할 수 있습니다.', + 'books_sort_move_up' => '위로 이동', + 'books_sort_move_down' => '아래로 이동', + 'books_sort_move_prev_book' => '이전 책으로 이동', + 'books_sort_move_next_book' => '다음 책으로 이동', + 'books_sort_move_prev_chapter' => '이전 챕터로 이동', + 'books_sort_move_next_chapter' => '다음 챕터로 이동', + 'books_sort_move_book_start' => '책 시작 부분으로 이동', + 'books_sort_move_book_end' => '책의 끝으로 이동', + 'books_sort_move_before_chapter' => '이전 챕터로 이동', + 'books_sort_move_after_chapter' => '챕터 뒤로 이동', 'books_copy' => '책 복사하기', 'books_copy_success' => '책 복사함', @@ -207,7 +207,7 @@ return [ 'pages_delete_draft' => '초안 문서 삭제하기', 'pages_delete_success' => '문서 지움', 'pages_delete_draft_success' => '초안 문서 지움', - 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', + 'pages_delete_warning_template' => '이 페이지는 책의 기본 페이지 템플릿으로 사용 중입니다. 이 페이지가 삭제된 후에는 이러한 책에 더 이상 기본 페이지 템플릿이 할당되지 않습니다.', 'pages_delete_confirm' => '이 문서를 지울 건가요?', 'pages_delete_draft_confirm' => '이 초안을 지울 건가요?', 'pages_editing_named' => ':pageName 수정', @@ -218,7 +218,7 @@ return [ 'pages_editing_page' => '문서 수정', 'pages_edit_draft_save_at' => '보관함: ', 'pages_edit_delete_draft' => '초안 삭제', - 'pages_edit_delete_draft_confirm' => 'Are you sure you want to delete your draft page changes? All of your changes, since the last full save, will be lost and the editor will be updated with the latest page non-draft save state.', + 'pages_edit_delete_draft_confirm' => '초안 페이지 변경 내용을 삭제하시겠습니까? 마지막 전체 저장 이후의 모든 변경 내용이 손실되고 편집기가 최신 페이지의 초안 저장 상태가 아닌 상태로 업데이트됩니다.', 'pages_edit_discard_draft' => '폐기', 'pages_edit_switch_to_markdown' => '마크다운 편집기로 전환', 'pages_edit_switch_to_markdown_clean' => '(Clean Content)', @@ -230,9 +230,9 @@ return [ 'pages_editor_switch_title' => '편집기 전환', 'pages_editor_switch_are_you_sure' => '이 페이지의 편집기를 변경하시겠어요?', 'pages_editor_switch_consider_following' => '편집기를 전환할 때에 다음 사항들을 고려하세요:', - 'pages_editor_switch_consideration_a' => 'Once saved, the new editor option will be used by any future editors, including those that may not be able to change editor type themselves.', - 'pages_editor_switch_consideration_b' => 'This can potentially lead to a loss of detail and syntax in certain circumstances.', - 'pages_editor_switch_consideration_c' => 'Tag or changelog changes, made since last save, won\'t persist across this change.', + 'pages_editor_switch_consideration_a' => '저장된 새 편집기 옵션은 편집기 유형을 직접 변경할 수 없는 편집자를 포함하여 향후 모든 편집자가 사용할 수 있습니다.', + 'pages_editor_switch_consideration_b' => '이로 인해 특정 상황에서는 디테일과 구문이 손실될 수 있습니다.', + 'pages_editor_switch_consideration_c' => '마지막 저장 이후 변경된 태그 또는 변경 로그 변경 사항은 이 변경 사항에서 유지되지 않습니다.', 'pages_save' => '저장', 'pages_title' => '문서 제목', 'pages_name' => '문서 이름', @@ -241,10 +241,10 @@ return [ 'pages_md_insert_image' => '이미지 추가', 'pages_md_insert_link' => '내부 링크', 'pages_md_insert_drawing' => '드로잉 추가', - 'pages_md_show_preview' => 'Show preview', - 'pages_md_sync_scroll' => 'Sync preview scroll', - 'pages_drawing_unsaved' => 'Unsaved Drawing Found', - 'pages_drawing_unsaved_confirm' => 'Unsaved drawing data was found from a previous failed drawing save attempt. Would you like to restore and continue editing this unsaved drawing?', + 'pages_md_show_preview' => '미리보기 표시', + 'pages_md_sync_scroll' => '미리보기 스크롤 동기화', + 'pages_drawing_unsaved' => '저장되지 않은 드로잉 발견', + 'pages_drawing_unsaved_confirm' => '이전에 실패한 드로잉 저장 시도에서 저장되지 않은 드로잉 데이터가 발견되었습니다. 이 저장되지 않은 드로잉을 복원하고 계속 편집하시겠습니까?', 'pages_not_in_chapter' => '챕터에 있는 문서가 아닙니다.', 'pages_move' => '문서 이동하기', 'pages_copy' => '문서 복제', @@ -254,14 +254,14 @@ return [ 'pages_permissions_success' => '문서 권한 바꿈', 'pages_revision' => '수정본', 'pages_revisions' => '문서 수정본', - 'pages_revisions_desc' => 'Listed below are all the past revisions of this page. You can look back upon, compare, and restore old page versions if permissions allow. The full history of the page may not be fully reflected here since, depending on system configuration, old revisions could be auto-deleted.', + 'pages_revisions_desc' => '아래는 이 페이지의 모든 과거 개정 버전입니다. 권한이 허용하는 경우 이전 페이지 버전을 되돌아보고, 비교하고, 복원할 수 있습니다. 시스템 구성에 따라 이전 수정본이 자동으로 삭제될 수 있으므로 페이지의 전체 기록이 여기에 완전히 반영되지 않을 수 있습니다.', 'pages_revisions_named' => ':pageName 수정본', 'pages_revision_named' => ':pageName 수정본', 'pages_revision_restored_from' => '#:id; :summary에서 복구함', 'pages_revisions_created_by' => '만든 사용자', 'pages_revisions_date' => '수정한 날짜', 'pages_revisions_number' => 'No.', - 'pages_revisions_sort_number' => 'Revision Number', + 'pages_revisions_sort_number' => '수정 번호', 'pages_revisions_numbered' => '수정본 :id', 'pages_revisions_numbered_changes' => '수정본 :id에서 바꾼 부분', 'pages_revisions_editor' => '편집기 유형', @@ -272,16 +272,16 @@ return [ 'pages_revisions_restore' => '복원', 'pages_revisions_none' => '수정본이 없습니다.', 'pages_copy_link' => '주소 복사', - 'pages_edit_content_link' => 'Jump to section in editor', - 'pages_pointer_enter_mode' => 'Enter section select mode', - 'pages_pointer_label' => 'Page Section Options', - 'pages_pointer_permalink' => 'Page Section Permalink', - 'pages_pointer_include_tag' => 'Page Section Include Tag', - 'pages_pointer_toggle_link' => 'Permalink mode, Press to show include tag', - 'pages_pointer_toggle_include' => 'Include tag mode, Press to show permalink', + 'pages_edit_content_link' => '편집기의 섹션으로 이동', + 'pages_pointer_enter_mode' => '섹션 선택 모드로 들어가기', + 'pages_pointer_label' => '페이지 섹션 옵션', + 'pages_pointer_permalink' => '페이지 섹션 퍼머링크', + 'pages_pointer_include_tag' => '페이지 섹션 포함 태그 포함', + 'pages_pointer_toggle_link' => '퍼머링크 모드, 포함 태그를 표시하려면 누릅니다.', + 'pages_pointer_toggle_include' => '태그 포함 모드, 퍼머링크를 표시하려면 누릅니다.', 'pages_permissions_active' => '문서 권한 허용함', 'pages_initial_revision' => '처음 판본', - 'pages_references_update_revision' => 'System auto-update of internal links', + 'pages_references_update_revision' => '시스템에서 내부 링크 자동 업데이트', 'pages_initial_name' => '제목 없음', 'pages_editing_draft_notification' => ':timeDiff에 초안 문서입니다.', 'pages_draft_edited_notification' => '최근에 수정한 문서이기 때문에 초안 문서를 폐기하는 편이 좋습니다.', @@ -293,20 +293,20 @@ return [ 'time_b' => '(:minCount분 전)', 'message' => ':start :time. 다른 사용자의 수정본을 덮어쓰지 않도록 주의하세요.', ], - 'pages_draft_discarded' => 'Draft discarded! The editor has been updated with the current page content', - 'pages_draft_deleted' => 'Draft deleted! The editor has been updated with the current page content', + 'pages_draft_discarded' => '초안 폐기! 편집기가 현재 페이지 콘텐츠로 업데이트되었습니다.', + 'pages_draft_deleted' => '초안이 삭제되었습니다! 편집기가 현재 페이지 콘텐츠로 업데이트되었습니다.', 'pages_specific' => '특정한 문서', 'pages_is_template' => '템플릿', // Editor Sidebar - 'toggle_sidebar' => 'Toggle Sidebar', + 'toggle_sidebar' => '사이드바 토글', 'page_tags' => '문서 꼬리표', 'chapter_tags' => '챕터 꼬리표', 'book_tags' => '책 꼬리표', 'shelf_tags' => '책꽂이 꼬리표', 'tag' => '꼬리표', 'tags' => '꼬리표', - 'tags_index_desc' => 'Tags can be applied to content within the system to apply a flexible form of categorization. Tags can have both a key and value, with the value being optional. Once applied, content can then be queried using the tag name and value.', + 'tags_index_desc' => '태그를 시스템 내의 콘텐츠에 적용하여 유연한 형태의 분류를 적용할 수 있습니다. 태그는 키와 값을 모두 가질 수 있으며 값은 선택 사항입니다. 태그가 적용되면 태그 이름과 값을 사용하여 콘텐츠를 쿼리할 수 있습니다.', 'tag_name' => '꼬리표 이름', 'tag_value' => '리스트 값 (선택 사항)', 'tags_explain' => "꼬리표로 문서를 분류하세요.", @@ -327,10 +327,10 @@ return [ 'attachments_explain_instant_save' => '여기에서 바꾼 내용은 바로 적용합니다.', 'attachments_upload' => '파일 올리기', 'attachments_link' => '링크로 첨부', - 'attachments_upload_drop' => 'Alternatively you can drag and drop a file here to upload it as an attachment.', + 'attachments_upload_drop' => '또는 파일을 여기로 끌어다 놓아 첨부 파일로 업로드할 수도 있습니다.', 'attachments_set_link' => '링크 설정', 'attachments_delete' => '이 첨부 파일을 지울 건가요?', - 'attachments_dropzone' => 'Drop files here to upload', + 'attachments_dropzone' => '업로드할 파일을 여기에 놓아주세요.', 'attachments_no_files' => '올린 파일 없음', 'attachments_explain_link' => '파일을 올리지 않고 링크로 첨부할 수 있습니다.', 'attachments_link_name' => '링크 이름', @@ -373,13 +373,13 @@ return [ 'comment_new' => '새로운 댓글', 'comment_created' => '댓글 등록함 :createDiff', 'comment_updated' => ':username(이)가 댓글 수정함 :updateDiff', - 'comment_updated_indicator' => 'Updated', + 'comment_updated_indicator' => '업데이트됨', 'comment_deleted_success' => '댓글 지움', 'comment_created_success' => '댓글 등록함', 'comment_updated_success' => '댓글 수정함', 'comment_delete_confirm' => '이 댓글을 지울 건가요?', 'comment_in_reply_to' => ':commentId(을)를 향한 답글', - 'comment_editor_explain' => 'Here are the comments that have been left on this page. Comments can be added & managed when viewing the saved page.', + 'comment_editor_explain' => '이 페이지에 남겨진 댓글은 다음과 같습니다. 저장된 페이지를 볼 때 댓글을 추가하고 관리할 수 있습니다.', // Revision 'revision_delete_confirm' => '이 수정본을 지울 건가요?', @@ -396,42 +396,42 @@ return [ // Conversions 'convert_to_shelf' => '책장으로 변환', - 'convert_to_shelf_contents_desc' => 'You can convert this book to a new shelf with the same contents. Chapters contained within this book will be converted to new books. If this book contains any pages, that are not in a chapter, this book will be renamed and contain such pages, and this book will become part of the new shelf.', - 'convert_to_shelf_permissions_desc' => 'Any permissions set on this book will be copied to the new shelf and to all new child books that don\'t have their own permissions enforced. Note that permissions on shelves do not auto-cascade to content within, as they do for books.', + 'convert_to_shelf_contents_desc' => '이 책을 동일한 내용의 새 서가로 변환할 수 있습니다. 이 책에 포함된 챕터는 새 책으로 변환됩니다. 이 책에 챕터에 포함되지 않은 페이지가 포함되어 있는 경우, 이 책의 이름이 변경되어 해당 페이지가 포함되며 이 책은 새 서가의 일부가 됩니다.', + 'convert_to_shelf_permissions_desc' => '이 책에 설정된 모든 권한은 새 서가 및 자체 권한이 적용되지 않은 모든 새 책에 복사됩니다. 책에 대한 권한은 책에 대한 권한처럼 그 안의 콘텐츠로 자동 캐스케이드되지 않는다는 점에 유의하세요.', 'convert_book' => '책 변환', 'convert_book_confirm' => '이 책을 변환하시겠어요?', 'convert_undo_warning' => '이 작업은 되돌리기 어렵습니다.', 'convert_to_book' => '책으로 변환', - 'convert_to_book_desc' => 'You can convert this chapter to a new book with the same contents. Any permissions set on this chapter will be copied to the new book but any inherited permissions, from the parent book, will not be copied which could lead to a change of access control.', + 'convert_to_book_desc' => '이 챕터를 동일한 내용의 새 책으로 변환할 수 있습니다. 이 장에 설정된 모든 권한은 새 책에 복사되지만 상위 책에서 상속된 권한은 복사되지 않으므로 액세스 제어가 변경될 수 있습니다.', 'convert_chapter' => '챕터 변환', 'convert_chapter_confirm' => '이 챕터를 변환하시겠어요?', // References - 'references' => 'References', - 'references_none' => 'There are no tracked references to this item.', - 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', + 'references' => '참조', + 'references_none' => '이 항목에 대한 추적된 참조가 없습니다.', + 'references_to_desc' => '이 항목으로 연결되는 시스템에서 알려진 모든 콘텐츠가 아래에 나열되어 있습니다.', // Watch Options - 'watch' => 'Watch', - 'watch_title_default' => 'Default Preferences', - 'watch_desc_default' => 'Revert watching to just your default notification preferences.', - 'watch_title_ignore' => 'Ignore', - 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', - 'watch_title_new' => 'New Pages', - 'watch_desc_new' => 'Notify when any new page is created within this item.', - 'watch_title_updates' => 'All Page Updates', - 'watch_desc_updates' => 'Notify upon all new pages and page changes.', - 'watch_desc_updates_page' => 'Notify upon all page changes.', - 'watch_title_comments' => 'All Page Updates & Comments', - 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', - 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', - 'watch_change_default' => 'Change default notification preferences', - 'watch_detail_ignore' => 'Ignoring notifications', - 'watch_detail_new' => 'Watching for new pages', - 'watch_detail_updates' => 'Watching new pages and updates', - 'watch_detail_comments' => 'Watching new pages, updates & comments', - 'watch_detail_parent_book' => 'Watching via parent book', - 'watch_detail_parent_book_ignore' => 'Ignoring via parent book', - 'watch_detail_parent_chapter' => 'Watching via parent chapter', - 'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter', + 'watch' => '주시', + 'watch_title_default' => '기본 설정', + 'watch_desc_default' => '보기를 기본 알림 환경설정으로 되돌릴 수 있습니다.', + 'watch_title_ignore' => '무시', + 'watch_desc_ignore' => '사용자 수준 환경설정의 알림을 포함한 모든 알림을 무시합니다.', + 'watch_title_new' => '새로운 페이지', + 'watch_desc_new' => '이 항목에 새 페이지가 생성되면 알림을 받습니다.', + 'watch_title_updates' => '전체 페이지 업데이트', + 'watch_desc_updates' => '모든 새 페이지와 페이지 변경 시 알림을 보냅니다.', + 'watch_desc_updates_page' => '모든 페이지 변경 시 알림을 보냅니다.', + 'watch_title_comments' => '모든 페이지 업데이트 및 댓글', + 'watch_desc_comments' => '모든 새 페이지, 페이지 변경 및 새 댓글에 대해 알림을 보냅니다.', + 'watch_desc_comments_page' => '페이지 변경 및 새 댓글이 있을 때 알림을 보냅니다.', + 'watch_change_default' => '기본 알림 환경설정 변경하기', + 'watch_detail_ignore' => '알림 무시하기', + 'watch_detail_new' => '새 페이지 보기', + 'watch_detail_updates' => '새 페이지 및 업데이트 보기', + 'watch_detail_comments' => '새 페이지, 업데이트 및 댓글 보기', + 'watch_detail_parent_book' => '상위 책을 통해 보기', + 'watch_detail_parent_book_ignore' => '페어런트 북을 통한 무시하기', + 'watch_detail_parent_chapter' => '상위 챕터를 통해 보기', + 'watch_detail_parent_chapter_ignore' => '상위 챕터를 통해 무시하기', ]; diff --git a/lang/ko/errors.php b/lang/ko/errors.php index a652cc697..0ab6789cc 100644 --- a/lang/ko/errors.php +++ b/lang/ko/errors.php @@ -5,68 +5,68 @@ return [ // Permissions - 'permission' => '권한이 없습니다.', - 'permissionJson' => '권한이 없습니다.', + 'permission' => '요청된 페이지에 액세스할 수 있는 권한이 없습니다.', + 'permissionJson' => '요청된 작업을 수행할 수 있는 권한이 없습니다.', // Auth - 'error_user_exists_different_creds' => ':email(을)를 가진 다른 사용자가 있습니다.', - 'email_already_confirmed' => '확인이 끝난 메일 주소입니다. 로그인하세요.', - 'email_confirmation_invalid' => '이 링크는 더 이상 유효하지 않습니다. 다시 가입하세요.', - 'email_confirmation_expired' => '이 링크는 더 이상 유효하지 않습니다. 메일을 다시 보냈습니다.', - 'email_confirmation_awaiting' => '사용 중인 계정의 이메일 주소를 확인해 주어야 합니다.', - 'ldap_fail_anonymous' => '익명 정보로 LDAP 서버에 접근할 수 없습니다.', - 'ldap_fail_authed' => '이 정보로 LDAP 서버에 접근할 수 없습니다.', - 'ldap_extension_not_installed' => 'PHP에 LDAP 확장 도구를 설치하세요.', - 'ldap_cannot_connect' => 'LDAP 서버에 연결할 수 없습니다.', - 'saml_already_logged_in' => '로그인 중입니다.', - 'saml_no_email_address' => '인증 시스템이 제공한 메일 주소가 없습니다.', - 'saml_invalid_response_id' => '인증 시스템이 요청을 받지 못했습니다. 인증 후 이전 페이지로 돌아갈 때 발생할 수 있는 현상입니다.', - 'saml_fail_authed' => ':system에 로그인할 수 없습니다.', - 'oidc_already_logged_in' => '로그인 중입니다.', - 'oidc_user_not_registered' => ':name 사용자가 없습니다. 자동 가입 옵션이 비활성 상태입니다.', - 'oidc_no_email_address' => '인증 시스템이 제공한 메일 주소가 없습니다.', - 'oidc_fail_authed' => ':system에 로그인할 수 없습니다.', - 'social_no_action_defined' => '무슨 활동인지 알 수 없습니다.', - 'social_login_bad_response' => ":socialAccount에 로그인할 수 없습니다. : \\n:error", - 'social_account_in_use' => ':socialAccount(을)를 가진 사용자가 있습니다. :socialAccount로 로그인하세요.', - 'social_account_email_in_use' => ':email(을)를 가진 사용자가 있습니다. 쓰고 있는 계정을 :socialAccount에 연결하세요.', - 'social_account_existing' => ':socialAccount(와)과 연결 상태입니다.', - 'social_account_already_used_existing' => ':socialAccount(와)과 연결한 다른 계정이 있습니다.', - 'social_account_not_used' => ':socialAccount(와)과 연결한 계정이 없습니다. 쓰고 있는 계정을 연결하세요.', - 'social_account_register_instructions' => '계정이 없어도 :socialAccount로 가입할 수 있습니다.', - 'social_driver_not_found' => '가입할 수 없습니다.', - 'social_driver_not_configured' => ':socialAccount가 유효하지 않습니다.', - 'invite_token_expired' => '이 링크는 더 이상 유효하지 않습니다. 패스워드를 바꾸세요.', + 'error_user_exists_different_creds' => '이메일 :email 이 이미 존재하지만 다른 자격 증명을 가진 사용자입니다.', + 'email_already_confirmed' => '이메일이 이미 확인되었으니 로그인해 보세요.', + 'email_confirmation_invalid' => '이 확인 토큰이 유효하지 않거나 이미 사용되었습니다. 다시 등록해 주세요.', + 'email_confirmation_expired' => '확인 토큰이 만료되었습니다. 새 확인 이메일이 전송되었습니다.', + 'email_confirmation_awaiting' => '사용 중인 계정의 이메일 주소를 확인해야 합니다.', + 'ldap_fail_anonymous' => '익명 바인딩을 사용하여 LDAP 액세스에 실패했습니다.', + 'ldap_fail_authed' => '주어진 dn 및 암호 세부 정보를 사용하여 LDAP 액세스에 실패했습니다.', + 'ldap_extension_not_installed' => 'LDAP PHP 확장이 설치되지 않았습니다.', + 'ldap_cannot_connect' => 'LDAP 서버에 연결할 수 없음, 초기 연결 실패', + 'saml_already_logged_in' => '이미 로그인했습니다.', + 'saml_no_email_address' => '외부 인증 시스템에서 제공한 데이터에서 이 사용자의 이메일 주소를 찾을 수 없습니다.', + 'saml_invalid_response_id' => '이 애플리케이션에서 시작한 프로세스에서 외부 인증 시스템의 요청을 인식하지 못합니다. 로그인 후 다시 이동하면 이 문제가 발생할 수 있습니다.', + 'saml_fail_authed' => ':system 을 사용하여 로그인, 시스템이 성공적인 인증을 제공하지 않음', + 'oidc_already_logged_in' => '이미 로그인했습니다.', + 'oidc_user_not_registered' => '사용자 :name 이 등록되지 않았으며 자동 등록이 비활성화되었습니다.', + 'oidc_no_email_address' => '외부 인증 시스템에서 제공한 데이터에서 이 사용자의 이메일 주소를 찾을 수 없습니다.', + 'oidc_fail_authed' => ':system 을 사용하여 로그인, 시스템이 성공적인 인증을 제공하지 않음', + 'social_no_action_defined' => '정의된 동작 없음', + 'social_login_bad_response' => ":socialAccount 로 로그인 동안 에러가 발생했습니다: \n:error", + 'social_account_in_use' => ':socialAccount(을)를 가진 사용자가 있습니다. :socialAccount 옵션을 통해 로그인해 보세요.', + 'social_account_email_in_use' => '이메일 :email 은(는) 이미 사용 중입니다. 이미 계정이 있는 경우 프로필 설정에서 :socialAccount 계정을 연결할 수 있습니다.', + 'social_account_existing' => '이 :socialAccount 는 이미 프로필에 연결되어 있습니다.', + 'social_account_already_used_existing' => '이 :socialAccount 계정은 다른 사용자가 이미 사용하고 있습니다.', + 'social_account_not_used' => '이 :socialAccount 계정은 어떤 사용자와도 연결되어 있지 않습니다. 프로필 설정에서 첨부하세요. ', + 'social_account_register_instructions' => '아직 계정이 없는 경우 :socialAccount 옵션을 사용하여 계정을 등록할 수 있습니다.', + 'social_driver_not_found' => '소셜 드라이버를 찾을 수 없습니다.', + 'social_driver_not_configured' => '소셜 계정 :socialAccount 가(이) 올바르게 구성되지 않았습니다.', + 'invite_token_expired' => '이 초대 링크가 만료되었습니다. 대신 계정 비밀번호 재설정을 시도해 보세요.', // System - 'path_not_writable' => ':filePath에 쓰는 것을 서버에서 허용하지 않습니다.', - 'cannot_get_image_from_url' => ':url에서 이미지를 불러올 수 없습니다.', - 'cannot_create_thumbs' => '섬네일을 못 만들었습니다. PHP에 GD 확장 도구를 설치하세요.', - 'server_upload_limit' => '파일 크기가 서버에서 허용하는 수치를 넘습니다.', - 'server_post_limit' => 'The server cannot receive the provided amount of data. Try again with less data or a smaller file.', - 'uploaded' => '파일 크기가 서버에서 허용하는 수치를 넘습니다.', + 'path_not_writable' => '파일 경로 :filePath 에 업로드할 수 없습니다. 서버에 저장이 가능한지 확인하세요.', + 'cannot_get_image_from_url' => ':url 에서 이미지를 가져올 수 없습니다.', + 'cannot_create_thumbs' => '서버에서 썸네일을 만들 수 없습니다. GD PHP 확장이 설치되어 있는지 확인하세요.', + 'server_upload_limit' => '서버에서 이 크기의 업로드를 허용하지 않습니다. 더 작은 파일 크기를 시도해 보세요.', + 'server_post_limit' => '서버가 제공된 데이터 양을 수신할 수 없습니다. 더 적은 데이터 또는 더 작은 파일로 다시 시도하세요.', + 'uploaded' => '서버에서 이 크기의 업로드를 허용하지 않습니다. 더 작은 파일 크기를 시도해 보세요.', // Drawing & Images - 'image_upload_error' => '이미지를 올리다 문제가 생겼습니다.', - 'image_upload_type_error' => '유효하지 않은 이미지 형식입니다.', - 'image_upload_replace_type' => 'Image file replacements must be of the same type', - 'image_upload_memory_limit' => 'Failed to handle image upload and/or create thumbnails due to system resource limits.', - 'image_thumbnail_memory_limit' => 'Failed to create image size variations due to system resource limits.', - 'image_gallery_thumbnail_memory_limit' => 'Failed to create gallery thumbnails due to system resource limits.', - 'drawing_data_not_found' => 'Drawing data could not be loaded. The drawing file might no longer exist or you may not have permission to access it.', + 'image_upload_error' => '이미지를 업로드하는 동안 오류가 발생했습니다.', + 'image_upload_type_error' => '업로드 중인 이미지 유형이 유효하지 않습니다.', + 'image_upload_replace_type' => '이미지 파일 교체는 반드시 동일한 유형이어야 합니다.', + 'image_upload_memory_limit' => '시스템 리소스 제한으로 인해 이미지 업로드를 처리하거나 미리보기 이미지를 만들지 못했습니다.', + 'image_thumbnail_memory_limit' => '시스템 리소스 제한으로 인해 이미지 크기 변형을 만들지 못했습니다.', + 'image_gallery_thumbnail_memory_limit' => '시스템 리소스 제한으로 인해 갤러리 썸네일을 만들지 못했습니다.', + 'drawing_data_not_found' => '드로잉 데이터를 로드할 수 없습니다. 드로잉 파일이 더 이상 존재하지 않거나 해당 파일에 액세스할 수 있는 권한이 없을 수 있습니다.', // Attachments - 'attachment_not_found' => '첨부 파일이 없습니다.', - 'attachment_upload_error' => 'An error occurred uploading the attachment file', + 'attachment_not_found' => '첨부 파일을 찾을 수 없습니다.', + 'attachment_upload_error' => '첨부 파일을 업로드하는 동안 오류가 발생했습니다.', // Pages - 'page_draft_autosave_fail' => '초안 문서를 유실했습니다. 인터넷 연결 상태를 확인하세요.', - 'page_draft_delete_fail' => 'Failed to delete page draft and fetch current page saved content', - 'page_custom_home_deletion' => '처음 페이지는 지울 수 없습니다.', + 'page_draft_autosave_fail' => '초안을 저장하지 못했습니다. 이 페이지를 저장하기 전에 인터넷에 연결되어 있는지 확인하세요.', + 'page_draft_delete_fail' => '페이지 초안을 삭제하고 현재 페이지에 저장된 콘텐츠를 가져오지 못했습니다.', + 'page_custom_home_deletion' => '페이지가 홈페이지로 설정되어 있는 동안에는 삭제할 수 없습니다.', // Entities 'entity_not_found' => '항목이 없습니다.', - 'bookshelf_not_found' => 'Shelf not found', + 'bookshelf_not_found' => '책장을 찾을 수 없음', 'book_not_found' => '책이 없습니다.', 'page_not_found' => '문서가 없습니다.', 'chapter_not_found' => '챕터가 없습니다.', @@ -115,5 +115,5 @@ return [ 'maintenance_test_email_failure' => '메일을 발송하는 도중 문제가 생겼습니다:', // HTTP errors - 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', + 'http_ssr_url_no_match' => 'URL이 구성된 허용된 SSR 호스트와 일치하지 않습니다.', ]; diff --git a/lang/ko/notifications.php b/lang/ko/notifications.php index 1afd23f1d..b337fa86d 100644 --- a/lang/ko/notifications.php +++ b/lang/ko/notifications.php @@ -4,24 +4,24 @@ */ return [ - 'new_comment_subject' => 'New comment on page: :pageName', - 'new_comment_intro' => 'A user has commented on a page in :appName:', - 'new_page_subject' => 'New page: :pageName', - 'new_page_intro' => 'A new page has been created in :appName:', - 'updated_page_subject' => 'Updated page: :pageName', - 'updated_page_intro' => 'A page has been updated in :appName:', - 'updated_page_debounce' => 'To prevent a mass of notifications, for a while you won\'t be sent notifications for further edits to this page by the same editor.', + 'new_comment_subject' => '페이지에 새 댓글이 추가되었습니다: :pageName', + 'new_comment_intro' => '사용자가 :appName: 의 페이지에 댓글을 달았습니다.', + 'new_page_subject' => '새로운 페이지: :pageName', + 'new_page_intro' => ':appName: 에 새 페이지가 생성되었습니다.', + 'updated_page_subject' => '페이지 업데이트됨: :pageName', + 'updated_page_intro' => ':appName: 에서 페이지가 업데이트되었습니다:', + 'updated_page_debounce' => '알림이 한꺼번에 몰리는 것을 방지하기 위해 당분간 동일한 편집자가 이 페이지를 추가로 편집할 경우 알림이 전송되지 않습니다.', - 'detail_page_name' => 'Page Name:', - 'detail_page_path' => 'Page Path:', - 'detail_commenter' => 'Commenter:', - 'detail_comment' => 'Comment:', - 'detail_created_by' => 'Created By:', - 'detail_updated_by' => 'Updated By:', + 'detail_page_name' => '페이지 이름:', + 'detail_page_path' => '페이지 경로:', + 'detail_commenter' => '댓글 작성자:', + 'detail_comment' => '댓글:', + 'detail_created_by' => '작성자:', + 'detail_updated_by' => '업데이트한 사람:', - 'action_view_comment' => 'View Comment', - 'action_view_page' => 'View Page', + 'action_view_comment' => '댓글 보기', + 'action_view_page' => '페이지 보기', - 'footer_reason' => 'This notification was sent to you because :link cover this type of activity for this item.', - 'footer_reason_link' => 'your notification preferences', + 'footer_reason' => '이 알림이 전송된 이유는 :link 가 이 항목의 이러한 유형의 활동에 해당하기 때문입니다.', + 'footer_reason_link' => '내 알림 환경설정', ]; diff --git a/lang/ko/preferences.php b/lang/ko/preferences.php index dec399112..ccc09e7a3 100644 --- a/lang/ko/preferences.php +++ b/lang/ko/preferences.php @@ -5,10 +5,10 @@ */ return [ - 'my_account' => 'My Account', + 'my_account' => '내 계정', 'shortcuts' => '단축키', - 'shortcuts_interface' => 'UI Shortcut Preferences', + 'shortcuts_interface' => 'UI 바로 가기 환경설정', 'shortcuts_toggle_desc' => '여기에서 탐색과 행동에 사용될 수 있는 키보드 단축키를 활성화하거나 비활성화할 수 있습니다.', 'shortcuts_customize_desc' => '아래에서 각 단축키를 사용자 지정할 수 있습니다. 바로가기에 대한 입력을 선택한 후 원하는 키 조합을 누르기만 하면 됩니다.', 'shortcuts_toggle_label' => '키보드 단축키가 활성화되었습니다.', @@ -17,35 +17,35 @@ return [ 'shortcuts_save' => '단축키 저장', 'shortcuts_overlay_desc' => '참고: 바로가기가 활성화된 경우 "?"를 누르면 현재 화면에 표시되는 작업에 대해 사용 가능한 바로가기를 강조 표시하는 도우미 오버레이를 사용할 수 있습니다.', 'shortcuts_update_success' => '단축키 설정이 수정되었습니다!', - 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', + 'shortcuts_overview_desc' => '시스템 사용자 인터페이스를 탐색하는 데 사용할 수 있는 키보드 단축키를 관리합니다.', - 'notifications' => 'Notification Preferences', - 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', - 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', - 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', - 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', - 'notifications_save' => 'Save Preferences', - 'notifications_update_success' => 'Notification preferences have been updated!', - 'notifications_watched' => 'Watched & Ignored Items', - 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', + 'notifications' => '알림 환경설정', + 'notifications_desc' => '시스템 내에서 특정 활동이 수행될 때 수신하는 이메일 알림을 제어합니다.', + 'notifications_opt_own_page_changes' => '내가 소유한 페이지가 변경되면 알림 받기', + 'notifications_opt_own_page_comments' => '내가 소유한 페이지에 댓글이 달렸을 때 알림 받기', + 'notifications_opt_comment_replies' => '내 댓글에 대한 답글 알림 받기', + 'notifications_save' => '환경설정 저장', + 'notifications_update_success' => '알림 환경설정이 업데이트되었습니다!', + 'notifications_watched' => '주시 및 무시한 항목', + 'notifications_watched_desc' => ' 아래는 사용자 지정 시계 환경설정이 적용된 항목입니다. 이러한 항목에 대한 환경설정을 업데이트하려면 해당 항목을 본 다음 사이드바에서 시계 옵션을 찾습니다.', - 'auth' => 'Access & Security', - 'auth_change_password' => 'Change Password', - 'auth_change_password_desc' => 'Change the password you use to log-in to the application. This must be at least 8 characters long.', - 'auth_change_password_success' => 'Password has been updated!', + 'auth' => '액세스 및 보안', + 'auth_change_password' => '패스워드 변경', + 'auth_change_password_desc' => '애플리케이션에 로그인할 때 사용하는 패스워드를 변경합니다. 패스워드는 8자 이상이어야 합니다.', + 'auth_change_password_success' => '패스워드가 업데이트되었습니다!', - 'profile' => 'Profile Details', - 'profile_desc' => 'Manage the details of your account which represents you to other users, in addition to details that are used for communication and system personalisation.', - 'profile_view_public' => 'View Public Profile', - 'profile_name_desc' => 'Configure your display name which will be visible to other users in the system through the activity you perform, and content you own.', - 'profile_email_desc' => 'This email will be used for notifications and, depending on active system authentication, system access.', - 'profile_email_no_permission' => 'Unfortunately you don\'t have permission to change your email address. If you want to change this, you\'d need to ask an administrator to change this for you.', - 'profile_avatar_desc' => 'Select an image which will be used to represent yourself to others in the system. Ideally this image should be square and about 256px in width and height.', - 'profile_admin_options' => 'Administrator Options', - 'profile_admin_options_desc' => 'Additional administrator-level options, like those to manage role assignments, can be found for your user account in the "Settings > Users" area of the application.', + 'profile' => '프로필 세부 정보', + 'profile_desc' => '커뮤니케이션 및 시스템 맞춤 설정에 사용되는 세부 정보 외에 다른 사용자에게 나를 나타내는 계정의 세부 정보를 관리합니다.', + 'profile_view_public' => '공개 프로필 보기', + 'profile_name_desc' => '내가 수행하는 활동과 내가 소유한 콘텐츠를 통해 시스템의 다른 사용자에게 표시되는 표시 이름을 구성합니다.', + 'profile_email_desc' => '이 이메일은 알림 및 활성 시스템 인증에 따라 시스템 액세스에 사용됩니다.', + 'profile_email_no_permission' => '안타깝게도 회원님에게는 이메일 주소를 변경할 수 있는 권한이 없습니다. 이메일 주소를 변경하려면 관리자에게 변경을 요청해야 합니다.', + 'profile_avatar_desc' => '시스템에서 다른 사람들에게 자신을 나타내는 데 사용할 이미지를 선택합니다. 이 이미지는 256 x 256픽셀인 정사각형이 가장 이상적입니다.', + 'profile_admin_options' => '관리자 옵션', + 'profile_admin_options_desc' => '역할 할당 관리와 같은 추가 관리자 수준 옵션은 애플리케이션의 "설정 > 사용자" 영역에서 사용자 계정에 대해 찾을 수 있습니다.', - 'delete_account' => 'Delete Account', - 'delete_my_account' => 'Delete My Account', - 'delete_my_account_desc' => 'This will fully delete your user account from the system. You will not be able to recover this account or revert this action. Content you\'ve created, such as created pages and uploaded images, will remain.', - 'delete_my_account_warning' => 'Are you sure you want to delete your account?', + 'delete_account' => '계정 삭제', + 'delete_my_account' => '내 계정 삭제', + 'delete_my_account_desc' => '이렇게 하면 시스템에서 사용자 계정이 완전히 삭제됩니다. 이 계정을 복구하거나 이 작업을 되돌릴 수 없습니다. 생성한 페이지와 업로드한 이미지 등 사용자가 만든 콘텐츠는 그대로 유지됩니다.', + 'delete_my_account_warning' => '정말 계정을 삭제하시겠습니까?', ]; diff --git a/lang/ko/settings.php b/lang/ko/settings.php index e60bb6646..fe6dd4c7e 100644 --- a/lang/ko/settings.php +++ b/lang/ko/settings.php @@ -14,47 +14,47 @@ return [ // App Settings 'app_customization' => '맞춤', - 'app_features_security' => '보안', - 'app_name' => '사이트 제목', - 'app_name_desc' => '메일을 보낼 때 이 제목을 씁니다.', - 'app_name_header' => '사이트 헤더 사용', + 'app_features_security' => '기능 및 보안', + 'app_name' => '애플리케이션 이름 (사이트 제목)', + 'app_name_desc' => '이 이름은 헤더와 시스템에서 보낸 모든 이메일에 표시됩니다.', + 'app_name_header' => '헤더에 이름 표시', 'app_public_access' => '사이트 공개', - 'app_public_access_desc' => '계정 없는 사용자가 문서를 볼 수 있습니다.', - 'app_public_access_desc_guest' => '이들의 권한은 사용자 이름이 Guest인 사용자로 관리할 수 있습니다.', - 'app_public_access_toggle' => '사이트 공개', - 'app_public_viewing' => '공개할 건가요?', - 'app_secure_images' => '이미지 주소 보호', - 'app_secure_images_toggle' => '이미지 주소 보호', - 'app_secure_images_desc' => '성능상의 문제로 이미지에 누구나 접근할 수 있기 때문에 이미지 주소를 무작위한 문자로 구성합니다. 폴더 색인을 끄세요.', + 'app_public_access_desc' => '이 옵션을 활성화하면 로그인하지 않은 방문자도 북스택 인스턴스의 콘텐츠에 액세스할 수 있습니다.', + 'app_public_access_desc_guest' => '일반 방문자의 액세스는 "Guest" 사용자를 통해 제어할 수 있습니다.', + 'app_public_access_toggle' => '공개 액세스 허용', + 'app_public_viewing' => '공개 열람을 허용할까요?', + 'app_secure_images' => '보안 강화된 이미지 업로드', + 'app_secure_images_toggle' => '보안 강화된 이미지 업로드 사용', + 'app_secure_images_desc' => '성능상의 이유로 모든 이미지는 공개됩니다. 이 옵션은 이미지 URL 앞에 추측하기 어려운 임의의 문자열을 추가합니다. 쉽게 액세스할 수 없도록 디렉토리 인덱스가 활성화되어 있지 않은지 확인하세요.', 'app_default_editor' => '기본 페이지 편집기', 'app_default_editor_desc' => '새 페이지를 편집할 때 기본으로 사용될 편집기를 선택합니다. 권한을 갖고 있다면 페이지마다 다르게 적용될 수 있습니다.', - 'app_custom_html' => '헤드 작성', - 'app_custom_html_desc' => '설정 페이지를 제외한 모든 페이지 head 태그 끝머리에 추가합니다.', - 'app_custom_html_disabled_notice' => '문제가 생겨도 설정 페이지에서 되돌릴 수 있어요.', - 'app_logo' => '사이트 로고', + 'app_custom_html' => '사용자 지정 HTML 헤드 콘텐츠', + 'app_custom_html_desc' => '여기에 추가된 모든 콘텐츠는 모든 페이지의 섹션 하단에 삽입됩니다. 스타일을 재정의하거나 분석 코드를 추가할 때 유용합니다.', + 'app_custom_html_disabled_notice' => '이 설정 페이지에서는 사용자 지정 HTML 헤드 콘텐츠가 비활성화되어 변경 사항을 되돌릴 수 있습니다.', + 'app_logo' => '애플리케이션 로고 (사이트 로고)', 'app_logo_desc' => '이 이미지는 애플리케이션 헤더 표시줄 등 여러 영역에서 사용됩니다. 이 이미지의 높이는 86픽셀이어야 합니다. 큰 이미지는 축소됩니다.', 'app_icon' => '애플리케이션 아이콘', 'app_icon_desc' => '이 아이콘은 브라우저 탭과 바로 가기 아이콘에 사용됩니다. 256픽셀의 정사각형 PNG 이미지여야 합니다.', - 'app_homepage' => '처음 페이지', - 'app_homepage_desc' => '고른 페이지에 설정한 권한은 무시합니다.', - 'app_homepage_select' => '문서 고르기', + 'app_homepage' => '애플리케이션 홈페이지', + 'app_homepage_desc' => '기본 보기 대신 홈페이지에 표시할 보기를 선택합니다. 선택한 페이지에 대한 페이지 권한은 무시됩니다.', + 'app_homepage_select' => '페이지를 선택하십시오', 'app_footer_links' => '바닥글 링크', - 'app_footer_links_desc' => '바닥글 링크는 로그인하지 않아도 보일 수 있습니다. "trans::" 레이블로 시스템에 있는 번역을 가져옵니다. trans::common.privacy_policy와 trans::common.terms_of_service를 쓸 수 있습니다.', + 'app_footer_links_desc' => '사이트 바닥글에 표시할 링크를 추가합니다. 이 링크는 로그인이 필요 없는 페이지를 포함하여 대부분의 페이지 하단에 표시됩니다. "trans::" 레이블을 사용하여 시스템 정의 번역을 사용할 수 있습니다. 예를 들면 다음과 같습니다: "trans::common.privacy_policy"를 사용하면 "개인정보처리방침"이라는 번역 텍스트가 제공되고 "trans::common.terms_of_service"를 사용하면 "서비스 약관"이라는 번역 텍스트가 제공됩니다.', 'app_footer_links_label' => '링크 레이블', 'app_footer_links_url' => '링크 URL', 'app_footer_links_add' => '바닥글 링크 추가', 'app_disable_comments' => '댓글 사용 안 함', 'app_disable_comments_toggle' => '댓글 사용 안 함', - 'app_disable_comments_desc' => '모든 페이지에서 댓글을 숨깁니다.', + 'app_disable_comments_desc' => '애플리케이션의 모든 페이지에서 코멘트를 비활성화합니다.
기존 댓글도 표시되지 않습니다.', // Color settings - 'color_scheme' => '애플리케이션 색상 스키마', - 'color_scheme_desc' => '애플리케이션 사용자 인터페이스에서 사용할 색상을 설정합니다. 테마에 가장 잘 맞으면서 가독성을 보장하기 위해 어두운 모드와 밝은 모드에 대해 색상을 개별적으로 구성할 수 있습니다.', + 'color_scheme' => '애플리케이션 색상 구성표', + 'color_scheme_desc' => '애플리케이션 사용자 인터페이스에서 사용할 색상을 설정합니다. 테마에 가장 잘 어울리고 가독성을 보장하기 위해 어두운 모드와 밝은 모드에 대해 색상을 별도로 구성할 수 있습니다.', 'ui_colors_desc' => '애플리케이션 기본 색상과 기본 링크 색상을 설정합니다. 기본 색상은 주로 헤더 배너, 버튼 및 인터페이스 장식에 사용됩니다. 기본 링크 색상은 작성된 콘텐츠와 애플리케이션 인터페이스 모두에서 텍스트 기반 링크 및 작업에 사용됩니다.', 'app_color' => '주 색상', 'link_color' => '기본 링크 색상', 'content_colors_desc' => '페이지 구성 계층 구조의 모든 요소에 대한 색상을 설정합니다. 가독성을 위해 기본 색상과 비슷한 밝기의 색상을 선택하는 것이 좋습니다.', - 'bookshelf_color' => '책꽂이 색상', + 'bookshelf_color' => '책장 색상', 'book_color' => '책 색상', 'chapter_color' => '챕터 색상', 'page_color' => '페이지 색상', @@ -62,7 +62,7 @@ return [ // Registration Settings 'reg_settings' => '가입', - 'reg_enable' => '가입 받기', + 'reg_enable' => '가입 활성화', 'reg_enable_toggle' => '가입 받기', 'reg_enable_desc' => '가입한 사용자는 한 가지 권한을 가집니다.', 'reg_default_role' => '기본 권한', @@ -163,7 +163,7 @@ return [ 'role_manage_settings' => '사이트 설정 관리', 'role_export_content' => '항목 내보내기', 'role_editor_change' => '페이지 편집기 변경', - 'role_notifications' => 'Receive & manage notifications', + 'role_notifications' => '알림 수신 및 관리', 'role_asset' => '권한 항목', 'roles_system_warning' => '위 세 권한은 자신의 권한이나 다른 유저의 권한을 바꿀 수 있습니다.', 'role_asset_desc' => '책, 챕터, 문서별 권한은 이 설정에 우선합니다.', @@ -193,8 +193,8 @@ return [ 'users_send_invite_text' => '패스워드 설정을 권유하는 메일을 보내거나 내가 정할 수 있습니다.', 'users_send_invite_option' => '메일 보내기', 'users_external_auth_id' => '외부 인증 계정', - 'users_external_auth_id_desc' => 'When an external authentication system is in use (such as SAML2, OIDC or LDAP) this is the ID which links this BookStack user to the authentication system account. You can ignore this field if using the default email-based authentication.', - 'users_password_warning' => 'Only fill the below if you would like to change the password for this user.', + 'users_external_auth_id_desc' => '외부 인증 시스템(예: SAML2, OIDC 또는 LDAP)을 사용 중인 경우 이 BookStack 사용자를 인증 시스템 계정에 연결하는 ID입니다. 기본 이메일 기반 인증을 사용하는 경우 이 필드를 무시할 수 있습니다.', + 'users_password_warning' => '이 사용자의 비밀번호를 변경하려는 경우에만 아래 내용을 입력하세요.', 'users_system_public' => '계정 없는 모든 사용자에 할당한 사용자입니다. 이 사용자로 로그인할 수 없어요.', 'users_delete' => '사용자 삭제', 'users_delete_named' => ':userName 삭제', @@ -210,16 +210,16 @@ return [ 'users_preferred_language' => '언어', 'users_preferred_language_desc' => '문서 내용에는 아무런 영향을 주지 않습니다.', 'users_social_accounts' => '소셜 계정', - 'users_social_accounts_desc' => 'View the status of the connected social accounts for this user. Social accounts can be used in addition to the primary authentication system for system access.', + 'users_social_accounts_desc' => '이 사용자에 대해 연결된 소셜 계정의 상태를 봅니다. 소셜 계정은 시스템 액세스를 위한 기본 인증 시스템과 함께 사용할 수 있습니다.', 'users_social_accounts_info' => '다른 계정으로 간단하게 로그인하세요. 여기에서 계정 연결을 끊는 것과 소셜 계정에서 접근 권한을 취소하는 것은 다릅니다.', 'users_social_connect' => '계정 연결', 'users_social_disconnect' => '계정 연결 끊기', - 'users_social_status_connected' => 'Connected', - 'users_social_status_disconnected' => 'Disconnected', + 'users_social_status_connected' => '연결됨', + 'users_social_status_disconnected' => '연결 해제됨', 'users_social_connected' => ':socialAccount(와)과 연결했습니다.', 'users_social_disconnected' => ':socialAccount(와)과의 연결을 끊었습니다.', 'users_api_tokens' => 'API 토큰', - 'users_api_tokens_desc' => 'Create and manage the access tokens used to authenticate with the BookStack REST API. Permissions for the API are managed via the user that the token belongs to.', + 'users_api_tokens_desc' => 'BookStack REST API로 인증하는 데 사용되는 액세스 토큰을 생성하고 관리합니다. API에 대한 권한은 토큰이 속한 사용자를 통해 관리됩니다.', 'users_api_tokens_none' => '이 사용자를 위해 생성된 API 토큰이 없습니다.', 'users_api_tokens_create' => '토큰 만들기', 'users_api_tokens_expires' => '만료', diff --git a/lang/ko/validation.php b/lang/ko/validation.php index 1a5de20f4..e533429ee 100644 --- a/lang/ko/validation.php +++ b/lang/ko/validation.php @@ -9,7 +9,7 @@ return [ // Standard laravel validation lines 'accepted' => ':attribute(을)를 허용하세요.', - 'active_url' => ':attribute(을)를 유효한 주소로 구성하세요.', + 'active_url' => ':attribute이 유효한 URL이 아닙니다.', 'after' => ':attribute(을)를 :date 후로 설정하세요.', 'alpha' => ':attribute(을)를 문자로만 구성하세요.', 'alpha_dash' => ':attribute(을)를 문자, 숫자, -, _로만 구성하세요.', diff --git a/lang/lv/activities.php b/lang/lv/activities.php index 344162154..742288c98 100644 --- a/lang/lv/activities.php +++ b/lang/lv/activities.php @@ -64,17 +64,17 @@ return [ // Auth 'auth_login' => 'logged in', 'auth_register' => 'registered as new user', - 'auth_password_reset_request' => 'requested user password reset', - 'auth_password_reset_update' => 'reset user password', - 'mfa_setup_method' => 'configured MFA method', + 'auth_password_reset_request' => 'pieprasīja lietotāja paroles atiestatīšanu', + 'auth_password_reset_update' => 'atiestatīja lietotāja paroli', + 'mfa_setup_method' => 'uzstādīja MFA metodi', 'mfa_setup_method_notification' => '2FA funkcija aktivizēta', - 'mfa_remove_method' => 'removed MFA method', + 'mfa_remove_method' => 'noņēma MFA metodi', 'mfa_remove_method_notification' => '2FA funkcija noņemta', // Settings - 'settings_update' => 'updated settings', - 'settings_update_notification' => 'Settings successfully updated', - 'maintenance_action_run' => 'ran maintenance action', + 'settings_update' => 'atjaunoja uzstādījumus', + 'settings_update_notification' => 'Uzstādījums veiksmīgi atjaunināts', + 'maintenance_action_run' => 'veica apkopes darbību', // Webhooks 'webhook_create' => 'izveidoja webhook', @@ -85,39 +85,39 @@ return [ 'webhook_delete_notification' => 'Webhook veiksmīgi izdzēsts', // Users - 'user_create' => 'created user', - 'user_create_notification' => 'User successfully created', - 'user_update' => 'updated user', + 'user_create' => 'izveidoja lietotāju', + 'user_create_notification' => 'Lietotājs veiksmīgi izveidots', + 'user_update' => 'atjaunoja lietotāju', 'user_update_notification' => 'Lietotājs veiksmīgi atjaunināts', - 'user_delete' => 'deleted user', + 'user_delete' => 'dzēsa lietotāju', 'user_delete_notification' => 'Lietotājs veiksmīgi dzēsts', // API Tokens - 'api_token_create' => 'created api token', - 'api_token_create_notification' => 'API token successfully created', - 'api_token_update' => 'updated api token', - 'api_token_update_notification' => 'API token successfully updated', - 'api_token_delete' => 'deleted api token', - 'api_token_delete_notification' => 'API token successfully deleted', + 'api_token_create' => 'izveidoja API žetonu', + 'api_token_create_notification' => 'API žetons veiksmīgi izveidots', + 'api_token_update' => 'atjaunoja API žetonu', + 'api_token_update_notification' => 'API žetons veiksmīgi atjaunināts', + 'api_token_delete' => 'dzēsa API žetonu', + 'api_token_delete_notification' => 'API žetons veiksmīgi dzēsts', // Roles - 'role_create' => 'created role', + 'role_create' => 'izveidoja lomu', 'role_create_notification' => 'Loma veiksmīgi izveidota', - 'role_update' => 'updated role', + 'role_update' => 'atjaunoja lomu', 'role_update_notification' => 'Loma veiksmīgi atjaunināta', - 'role_delete' => 'deleted role', + 'role_delete' => 'dzēsa lomu', 'role_delete_notification' => 'Loma veiksmīgi dzēsta', // Recycle Bin - 'recycle_bin_empty' => 'emptied recycle bin', - 'recycle_bin_restore' => 'restored from recycle bin', - 'recycle_bin_destroy' => 'removed from recycle bin', + 'recycle_bin_empty' => 'iztukšoja atkritni', + 'recycle_bin_restore' => 'atjaunoja no atkritnes', + 'recycle_bin_destroy' => 'izdzēsa no atkritnes', // Comments 'commented_on' => 'komentēts', - 'comment_create' => 'added comment', - 'comment_update' => 'updated comment', - 'comment_delete' => 'deleted comment', + 'comment_create' => 'pievienoja komentāru', + 'comment_update' => 'atjaunoja komentārju', + 'comment_delete' => 'dzēsa komentāru', // Other 'permissions_update' => 'atjaunoja atļaujas', diff --git a/lang/lv/components.php b/lang/lv/components.php index 2b29b5625..5a7197687 100644 --- a/lang/lv/components.php +++ b/lang/lv/components.php @@ -8,34 +8,34 @@ return [ 'image_select' => 'Attēla izvēle', 'image_list' => 'Attēlu saraksts', 'image_details' => 'Attēla dati', - 'image_upload' => 'Augšuplādēt attēlu', + 'image_upload' => 'Augšupielādēt attēlu', 'image_intro' => 'Šeit jūs varat izvēlēties un pārvaldīt attēlus, kuri iepriekš tika aplugšupielādēti sistēmā.', - 'image_intro_upload' => 'Upload a new image by dragging an image file into this window, or by using the "Upload Image" button above.', + 'image_intro_upload' => 'Augšupielādējiet jaunu attēlu ievelkot attēla failu šajā logā vai izmantojot "Augšupielādēt attēlu" pogu augstāk.', 'image_all' => 'Visi', 'image_all_title' => 'Skatīt visus attēlus', 'image_book_title' => 'Apskatīt augšupielādētos attēlus šajā grāmatā', 'image_page_title' => 'Apskatīt augšupielādētos attēlus šajā lapā', 'image_search_hint' => 'Meklēt pēc attēla vārda', 'image_uploaded' => 'Augšupielādēts :uploadedDate', - 'image_uploaded_by' => 'Uploaded by :userName', - 'image_uploaded_to' => 'Uploaded to :pageLink', - 'image_updated' => 'Updated :updateDate', + 'image_uploaded_by' => 'Augšupielādēja :userName', + 'image_uploaded_to' => 'Augšupielādēja :pageLink', + 'image_updated' => 'Atjaunots :updateDate', 'image_load_more' => 'Ielādēt vairāk', 'image_image_name' => 'Attēla nosaukums', 'image_delete_used' => 'Šis attēls ir ievietots zemāk redzamajās lapās.', 'image_delete_confirm_text' => 'Vai tiešām vēlaties dzēst šo attēlu?', 'image_select_image' => 'Atlasīt attēlu', 'image_dropzone' => 'Ievilkt attēlu vai klikšķinat šeit, lai augšupielādētu', - 'image_dropzone_drop' => 'Drop images here to upload', + 'image_dropzone_drop' => 'Ievelciet attēlus šeit, lai augšupielādētu', 'images_deleted' => 'Dzēstie attēli', 'image_preview' => 'Attēla priekšskatījums', 'image_upload_success' => 'Attēls ir veiksmīgi augšupielādēts', 'image_update_success' => 'Attēlā informācija ir veiksmīgi atjunināta', 'image_delete_success' => 'Attēls veiksmīgi dzēsts', 'image_replace' => 'Nomainīt bildi', - 'image_replace_success' => 'Image file successfully updated', - 'image_rebuild_thumbs' => 'Regenerate Size Variations', - 'image_rebuild_thumbs_success' => 'Image size variations successfully rebuilt!', + 'image_replace_success' => 'Attēla fails veiksmīgi atjaunots', + 'image_rebuild_thumbs' => 'No jauna izveidot attēla dažādu izmēru variantus', + 'image_rebuild_thumbs_success' => 'Attēlu varianti veiksmīgi atjaunoti!', // Code Editor 'code_editor' => 'Rediģēt kodu', diff --git a/lang/lv/entities.php b/lang/lv/entities.php index 40115afff..96d6b706a 100644 --- a/lang/lv/entities.php +++ b/lang/lv/entities.php @@ -132,7 +132,7 @@ return [ 'books_edit_named' => 'Labot grāmatu :bookName', 'books_form_book_name' => 'Grāmatas nosaukums', 'books_save' => 'Saglabāt grāmatu', - 'books_default_template' => 'Default Page Template', + 'books_default_template' => 'Noklusētā lapas sagatave', 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Grāmatas atļaujas', @@ -272,11 +272,11 @@ return [ 'pages_revisions_restore' => 'Atjaunot', 'pages_revisions_none' => 'Šai lapai nav revīziju', 'pages_copy_link' => 'Kopēt saiti', - 'pages_edit_content_link' => 'Jump to section in editor', + 'pages_edit_content_link' => 'Pārlekt uz sadaļu redaktorā', 'pages_pointer_enter_mode' => 'Enter section select mode', - 'pages_pointer_label' => 'Page Section Options', - 'pages_pointer_permalink' => 'Page Section Permalink', - 'pages_pointer_include_tag' => 'Page Section Include Tag', + 'pages_pointer_label' => 'Lapas sadaļas uzstādījumi', + 'pages_pointer_permalink' => 'Lapas sadaļas saite', + 'pages_pointer_include_tag' => 'Lapas sadaļas iekļaušanas tags', 'pages_pointer_toggle_link' => 'Permalink mode, Press to show include tag', 'pages_pointer_toggle_include' => 'Include tag mode, Press to show permalink', 'pages_permissions_active' => 'Lapas atļaujas ir aktīvas', @@ -373,7 +373,7 @@ return [ 'comment_new' => 'Jauns komentārs', 'comment_created' => 'komentējis :createDiff', 'comment_updated' => ':username atjauninājis pirms :updateDiff', - 'comment_updated_indicator' => 'Updated', + 'comment_updated_indicator' => 'Atjaunots', 'comment_deleted_success' => 'Komentārs ir dzēsts', 'comment_created_success' => 'Komentārs ir pievienots', 'comment_updated_success' => 'Komentārs ir atjaunināts', @@ -412,21 +412,21 @@ return [ 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', // Watch Options - 'watch' => 'Watch', + 'watch' => 'Vērot', 'watch_title_default' => 'Default Preferences', 'watch_desc_default' => 'Revert watching to just your default notification preferences.', - 'watch_title_ignore' => 'Ignore', + 'watch_title_ignore' => 'Ignorēt', 'watch_desc_ignore' => 'Ignore all notifications, including those from user-level preferences.', - 'watch_title_new' => 'New Pages', + 'watch_title_new' => 'Jaunas lapas', 'watch_desc_new' => 'Notify when any new page is created within this item.', - 'watch_title_updates' => 'All Page Updates', + 'watch_title_updates' => 'Visi lapu atjauninājumi', 'watch_desc_updates' => 'Notify upon all new pages and page changes.', 'watch_desc_updates_page' => 'Notify upon all page changes.', - 'watch_title_comments' => 'All Page Updates & Comments', + 'watch_title_comments' => 'Visi lapu atjauninājumi un komentāri', 'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.', 'watch_desc_comments_page' => 'Notify upon page changes and new comments.', 'watch_change_default' => 'Change default notification preferences', - 'watch_detail_ignore' => 'Ignoring notifications', + 'watch_detail_ignore' => 'Ignorēt paziņojumus', 'watch_detail_new' => 'Watching for new pages', 'watch_detail_updates' => 'Watching new pages and updates', 'watch_detail_comments' => 'Watching new pages, updates & comments', diff --git a/lang/lv/errors.php b/lang/lv/errors.php index 72abed6e6..2a5777a0a 100644 --- a/lang/lv/errors.php +++ b/lang/lv/errors.php @@ -43,16 +43,16 @@ return [ 'cannot_get_image_from_url' => 'Nevar iegūt bildi no :url', 'cannot_create_thumbs' => 'Serveris nevar izveidot samazinātus attēlus. Lūdzu pārbaudiet, vai ir uzstādīts PHP GD paplašinājums.', 'server_upload_limit' => 'Serveris neatļauj šāda izmēra failu ielādi. Lūdzu mēģiniet mazāka izmēra failu.', - 'server_post_limit' => 'The server cannot receive the provided amount of data. Try again with less data or a smaller file.', + 'server_post_limit' => 'Serveris nevar apstrādāt šāda izmēra datus. Lūdzu mēģiniet vēlreiz ar mazāku datu apjomu vai mazāku failu.', 'uploaded' => 'Serveris neatļauj šāda izmēra failu ielādi. Lūdzu mēģiniet mazāka izmēra failu.', // Drawing & Images 'image_upload_error' => 'Radās kļūda augšupielādējot attēlu', 'image_upload_type_error' => 'Ielādējamā attēla tips nav derīgs', 'image_upload_replace_type' => 'Aizvietojot attēlu tipiem ir jābūt vienādiem', - 'image_upload_memory_limit' => 'Failed to handle image upload and/or create thumbnails due to system resource limits.', - 'image_thumbnail_memory_limit' => 'Failed to create image size variations due to system resource limits.', - 'image_gallery_thumbnail_memory_limit' => 'Failed to create gallery thumbnails due to system resource limits.', + 'image_upload_memory_limit' => 'Neizdevās apstrādāt attēla ielādi vai izveidot attēlu variantus sistēmas resursu ierobežojumu dēļ.', + 'image_thumbnail_memory_limit' => 'Neizdevās izveidot attēla dažādu izmēru variantus sistēmas resursu ierobežojumu dēļ.', + 'image_gallery_thumbnail_memory_limit' => 'Neizdevās izveidot galerijas sīktēlus sistēmas resursu ierobežojumu dēļ.', 'drawing_data_not_found' => 'Attēla datus nevarēja ielādēt. Attēla fails, iespējams, vairs neeksistē, vai arī jums varētu nebūt piekļuves tiesības tam.', // Attachments @@ -115,5 +115,5 @@ return [ 'maintenance_test_email_failure' => 'Radusies kļūda sūtot testa epastu:', // HTTP errors - 'http_ssr_url_no_match' => 'The URL does not match the configured allowed SSR hosts', + 'http_ssr_url_no_match' => 'Adrese (URL) nesakrīt ar atļautajām SSR adresēm', ]; diff --git a/lang/lv/preferences.php b/lang/lv/preferences.php index aea349b3b..b45a06334 100644 --- a/lang/lv/preferences.php +++ b/lang/lv/preferences.php @@ -5,47 +5,47 @@ */ return [ - 'my_account' => 'My Account', + 'my_account' => 'Mans konts', 'shortcuts' => 'Saīsnes', - 'shortcuts_interface' => 'UI Shortcut Preferences', + 'shortcuts_interface' => 'Saskarnes īsceļu iestatījumi', 'shortcuts_toggle_desc' => 'Here you can enable or disable keyboard system interface shortcuts, used for navigation and actions.', 'shortcuts_customize_desc' => 'You can customize each of the shortcuts below. Just press your desired key combination after selecting the input for a shortcut.', 'shortcuts_toggle_label' => 'Klaviatūras saīsnes ieslēgtas', 'shortcuts_section_navigation' => 'Navigācija', - 'shortcuts_section_actions' => 'Common Actions', + 'shortcuts_section_actions' => 'Biežākās darbības', 'shortcuts_save' => 'Saglabāt saīsnes', 'shortcuts_overlay_desc' => 'Note: When shortcuts are enabled a helper overlay is available via pressing "?" which will highlight the available shortcuts for actions currently visible on the screen.', 'shortcuts_update_success' => 'Saīsņu uzstādījumi ir saglabāt!', 'shortcuts_overview_desc' => 'Manage keyboard shortcuts you can use to navigate the system user interface.', - 'notifications' => 'Notification Preferences', + 'notifications' => 'Paziņojumu iestatījumi', 'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.', - 'notifications_opt_own_page_changes' => 'Notify upon changes to pages I own', - 'notifications_opt_own_page_comments' => 'Notify upon comments on pages I own', - 'notifications_opt_comment_replies' => 'Notify upon replies to my comments', - 'notifications_save' => 'Save Preferences', - 'notifications_update_success' => 'Notification preferences have been updated!', - 'notifications_watched' => 'Watched & Ignored Items', + 'notifications_opt_own_page_changes' => 'Paziņot par izmaiņām manās lapās', + 'notifications_opt_own_page_comments' => 'Paziņot par komentāriem manās lapās', + 'notifications_opt_comment_replies' => 'Paziņot par atbildēm uz maniem komentāriem', + 'notifications_save' => 'Saglabāt iestatījumus', + 'notifications_update_success' => 'Paziņojumu iestatījumi ir atjaunoti!', + 'notifications_watched' => 'Vērotie un ignorētie vienumi', 'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.', - 'auth' => 'Access & Security', - 'auth_change_password' => 'Change Password', - 'auth_change_password_desc' => 'Change the password you use to log-in to the application. This must be at least 8 characters long.', - 'auth_change_password_success' => 'Password has been updated!', + 'auth' => 'Piekļuve un drošība', + 'auth_change_password' => 'Mainīt paroli', + 'auth_change_password_desc' => 'Mainīt paroli, ko izmantojat, lai piekļūtu aplikācijai. Tai jābūt vismaz 8 simbolus garai.', + 'auth_change_password_success' => 'Parole ir nomainīta!', - 'profile' => 'Profile Details', + 'profile' => 'Profila informācija', 'profile_desc' => 'Manage the details of your account which represents you to other users, in addition to details that are used for communication and system personalisation.', - 'profile_view_public' => 'View Public Profile', + 'profile_view_public' => 'Skatīt publisko profilu', 'profile_name_desc' => 'Configure your display name which will be visible to other users in the system through the activity you perform, and content you own.', - 'profile_email_desc' => 'This email will be used for notifications and, depending on active system authentication, system access.', - 'profile_email_no_permission' => 'Unfortunately you don\'t have permission to change your email address. If you want to change this, you\'d need to ask an administrator to change this for you.', + 'profile_email_desc' => 'Šis epasts tiks izmantots paziņojumiem un sistēmas piekļuvei, atkarībā no sistēmā uzstādītās autentifikācijas metodes.', + 'profile_email_no_permission' => 'Diemžēl jums nav tiesību mainīt savu epasta adresi. Ja vēlaties to mainīt, jums jāsazinās ar administratoru, lai tas nomaina šo adresi.', 'profile_avatar_desc' => 'Select an image which will be used to represent yourself to others in the system. Ideally this image should be square and about 256px in width and height.', 'profile_admin_options' => 'Administrator Options', 'profile_admin_options_desc' => 'Additional administrator-level options, like those to manage role assignments, can be found for your user account in the "Settings > Users" area of the application.', - 'delete_account' => 'Delete Account', - 'delete_my_account' => 'Delete My Account', + 'delete_account' => 'Dzēst kontu', + 'delete_my_account' => 'Izdzēst manu kontu', 'delete_my_account_desc' => 'This will fully delete your user account from the system. You will not be able to recover this account or revert this action. Content you\'ve created, such as created pages and uploaded images, will remain.', - 'delete_my_account_warning' => 'Are you sure you want to delete your account?', + 'delete_my_account_warning' => 'Vai tiešām vēlaties dzēst savu kontu?', ]; diff --git a/lang/lv/settings.php b/lang/lv/settings.php index a443d4977..5c07cfc1d 100644 --- a/lang/lv/settings.php +++ b/lang/lv/settings.php @@ -49,8 +49,8 @@ return [ // Color settings 'color_scheme' => 'Lietotnes krāsu shēma', - 'color_scheme_desc' => 'Set the colors to use in the application user interface. Colors can be configured separately for dark and light modes to best fit the theme and ensure legibility.', - 'ui_colors_desc' => 'Set the application primary color and default link color. The primary color is mainly used for the header banner, buttons and interface decorations. The default link color is used for text-based links and actions, both within written content and in the application interface.', + 'color_scheme_desc' => 'Uzstādiet krāsas, ko izmantot aplikācijas lietotātja saskarnē. Krāsas var uzstādīt atsevišķi gaišajam un tumšajam režīmam, lai labāk iederētos vizuālajā tēmā un nodrošinātu lasāmību.', + 'ui_colors_desc' => 'Uzstādiem aplikācijas primāro krāsu un noklusēto saišu krāsu. Primārā krāsa tiek izmantota galvenokārt lapas galvenē, uz pogām un saskarnes dekoratīvajos elementos. Noklusētā saišu krāsa tiek lietota teksta saitēm un darbībām gan izveidotajā saturā, gan aplikācijas saskarnē.', 'app_color' => 'Pamatkrāsa', 'link_color' => 'Noklusētā saišu krāsa', 'content_colors_desc' => 'Norādīt krāsas visiem lapas hierarhijas elementiem. Lasāmības labad ieteicams izvēlēties krāsas ar līdzīgu spilgtumu kā noklusētajām.', @@ -92,10 +92,10 @@ return [ 'maint_send_test_email_mail_text' => 'Apsveicam! Tā kā jūs saņēmāt šo epasta paziņojumu, jūsu epasta uzstādījumi šķiet pareizi.', 'maint_recycle_bin_desc' => 'Dzēstie plaukti, grāmatas, nodaļas un lapas ir pārceltas uz miskasti, lai tos varētu atjaunot vai izdzēst pilnībā. Vecākas vienības miskastē var tikt automātiski dzēstas pēc kāda laika atkarībā no sistēmas uzstādījumiem.', 'maint_recycle_bin_open' => 'Atvērt miskasti', - 'maint_regen_references' => 'Regenerate References', - 'maint_regen_references_desc' => 'This action will rebuild the cross-item reference index within the database. This is usually handled automatically but this action can be useful to index old content or content added via unofficial methods.', - 'maint_regen_references_success' => 'Reference index has been regenerated!', - 'maint_timeout_command_note' => 'Note: This action can take time to run, which can lead to timeout issues in some web environments. As an alternative, this action be performed using a terminal command.', + 'maint_regen_references' => 'Atjaunot atsauces', + 'maint_regen_references_desc' => 'Šī darbība no jauna izveidos atsauču indeksu datubāzē. Tas parasti notiek automātiski, taču šī darbība var palīdzēt, lai indeksētu vecāku saturu vai saturu, kas pievienots, izmantojot nestandarta metodes.', + 'maint_regen_references_success' => 'Atsauču indekss ir izveidots!', + 'maint_timeout_command_note' => 'Piezīme: Šī darbība var prasīt ilgāku laiku, kas var radīt pieprasījuma laika kļūmes (timeout) pie noteiktiem interneta vietnes uzstādījumiem. Alternatīva var būt veikt šo darbību, izmantojot termināla komandu.', // Recycle Bin 'recycle_bin' => 'Miskaste', @@ -214,8 +214,8 @@ return [ 'users_social_accounts_info' => 'Te jūs varat pieslēgt citus kontus ātrākai un ērtākai piekļuvei. Konta atvienošana no šejienes neatceļ šai aplikācijai dotās tiesības šī konta piekļuvei. Atvienojtiet piekļuvi arī no jūsu profila uzstādījumiem pievienotajā sociālajā kontā.', 'users_social_connect' => 'Pievienot kontu', 'users_social_disconnect' => 'Atvienot kontu', - 'users_social_status_connected' => 'Connected', - 'users_social_status_disconnected' => 'Disconnected', + 'users_social_status_connected' => 'Savienots', + 'users_social_status_disconnected' => 'Atvienots', 'users_social_connected' => ':socialAccount konts veiksmīgi pieslēgts jūsu profilam.', 'users_social_disconnected' => ':socialAccount konts veiksmīgi atslēgts no jūsu profila.', 'users_api_tokens' => 'API žetoni', diff --git a/lang/nl/activities.php b/lang/nl/activities.php index 633ac888e..261583cc3 100644 --- a/lang/nl/activities.php +++ b/lang/nl/activities.php @@ -15,7 +15,7 @@ return [ 'page_restore' => 'herstelde pagina', 'page_restore_notification' => 'Pagina succesvol hersteld', 'page_move' => 'verplaatste pagina', - 'page_move_notification' => 'Pagina met succes verplaatst', + 'page_move_notification' => 'Pagina succesvol verplaatst', // Chapters 'chapter_create' => 'maakte hoofdstuk', @@ -25,13 +25,13 @@ return [ 'chapter_delete' => 'verwijderde hoofdstuk', 'chapter_delete_notification' => 'Hoofdstuk succesvol verwijderd', 'chapter_move' => 'verplaatste hoofdstuk', - 'chapter_move_notification' => 'Hoofdstuk met succes verplaatst', + 'chapter_move_notification' => 'Hoofdstuk succesvol verplaatst', // Books 'book_create' => 'maakte boek', 'book_create_notification' => 'Boek succesvol aangemaakt', - 'book_create_from_chapter' => 'heeft hoofdstuk geconverteerd naar boek', - 'book_create_from_chapter_notification' => 'Hoofdstuk is succesvol geconverteerd naar boekenplank', + 'book_create_from_chapter' => 'converteerde hoofdstuk naar boek', + 'book_create_from_chapter_notification' => 'Hoofdstuk succesvol geconverteerd naar een boek', 'book_update' => 'wijzigde boek', 'book_update_notification' => 'Boek succesvol bijgewerkt', 'book_delete' => 'verwijderde boek', @@ -40,14 +40,14 @@ return [ 'book_sort_notification' => 'Boek succesvol opnieuw gesorteerd', // Bookshelves - 'bookshelf_create' => 'heeft boekenplank aangemaakt', - 'bookshelf_create_notification' => 'Boekenplank is succesvol aangemaakt', - 'bookshelf_create_from_book' => 'heeft boek geconverteerd naar boekenplank', - 'bookshelf_create_from_book_notification' => 'Boek is succesvol geconverteerd naar boekenplank', - 'bookshelf_update' => 'heeft boekenplank bijgewerkt', - 'bookshelf_update_notification' => 'Boekenplank is succesvol bijgewerkt', - 'bookshelf_delete' => 'heeft boekenplank verwijderd', - 'bookshelf_delete_notification' => 'Boekenplank is succesvol verwijderd', + 'bookshelf_create' => 'maakte boekenplank aan', + 'bookshelf_create_notification' => 'Boekenplank succesvol aangemaakt', + 'bookshelf_create_from_book' => 'converteerde boek naar boekenplank', + 'bookshelf_create_from_book_notification' => 'Boek succesvol geconverteerd naar boekenplank', + 'bookshelf_update' => 'werkte boekenplank bij', + 'bookshelf_update_notification' => 'Boekenplank succesvol bijgewerkt', + 'bookshelf_delete' => 'verwijderde boekenplank', + 'bookshelf_delete_notification' => 'Boekenplank succesvol verwijderd', // Revisions 'revision_restore' => 'herstelde revisie', @@ -62,50 +62,50 @@ return [ 'watch_update_level_notification' => 'Volg voorkeuren succesvol aangepast', // Auth - 'auth_login' => 'heeft ingelogd', - 'auth_register' => 'geregistreerd als nieuwe gebruiker', - 'auth_password_reset_request' => 'heeft een nieuw gebruikerswachtwoord aangevraagd', - 'auth_password_reset_update' => 'heeft zijn gebruikerswachtwoord opnieuw ingesteld', - 'mfa_setup_method' => 'heeft zijn meervoudige verificatie methode ingesteld', - 'mfa_setup_method_notification' => 'Meervoudige verificatie methode is succesvol geconfigureerd', - 'mfa_remove_method' => 'heeft zijn meervoudige verificatie methode verwijderd', + 'auth_login' => 'logde in', + 'auth_register' => 'registreerde als nieuwe gebruiker', + 'auth_password_reset_request' => 'vraagde een nieuw gebruikerswachtwoord aan', + 'auth_password_reset_update' => 'stelde gebruikerswachtwoord opnieuw in', + 'mfa_setup_method' => 'stelde MFA-methode in', + 'mfa_setup_method_notification' => 'Meervoudige verificatie methode succesvol geconfigureerd', + 'mfa_remove_method' => 'verwijderde MFA-methode', 'mfa_remove_method_notification' => 'Meervoudige verificatie methode is succesvol verwijderd', // Settings - 'settings_update' => 'heeft de instellingen bijgewerkt', + 'settings_update' => 'werkte instellingen bij', 'settings_update_notification' => 'Instellingen met succes bijgewerkt', - 'maintenance_action_run' => 'heeft onderhoud uitgevoerd', + 'maintenance_action_run' => 'voerde onderhoudsactie uit', // Webhooks - 'webhook_create' => 'webhook aangemaakt', + 'webhook_create' => 'maakte webhook aan', 'webhook_create_notification' => 'Webhook succesvol aangemaakt', - 'webhook_update' => 'webhook bijgewerkt', + 'webhook_update' => 'werkte webhook bij', 'webhook_update_notification' => 'Webhook succesvol bijgewerkt', - 'webhook_delete' => 'webhook verwijderd', + 'webhook_delete' => 'verwijderde webhook', 'webhook_delete_notification' => 'Webhook succesvol verwijderd', // Users - 'user_create' => 'heeft gebruiker aangemaakt', + 'user_create' => 'maakte gebruiker aan', 'user_create_notification' => 'Gebruiker met succes aangemaakt', - 'user_update' => 'heeft gebruiker bijgewerkt', + 'user_update' => 'werkte gebruiker bij', 'user_update_notification' => 'Gebruiker succesvol bijgewerkt', - 'user_delete' => 'heeft gebruiker verwijderd', + 'user_delete' => 'verwijderde gebruiker', 'user_delete_notification' => 'Gebruiker succesvol verwijderd', // API Tokens - 'api_token_create' => 'heeft API token aangemaakt', - 'api_token_create_notification' => 'API token met succes aangemaakt', - 'api_token_update' => 'heeft API token bijgewerkt', - 'api_token_update_notification' => 'API token met succes bijgewerkt', - 'api_token_delete' => 'heeft API token verwijderd', - 'api_token_delete_notification' => 'API token met succes verwijderd', + 'api_token_create' => 'maakte api token aan', + 'api_token_create_notification' => 'API-token met succes aangemaakt', + 'api_token_update' => 'werkte api token bij', + 'api_token_update_notification' => 'API-token met succes bijgewerkt', + 'api_token_delete' => 'verwijderde api token', + 'api_token_delete_notification' => 'API-token met succes verwijderd', // Roles - 'role_create' => 'heeft rol aangemaakt', + 'role_create' => 'maakte rol aan', 'role_create_notification' => 'Rol succesvol aangemaakt', - 'role_update' => 'heeft rol bijgewerkt', + 'role_update' => 'werkte rol bij', 'role_update_notification' => 'Rol succesvol bijgewerkt', - 'role_delete' => 'heeft rol verwijderd', + 'role_delete' => 'verwijderde rol', 'role_delete_notification' => 'Rol succesvol verwijderd', // Recycle Bin @@ -115,9 +115,9 @@ return [ // Comments 'commented_on' => 'reageerde op', - 'comment_create' => 'heeft opmerking toegevoegd', - 'comment_update' => 'heeft opmerking aangepast', - 'comment_delete' => 'heeft opmerking verwijderd', + 'comment_create' => 'voegde opmerking toe', + 'comment_update' => 'paste opmerking aan', + 'comment_delete' => 'verwijderde opmerking', // Other 'permissions_update' => 'wijzigde machtigingen', diff --git a/lang/nl/auth.php b/lang/nl/auth.php index dab15caca..1b974647a 100644 --- a/lang/nl/auth.php +++ b/lang/nl/auth.php @@ -7,14 +7,14 @@ return [ 'failed' => 'Deze inloggegevens zijn niet bij ons bekend.', - 'throttle' => 'Te veel login pogingen! Probeer het opnieuw na :seconds seconden.', + 'throttle' => 'Te veel inlogpogingen! Probeer het opnieuw na :seconds seconden.', // Login & Register - 'sign_up' => 'Registreren', - 'log_in' => 'Inloggen', - 'log_in_with' => 'Login met :socialDriver', + 'sign_up' => 'Registreer', + 'log_in' => 'Log in', + 'log_in_with' => 'Log in met :socialDriver', 'sign_up_with' => 'Registreer met :socialDriver', - 'logout' => 'Uitloggen', + 'logout' => 'Log uit', 'name' => 'Naam', 'username' => 'Gebruikersnaam', @@ -23,7 +23,7 @@ return [ 'password_confirm' => 'Wachtwoord Bevestigen', 'password_hint' => 'Moet uit minstens 8 tekens bestaan', 'forgot_password' => 'Wachtwoord vergeten?', - 'remember_me' => 'Mij onthouden', + 'remember_me' => 'Onthoud Mij', 'ldap_email_hint' => 'Geef een e-mailadres op voor dit account.', 'create_account' => 'Account aanmaken', 'already_have_account' => 'Heb je al een account?', @@ -87,7 +87,7 @@ return [ 'mfa_setup_reconfigure' => 'Herconfigureren', 'mfa_setup_remove_confirmation' => 'Weet je zeker dat je deze multi-factor authenticatie methode wilt verwijderen?', 'mfa_setup_action' => 'Instellen', - 'mfa_backup_codes_usage_limit_warning' => 'U heeft minder dan 5 back-upcodes resterend. Genereer en sla een nieuwe set op voordat je geen codes meer hebt om te voorkomen dat je buiten je account wordt gesloten.', + 'mfa_backup_codes_usage_limit_warning' => 'U heeft minder dan 5 back-upcodes over. Genereer en sla een nieuwe set op voordat je geen codes meer hebt om te voorkomen dat je buiten je account wordt gesloten.', 'mfa_option_totp_title' => 'Mobiele app', 'mfa_option_totp_desc' => 'Om multi-factor authenticatie te gebruiken heeft u een mobiele applicatie nodig die TOTP ondersteunt, zoals Google Authenticator, Authy of Microsoft Authenticator.', 'mfa_option_backup_codes_title' => 'Back-up Codes', diff --git a/lang/nl/entities.php b/lang/nl/entities.php index 48625edd6..ee13185f6 100644 --- a/lang/nl/entities.php +++ b/lang/nl/entities.php @@ -6,24 +6,24 @@ return [ // Shared - 'recently_created' => 'Recent aangemaakt', - 'recently_created_pages' => 'Recent aangemaakte pagina\'s', + 'recently_created' => 'Recent gemaakt', + 'recently_created_pages' => 'Recent gemaakte pagina\'s', 'recently_updated_pages' => 'Recent bijgewerkte pagina\'s', - 'recently_created_chapters' => 'Recent aangemaakte hoofdstukken', - 'recently_created_books' => 'Recent aangemaakte boeken', - 'recently_created_shelves' => 'Recent aangemaakte boekenplanken', + 'recently_created_chapters' => 'Recent gemaakte hoofdstukken', + 'recently_created_books' => 'Recent gemaakte boeken', + 'recently_created_shelves' => 'Recent gemaakte boekenplanken', 'recently_update' => 'Recent bijgewerkt', 'recently_viewed' => 'Recent bekeken', 'recent_activity' => 'Recente activiteit', 'create_now' => 'Maak er nu één', 'revisions' => 'Revisies', 'meta_revision' => 'Revisie #:revisionCount', - 'meta_created' => 'Aangemaakt :timeLength', - 'meta_created_name' => 'Aangemaakt: :timeLength door :user', + 'meta_created' => 'Gemaakt op: :timeLength', + 'meta_created_name' => 'Gemaakt op :timeLength door :user', 'meta_updated' => 'Bijgewerkt: :timeLength', 'meta_updated_name' => 'Bijgewerkt: :timeLength door :user', 'meta_owned_name' => 'Eigendom van :user', - 'meta_reference_count' => 'Referenced by :count item|Referenced by :count items', + 'meta_reference_count' => 'Gerefereerd door :count item|Gerefereerd door :count items', 'entity_select' => 'Entiteit selecteren', 'entity_select_lack_permission' => 'Je hebt niet de vereiste machtiging om dit item te selecteren', 'images' => 'Afbeeldingen', @@ -32,7 +32,7 @@ return [ 'my_most_viewed_favourites' => 'Mijn meest bekeken favorieten', 'my_favourites' => 'Mijn favorieten', 'no_pages_viewed' => 'Je hebt nog geen pagina\'s bekeken', - 'no_pages_recently_created' => 'Er zijn geen recent aangemaakte pagina\'s', + 'no_pages_recently_created' => 'Er zijn geen recent gemaakte pagina\'s', 'no_pages_recently_updated' => 'Er zijn geen pagina\'s recent bijgewerkt', 'export' => 'Exporteer', 'export_html' => 'Ingesloten webbestand', @@ -43,26 +43,26 @@ return [ // Permissions and restrictions 'permissions' => 'Machtigingen', 'permissions_desc' => 'Stel hier machtigingen in om de standaardmachtigingen van gebruikersrollen te overschrijven.', - 'permissions_book_cascade' => 'Machtigingen voor boeken worden automatisch doorgegeven aan hoofdstukken en pagina\'s, tenzij deze hun eigen machtigingen hebben.', - 'permissions_chapter_cascade' => 'Machtigingen ingesteld op hoofdstukken zullen automatisch worden doorgegeven aan onderliggende pagina\'s, tenzij deze hun eigen machtigingen hebben.', + 'permissions_book_cascade' => 'Machtigingen voor boeken worden automatisch doorgegeven aan hoofdstukken en pagina\'s, behalve als deze hun eigen machtigingen hebben.', + 'permissions_chapter_cascade' => 'Machtigingen ingesteld op hoofdstukken zullen automatisch worden doorgegeven aan onderliggende pagina\'s, behalve als deze hun eigen machtigingen hebben.', 'permissions_save' => 'Machtigingen opslaan', 'permissions_owner' => 'Eigenaar', 'permissions_role_everyone_else' => 'De rest', - 'permissions_role_everyone_else_desc' => 'Stel machtigingen in voor alle rollen die niet specifiek overschreven worden.', + 'permissions_role_everyone_else_desc' => 'Stel machtigingen in voor alle rollen die niet specifiek overschreven zijn.', 'permissions_role_override' => 'Overschrijf machtigingen voor rol', 'permissions_inherit_defaults' => 'Standaardwaarden overnemen', // Search 'search_results' => 'Zoekresultaten', - 'search_total_results_found' => ':count resultaten gevonden|:count totaal aantal resultaten gevonden', + 'search_total_results_found' => ':count resultaten gevonden|totaal :count resultaten gevonden', 'search_clear' => 'Zoekopdracht wissen', - 'search_no_pages' => 'Er zijn geen pagina\'s gevonden', + 'search_no_pages' => 'Geen pagina\'s gevonden die overeenkomen met deze zoekopdracht', 'search_for_term' => 'Zoeken op :term', 'search_more' => 'Meer resultaten', 'search_advanced' => 'Uitgebreid zoeken', 'search_terms' => 'Zoektermen', 'search_content_type' => 'Inhoudstype', - 'search_exact_matches' => 'Exacte matches', + 'search_exact_matches' => 'Exacte overeenkomsten', 'search_tags' => 'Label Zoekopdrachten', 'search_options' => 'Opties', 'search_viewed_by_me' => 'Bekeken door mij', @@ -74,8 +74,8 @@ return [ 'search_date_options' => 'Datum opties', 'search_updated_before' => 'Bijgewerkt voor', 'search_updated_after' => 'Bijgewerkt na', - 'search_created_before' => 'Aangemaakt voor', - 'search_created_after' => 'Aangemaakt na', + 'search_created_before' => 'Gemaakt voor', + 'search_created_after' => 'Gemaakt na', 'search_set_date' => 'Stel datum in', 'search_update' => 'Update zoekresultaten', @@ -83,13 +83,13 @@ return [ 'shelf' => 'Boekenplank', 'shelves' => 'Boekenplanken', 'x_shelves' => ':count Boekenplank|:count Boekenplanken', - 'shelves_empty' => 'Er zijn geen boekenplanken aangemaakt', + 'shelves_empty' => 'Er zijn geen boekenplanken gemaakt', 'shelves_create' => 'Nieuwe boekenplank maken', 'shelves_popular' => 'Populaire boekenplanken', 'shelves_new' => 'Nieuwe boekenplanken', 'shelves_new_action' => 'Nieuwe boekenplank', 'shelves_popular_empty' => 'De meest populaire boekenplanken worden hier weergegeven.', - 'shelves_new_empty' => 'De meest recent aangemaakte boekenplanken worden hier weergeven.', + 'shelves_new_empty' => 'De meest recent gemaakte boekenplanken worden hier weergeven.', 'shelves_save' => 'Boekenplank opslaan', 'shelves_books' => 'Boeken op deze plank', 'shelves_add_books' => 'Voeg boeken toe aan deze plank', @@ -106,23 +106,23 @@ return [ 'shelves_permissions_updated' => 'Boekenplank Machtigingen Bijgewerkt', 'shelves_permissions_active' => 'Machtigingen op Boekenplank Actief', 'shelves_permissions_cascade_warning' => 'De ingestelde machtigingen op deze boekenplank worden niet automatisch toegepast op de boeken van deze boekenplank. Dit is omdat een boek toegekend kan worden op meerdere boekenplanken. De machtigingen van deze boekenplank kunnen echter wel gekopieerd worden naar de boeken van deze boekenplank via de optie hieronder.', - 'shelves_permissions_create' => '\'Maak boekenplank\' machtigingen worden enkel gebruikt om machtigingen te kopiëren naar boeken binnenin een boekenplank door gebruik te maken van onderstaande actie. Deze machtigingen laten niet toe om een nieuw boek aan te maken.', + 'shelves_permissions_create' => '\'Maak boekenplank\' machtigingen worden enkel gebruikt om machtigingen te kopiëren naar boeken binnenin een boekenplank door gebruik te maken van onderstaande actie. Deze machtigingen beheren het maken van boeken niet.', 'shelves_copy_permissions_to_books' => 'Kopieer Machtigingen naar Boeken', 'shelves_copy_permissions' => 'Kopieer Machtigingen', - 'shelves_copy_permissions_explain' => 'Met deze actie worden de machtigingen van deze boekenplank gekopieërd naar alle boeken van deze boekenplank. Voor je deze actie uitvoert, moet je ervoor zorgen dat alle wijzigingen in de machtigingen van deze boekenplank zijn opgeslagen.', - 'shelves_copy_permission_success' => 'Boekenplank machtingen gekopieerd naar :count boeken', + 'shelves_copy_permissions_explain' => 'Met deze actie worden de machtigingen van deze boekenplank gekopieerd naar alle boeken van deze boekenplank. Voor je deze actie uitvoert, moet je ervoor zorgen dat alle wijzigingen in de machtigingen van deze boekenplank zijn opgeslagen.', + 'shelves_copy_permission_success' => 'Boekenplank machtigingen gekopieerd naar :count boeken', // Books 'book' => 'Boek', 'books' => 'Boeken', 'x_books' => ':count Boek|:count Boeken', - 'books_empty' => 'Er zijn geen boeken aangemaakt', + 'books_empty' => 'Geen boeken gemaakt', 'books_popular' => 'Populaire boeken', 'books_recent' => 'Recente boeken', 'books_new' => 'Nieuwe boeken', 'books_new_action' => 'Nieuw boek', 'books_popular_empty' => 'De meest populaire boeken worden hier weergegeven.', - 'books_new_empty' => 'De meest recent aangemaakte boeken verschijnen hier.', + 'books_new_empty' => 'De meest recent gemaakte boeken verschijnen hier.', 'books_create' => 'Nieuw boek maken', 'books_delete' => 'Boek verwijderen', 'books_delete_named' => 'Verwijder boek :bookName', @@ -132,9 +132,9 @@ return [ 'books_edit_named' => 'Bewerk boek :bookName', 'books_form_book_name' => 'Boek naam', 'books_save' => 'Boek opslaan', - 'books_default_template' => 'Default Page Template', - 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', - 'books_default_template_select' => 'Select a template page', + 'books_default_template' => 'Standaard Paginasjabloon', + 'books_default_template_explain' => 'Wijs een paginasjabloon toe die zal worden gebruikt als de standaardinhoud voor alle nieuwe pagina\'s in dit boek. Bedenk wel dat dit sjabloon alleen gebruikt wordt als de paginamaker machtiging heeft om die te bekijken.', + 'books_default_template_select' => 'Selecteer een sjabloonpagina', 'books_permissions' => 'Boek machtigingen', 'books_permissions_updated' => 'Boek Machtigingen Bijgewerkt', 'books_empty_contents' => 'Er zijn nog geen hoofdstukken en pagina\'s voor dit boek gemaakt.', @@ -145,7 +145,7 @@ return [ 'books_search_this' => 'Zoeken in dit boek', 'books_navigation' => 'Boek navigatie', 'books_sort' => 'Inhoud van het boek sorteren', - 'books_sort_desc' => 'Verplaats hoofdstukken en pagina\'s binnen een boek om de inhoud ervan te reorganiseren. Andere boeken kunnen worden toegevoegd, zodat hoofdstukken en pagina\'s gemakkelijk tussen boeken kunnen worden verplaatst.', + 'books_sort_desc' => 'Verplaats hoofdstukken en pagina\'s binnen een boek om ze te organiseren. Andere boeken kunnen worden toegevoegd, zodat hoofdstukken en pagina\'s gemakkelijk tussen boeken kunnen worden verplaatst.', 'books_sort_named' => 'Sorteer boek :bookName', 'books_sort_name' => 'Sorteren op naam', 'books_sort_created' => 'Sorteren op datum van aanmaken', @@ -177,7 +177,7 @@ return [ 'chapters_create' => 'Nieuw hoofdstuk maken', 'chapters_delete' => 'Hoofdstuk verwijderen', 'chapters_delete_named' => 'Verwijder hoofdstuk :chapterName', - 'chapters_delete_explain' => 'Dit verwijdert het hoofdstuk met de naam \':chapterName\'. Alle pagina\'s die binnen dit hoofdstuk staan, worden ook verwijderd.', + 'chapters_delete_explain' => 'Dit verwijdert het hoofdstuk met de naam \':chapterName\'. Alle pagina\'s in dit hoofdstuk zullen ook worden verwijderd.', 'chapters_delete_confirm' => 'Weet je zeker dat je dit hoofdstuk wilt verwijderen?', 'chapters_edit' => 'Hoofdstuk aanpassen', 'chapters_edit_named' => 'Hoofdstuk :chapterName aanpassen', @@ -187,10 +187,10 @@ return [ 'chapters_copy' => 'Kopieer Hoofdstuk', 'chapters_copy_success' => 'Hoofdstuk succesvol gekopieerd', 'chapters_permissions' => 'Hoofdstuk Machtigingen', - 'chapters_empty' => 'Er zijn geen pagina\'s in dit hoofdstuk aangemaakt.', + 'chapters_empty' => 'Dit hoofdstuk heeft op dit moment geen pagina\'s.', 'chapters_permissions_active' => 'Hoofdstuk Machtigingen Actief', 'chapters_permissions_success' => 'Hoofdstuk Machtigingen Bijgewerkt', - 'chapters_search_this' => 'Doorzoek dit hoofdstuk', + 'chapters_search_this' => 'Zoek in dit hoofdstuk', 'chapter_sort_book' => 'Sorteer Boek', // Pages @@ -202,15 +202,15 @@ return [ 'pages_attachments' => 'Bijlages', 'pages_navigation' => 'Pagina navigatie', 'pages_delete' => 'Pagina verwijderen', - 'pages_delete_named' => 'Verwijderd pagina :pageName', + 'pages_delete_named' => 'Verwijder pagina :pageName', 'pages_delete_draft_named' => 'Verwijder concept pagina :pageName', 'pages_delete_draft' => 'Verwijder concept pagina', 'pages_delete_success' => 'Pagina verwijderd', 'pages_delete_draft_success' => 'Concept verwijderd', - 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', + 'pages_delete_warning_template' => 'Deze pagina is actief in gebruik als standaard paginasjabloon. Deze boeken zullen geen standaard paginasjabloon meer hebben als deze pagina verwijderd is.', 'pages_delete_confirm' => 'Weet je zeker dat je deze pagina wilt verwijderen?', 'pages_delete_draft_confirm' => 'Weet je zeker dat je dit concept wilt verwijderen?', - 'pages_editing_named' => 'Pagina :pageName bewerken', + 'pages_editing_named' => 'Pagina :pageName aan het bewerken', 'pages_edit_draft_options' => 'Concept opties', 'pages_edit_save_draft' => 'Concept opslaan', 'pages_edit_draft' => 'Paginaconcept bewerken', @@ -220,36 +220,36 @@ return [ 'pages_edit_delete_draft' => 'Concept verwijderen', 'pages_edit_delete_draft_confirm' => 'Weet je zeker dat je de wijzigingen in je concept wilt verwijderen? Al je wijzigingen sinds de laatste succesvolle bewaring gaan verloren en de editor wordt bijgewerkt met de meest recente niet-concept versie van de pagina.', 'pages_edit_discard_draft' => 'Concept verwijderen', - 'pages_edit_switch_to_markdown' => 'Verander naar Markdown Bewerker', - 'pages_edit_switch_to_markdown_clean' => '(Schoongemaakte Inhoud)', + 'pages_edit_switch_to_markdown' => 'Schakel naar de Markdown Bewerker', + 'pages_edit_switch_to_markdown_clean' => '(Opgeschoonde Inhoud)', 'pages_edit_switch_to_markdown_stable' => '(Stabiele Inhoud)', - 'pages_edit_switch_to_wysiwyg' => 'Verander naar WYSIWYG Bewerker', - 'pages_edit_set_changelog' => 'Wijzigingslogboek instellen', + 'pages_edit_switch_to_wysiwyg' => 'Schakel naar de WYSIWYG Bewerker', + 'pages_edit_set_changelog' => 'Logboek instellen', 'pages_edit_enter_changelog_desc' => 'Geef een korte omschrijving van de wijzigingen die je gemaakt hebt', - 'pages_edit_enter_changelog' => 'Voeg toe aan wijzigingslogboek', - 'pages_editor_switch_title' => 'Wijzig Bewerker', + 'pages_edit_enter_changelog' => 'Voeg toe aan logboek', + 'pages_editor_switch_title' => 'Schakel Bewerker', 'pages_editor_switch_are_you_sure' => 'Weet u zeker dat u de bewerker voor deze pagina wilt wijzigen?', 'pages_editor_switch_consider_following' => 'Houd rekening met het volgende als u van bewerker verandert:', 'pages_editor_switch_consideration_a' => 'Eenmaal opgeslagen, zal de nieuwe bewerker keuze gebruikt worden door alle toekomstige gebruikers, ook diegene die zelf niet van bewerker type kunnen veranderen.', - 'pages_editor_switch_consideration_b' => 'Dit kan mogelijks tot een verlies van detail en syntax leiden in bepaalde omstandigheden.', - 'pages_editor_switch_consideration_c' => 'De veranderingen aan Labels of aan het wijzigingslogboek, sinds de laatste keer opslaan, zullen niet behouden blijven met deze wijziging.', + 'pages_editor_switch_consideration_b' => 'Dit kan mogelijk tot een verlies van detail en syntaxis leiden in bepaalde omstandigheden.', + 'pages_editor_switch_consideration_c' => 'De veranderingen aan Labels of aan het logboek, sinds de laatste keer opslaan, zullen niet behouden blijven met deze wijziging.', 'pages_save' => 'Pagina opslaan', 'pages_title' => 'Pagina titel', 'pages_name' => 'Pagina naam', 'pages_md_editor' => 'Bewerker', 'pages_md_preview' => 'Voorbeeld', 'pages_md_insert_image' => 'Afbeelding invoegen', - 'pages_md_insert_link' => 'Entity link invoegen', + 'pages_md_insert_link' => 'Entiteit link invoegen', 'pages_md_insert_drawing' => 'Tekening invoegen', - 'pages_md_show_preview' => 'Toon preview', - 'pages_md_sync_scroll' => 'Synchroniseer preview scroll', + 'pages_md_show_preview' => 'Toon voorbeeld', + 'pages_md_sync_scroll' => 'Synchroniseer scrollen van voorbeeld', 'pages_drawing_unsaved' => 'Niet-opgeslagen Tekening Gevonden', 'pages_drawing_unsaved_confirm' => 'Er zijn niet-opgeslagen tekeninggegevens gevonden van een eerdere mislukte poging om de tekening op te slaan. Wilt u deze niet-opgeslagen tekening herstellen en verder bewerken?', - 'pages_not_in_chapter' => 'Deze pagina staat niet in een hoofdstuk', + 'pages_not_in_chapter' => 'Pagina is niet in een hoofdstuk', 'pages_move' => 'Pagina verplaatsten', 'pages_copy' => 'Pagina kopiëren', 'pages_copy_desination' => 'Kopieër bestemming', - 'pages_copy_success' => 'Pagina succesvol gekopieërd', + 'pages_copy_success' => 'Pagina succesvol gekopieerd', 'pages_permissions' => 'Pagina Machtigingen', 'pages_permissions_success' => 'Pagina machtigingen bijgewerkt', 'pages_revision' => 'Revisie', @@ -258,21 +258,21 @@ return [ 'pages_revisions_named' => 'Pagina revisies voor :pageName', 'pages_revision_named' => 'Pagina revisie voor :pageName', 'pages_revision_restored_from' => 'Hersteld van #:id; :samenvatting', - 'pages_revisions_created_by' => 'Aangemaakt door', + 'pages_revisions_created_by' => 'Gemaakt door', 'pages_revisions_date' => 'Revisiedatum', 'pages_revisions_number' => '#', 'pages_revisions_sort_number' => 'Versie Nummer', 'pages_revisions_numbered' => 'Revisie #:id', 'pages_revisions_numbered_changes' => 'Revisie #:id wijzigingen', 'pages_revisions_editor' => 'Bewerker Type', - 'pages_revisions_changelog' => 'Wijzigingsoverzicht', + 'pages_revisions_changelog' => 'Logboek', 'pages_revisions_changes' => 'Wijzigingen', 'pages_revisions_current' => 'Huidige versie', 'pages_revisions_preview' => 'Voorbeeld', 'pages_revisions_restore' => 'Herstellen', 'pages_revisions_none' => 'Deze pagina heeft geen revisies', 'pages_copy_link' => 'Link kopiëren', - 'pages_edit_content_link' => 'Spring naar sectie in editor', + 'pages_edit_content_link' => 'Ga naar sectie in bewerker', 'pages_pointer_enter_mode' => 'Open selectiemodus per onderdeel', 'pages_pointer_label' => 'Pagina Onderdeel Opties', 'pages_pointer_permalink' => 'Pagina Onderdeel Permalink', @@ -285,16 +285,16 @@ return [ 'pages_initial_name' => 'Nieuwe pagina', 'pages_editing_draft_notification' => 'U bewerkt momenteel een concept dat voor het laatst is opgeslagen op :timeDiff.', 'pages_draft_edited_notification' => 'Deze pagina is sindsdien bijgewerkt. Het wordt aanbevolen dat u dit concept verwijderd.', - 'pages_draft_page_changed_since_creation' => 'Deze pagina is bijgewerkt sinds het aanmaken van dit concept. Het wordt aanbevolen dat u dit ontwerp verwijdert of ervoor zorgt dat u wijzigingen op de pagina niet overschrijft.', + 'pages_draft_page_changed_since_creation' => 'Deze pagina is bijgewerkt sinds het aanmaken van dit concept. Het wordt aanbevolen dat u dit concept verwijdert of ervoor zorgt dat u wijzigingen op de pagina niet overschrijft.', 'pages_draft_edit_active' => [ 'start_a' => ':count gebruikers zijn begonnen deze pagina te bewerken', 'start_b' => ':userName is begonnen met het bewerken van deze pagina', - 'time_a' => 'since the pages was last updated', + 'time_a' => 'sinds de laatste pagina-update', 'time_b' => 'in de laatste :minCount minuten', 'message' => ':start :time. Let op om elkaars updates niet te overschrijven!', ], - 'pages_draft_discarded' => 'Concept verwporpen! De editor is bijgewerkt met de huidige inhoud van de pagina', - 'pages_draft_deleted' => 'Concept verwijderd! De editor is bijgewerkt met de huidige inhoud van de pagina', + 'pages_draft_discarded' => 'Concept verworpen! De bewerker is bijgewerkt met de huidige inhoud van de pagina', + 'pages_draft_deleted' => 'Concept verwijderd! De bewerker is bijgewerkt met de huidige inhoud van de pagina', 'pages_specific' => 'Specifieke pagina', 'pages_is_template' => 'Paginasjabloon', @@ -312,7 +312,7 @@ return [ 'tags_explain' => "Voeg enkele labels toe om uw inhoud beter te categoriseren. \nJe kunt een waarde aan een label toekennen voor een meer gedetailleerde organisatie.", 'tags_add' => 'Voeg nog een label toe', 'tags_remove' => 'Verwijder deze label', - 'tags_usages' => 'Totaal aantal gebruikte labels', + 'tags_usages' => 'Totaal aantal label-toepassingen', 'tags_assigned_pages' => 'Toegewezen aan pagina\'s', 'tags_assigned_chapters' => 'Toegewezen aan hoofdstukken', 'tags_assigned_books' => 'Toegewezen aan boeken', @@ -327,7 +327,7 @@ return [ 'attachments_explain_instant_save' => 'Wijzigingen worden meteen opgeslagen.', 'attachments_upload' => 'Bestand uploaden', 'attachments_link' => 'Link toevoegen', - 'attachments_upload_drop' => 'Of je kan een bestand hiernaartoe slepen om het als bijlage te uploaden.', + 'attachments_upload_drop' => 'Je kan ook een bestand hiernaartoe slepen om het als bijlage to uploaden.', 'attachments_set_link' => 'Zet link', 'attachments_delete' => 'Weet u zeker dat u deze bijlage wilt verwijderen?', 'attachments_dropzone' => 'Sleep hier de bestanden naar toe', @@ -357,11 +357,11 @@ return [ // Profile View 'profile_user_for_x' => 'Lid sinds :time', - 'profile_created_content' => 'Aangemaakte Inhoud', + 'profile_created_content' => 'Gemaakte Inhoud', 'profile_not_created_pages' => ':userName heeft geen pagina\'s gemaakt', 'profile_not_created_chapters' => ':userName heeft geen hoofdstukken gemaakt', 'profile_not_created_books' => ':userName heeft geen boeken gemaakt', - 'profile_not_created_shelves' => ':userName heeft nog geen boekenplanken gemaakt', + 'profile_not_created_shelves' => ':userName heeft geen boekenplanken gemaakt', // Comments 'comment' => 'Reactie', @@ -392,7 +392,7 @@ return [ 'copy_consider_owner' => 'Je wordt de eigenaar van alle gekopieerde inhoud.', 'copy_consider_images' => 'Afbeeldingsbestanden worden niet gedupliceerd & de originele afbeeldingen behouden hun koppeling met de pagina waarop ze oorspronkelijk werden geüpload.', 'copy_consider_attachments' => 'Pagina bijlagen worden niet gekopieerd.', - 'copy_consider_access' => 'Een verandering van locatie, eigenaar of machtigingen kan ertoe leiden dat deze inhoud toegankelijk wordt voor personen die er voordien geen toegang tot hadden.', + 'copy_consider_access' => 'Een verandering van locatie, eigenaar of machtigingen kan ertoe leiden dat deze inhoud toegankelijk wordt voor personen die eerder geen toegang hadden.', // Conversions 'convert_to_shelf' => 'Converteer naar Boekenplank', @@ -408,8 +408,8 @@ return [ // References 'references' => 'Verwijzingen', - 'references_none' => 'Er zijn geen verwijzingen naar dit artikel bijgehouden.', - 'references_to_desc' => 'Listed below is all the known content in the system that links to this item.', + 'references_none' => 'Er zijn geen verwijzingen naar dit item bijgehouden.', + 'references_to_desc' => 'Hieronder is alle inhoud in het systeem dat naar dit item linkt vermeld.', // Watch Options 'watch' => 'Volg', diff --git a/lang/nl/errors.php b/lang/nl/errors.php index 7a026615a..3fa1d94f7 100644 --- a/lang/nl/errors.php +++ b/lang/nl/errors.php @@ -106,9 +106,9 @@ return [ // API errors 'api_no_authorization_found' => 'Geen autorisatie token gevonden', 'api_bad_authorization_format' => 'Een autorisatie token is gevonden, maar het formaat schijnt onjuist te zijn', - 'api_user_token_not_found' => 'Er is geen overeenkomende API token gevonden voor de opgegeven autorisatie token', - 'api_incorrect_token_secret' => 'Het opgegeven geheim voor de API token is onjuist', - 'api_user_no_api_permission' => 'De eigenaar van de gebruikte API token heeft geen machtiging om API calls te maken', + 'api_user_token_not_found' => 'Er is geen overeenkomende API-token gevonden voor de opgegeven autorisatie token', + 'api_incorrect_token_secret' => 'Het opgegeven geheim voor de API-token is onjuist', + 'api_user_no_api_permission' => 'De eigenaar van de gebruikte API-token heeft geen machtiging om API calls te maken', 'api_user_token_expired' => 'De gebruikte autorisatie token is verlopen', // Settings & Maintenance diff --git a/lang/nl/settings.php b/lang/nl/settings.php index c6e2ad2ae..014153e53 100644 --- a/lang/nl/settings.php +++ b/lang/nl/settings.php @@ -109,7 +109,7 @@ return [ 'recycle_bin_contents_empty' => 'De prullenbak is momenteel leeg', 'recycle_bin_empty' => 'Prullenbak legen', 'recycle_bin_empty_confirm' => 'Dit zal permanent alle items in de prullenbak vernietigen, inclusief de inhoud die in elk item zit. Weet u zeker dat u de prullenbak wilt legen?', - 'recycle_bin_destroy_confirm' => 'Deze actie zal dit item permanent verwijderen, samen met alle onderliggende elementen hieronder vanuit het systeem en u kunt deze inhoud niet herstellen. Weet u zeker dat u dit item permanent wilt verwijderen?', + 'recycle_bin_destroy_confirm' => 'Deze actie zal dit item permanent uit het systeem verwijderen, samen met alle onderliggende elementen, en u kunt deze inhoud niet herstellen. Weet u zeker dat u dit item permanent wil verwijderen?', 'recycle_bin_destroy_list' => 'Te vernietigen items', 'recycle_bin_restore_list' => 'Items te herstellen', 'recycle_bin_restore_confirm' => 'Deze actie herstelt het verwijderde item, inclusief alle onderliggende elementen, op hun oorspronkelijke locatie. Als de oorspronkelijke locatie sindsdien is verwijderd en zich nu in de prullenbak bevindt, zal ook het bovenliggende item moeten worden hersteld.', @@ -216,14 +216,14 @@ return [ 'users_social_disconnect' => 'Account Ontkoppelen', 'users_social_status_connected' => 'Verbonden', 'users_social_status_disconnected' => 'Verbroken', - 'users_social_connected' => ':socialAccount account is succesvol aan je profiel gekoppeld.', - 'users_social_disconnected' => ':socialAccount account is succesvol ontkoppeld van je profiel.', - 'users_api_tokens' => 'API Tokens', + 'users_social_connected' => ':socialAccount account succesvol aan je profiel gekoppeld.', + 'users_social_disconnected' => ':socialAccount account succesvol ontkoppeld van je profiel.', + 'users_api_tokens' => 'API-Tokens', 'users_api_tokens_desc' => 'Creëer en beheer de toegangstokens die gebruikt worden om te authenticeren met de BookStack REST API. Machtigingen voor de API worden beheerd via de gebruiker waartoe het token behoort.', 'users_api_tokens_none' => 'Er zijn geen API-tokens gemaakt voor deze gebruiker', 'users_api_tokens_create' => 'Token aanmaken', 'users_api_tokens_expires' => 'Verloopt', - 'users_api_tokens_docs' => 'API Documentatie', + 'users_api_tokens_docs' => 'API-Documentatie', 'users_mfa' => 'Meervoudige Verificatie', 'users_mfa_desc' => 'Stel meervoudige verificatie in als extra beveiligingslaag voor uw gebruikersaccount.', 'users_mfa_x_methods' => ':count methode geconfigureerd|:count methoden geconfigureerd', @@ -236,11 +236,11 @@ return [ 'user_api_token_expiry' => 'Vervaldatum', 'user_api_token_expiry_desc' => 'Stel een datum in waarop deze token verloopt. Na deze datum zullen aanvragen die met deze token zijn ingediend niet langer werken. Als dit veld leeg blijft, wordt een vervaldatum van 100 jaar in de toekomst ingesteld.', 'user_api_token_create_secret_message' => 'Onmiddellijk na het aanmaken van dit token zal een "Token ID" en "Token Geheim" worden gegenereerd en weergegeven. Het geheim zal slechts één keer getoond worden. Kopieer de waarde dus eerst op een veilige plaats voordat u doorgaat.', - 'user_api_token' => 'API Token', + 'user_api_token' => 'API-Token', 'user_api_token_id' => 'Token ID', 'user_api_token_id_desc' => 'Dit is een niet-wijzigbare, door het systeem gegenereerde identificatiecode voor dit token, die in API-verzoeken moet worden verstrekt.', 'user_api_token_secret' => 'Geheime token sleutel', - 'user_api_token_secret_desc' => 'Dit is een door het systeem gegenereerd geheim voor dit token dat in API verzoeken zal moeten worden verstrekt. Dit zal slechts één keer worden weergegeven, dus kopieer deze waarde naar een veilige plaats.', + 'user_api_token_secret_desc' => 'Dit is een door het systeem gegenereerd geheim voor dit token dat in API-verzoeken zal moeten worden verstrekt. Dit zal slechts één keer worden weergegeven, dus kopieer deze waarde naar een veilige plaats.', 'user_api_token_created' => 'Token :timeAgo geleden aangemaakt', 'user_api_token_updated' => 'Token :timeAgo geleden bijgewerkt', 'user_api_token_delete' => 'Token Verwijderen', diff --git a/lang/sq/activities.php b/lang/sq/activities.php index d5b55c03d..1c7a10310 100644 --- a/lang/sq/activities.php +++ b/lang/sq/activities.php @@ -6,119 +6,119 @@ return [ // Pages - 'page_create' => 'created page', - 'page_create_notification' => 'Page successfully created', - 'page_update' => 'updated page', - 'page_update_notification' => 'Page successfully updated', - 'page_delete' => 'deleted page', - 'page_delete_notification' => 'Page successfully deleted', - 'page_restore' => 'restored page', - 'page_restore_notification' => 'Page successfully restored', - 'page_move' => 'moved page', - 'page_move_notification' => 'Page successfully moved', + 'page_create' => 'krijoi faqe', + 'page_create_notification' => 'Faqja u krijua me sukses', + 'page_update' => 'përditësoi faqe', + 'page_update_notification' => 'Faqja u përditësua me sukses', + 'page_delete' => 'fshiu faqe', + 'page_delete_notification' => 'Faqja u fshi me sukses', + 'page_restore' => 'riktheu faqe', + 'page_restore_notification' => 'Faqja u rikthye me sukses', + 'page_move' => 'zhvendosi faqe', + 'page_move_notification' => 'Faqja u zhvendos me sukses', // Chapters - 'chapter_create' => 'created chapter', - 'chapter_create_notification' => 'Chapter successfully created', - 'chapter_update' => 'updated chapter', - 'chapter_update_notification' => 'Chapter successfully updated', - 'chapter_delete' => 'deleted chapter', - 'chapter_delete_notification' => 'Chapter successfully deleted', - 'chapter_move' => 'moved chapter', - 'chapter_move_notification' => 'Chapter successfully moved', + 'chapter_create' => 'krijoi kapitull', + 'chapter_create_notification' => 'Kapitulli u krijua me sukses', + 'chapter_update' => 'përditësoi kapitull', + 'chapter_update_notification' => 'Kapitulli u përditësua me sukses', + 'chapter_delete' => 'fshiu kapitull', + 'chapter_delete_notification' => 'Kapitulli u fshi me sukses', + 'chapter_move' => 'zhvendosi kapitull', + 'chapter_move_notification' => 'Kapitulli u zhvendos me sukses', // Books - 'book_create' => 'created book', - 'book_create_notification' => 'Book successfully created', - 'book_create_from_chapter' => 'converted chapter to book', - 'book_create_from_chapter_notification' => 'Chapter successfully converted to a book', - 'book_update' => 'updated book', - 'book_update_notification' => 'Book successfully updated', - 'book_delete' => 'deleted book', - 'book_delete_notification' => 'Book successfully deleted', - 'book_sort' => 'sorted book', - 'book_sort_notification' => 'Book successfully re-sorted', + 'book_create' => 'krijoi libër', + 'book_create_notification' => 'Libri u krijua me sukses', + 'book_create_from_chapter' => 'konvertoi kapitullin në libër', + 'book_create_from_chapter_notification' => 'Kapitulli u konvertua në libër me sukses', + 'book_update' => 'përditësoi libër', + 'book_update_notification' => 'Libri u përditësua me sukses', + 'book_delete' => 'fshiu libër', + 'book_delete_notification' => 'Libri u fshi me sukses', + 'book_sort' => 'renditi libër', + 'book_sort_notification' => 'Libri u rendit me sukses', // Bookshelves - 'bookshelf_create' => 'created shelf', - 'bookshelf_create_notification' => 'Shelf successfully created', - 'bookshelf_create_from_book' => 'converted book to shelf', - 'bookshelf_create_from_book_notification' => 'Book successfully converted to a shelf', - 'bookshelf_update' => 'updated shelf', - 'bookshelf_update_notification' => 'Shelf successfully updated', - 'bookshelf_delete' => 'deleted shelf', - 'bookshelf_delete_notification' => 'Shelf successfully deleted', + 'bookshelf_create' => 'krijoi raft', + 'bookshelf_create_notification' => 'Rafti u krijua me sukses', + 'bookshelf_create_from_book' => 'konvertoi librin në raft', + 'bookshelf_create_from_book_notification' => 'Libri u konvertua ne raft me sukses', + 'bookshelf_update' => 'përditësoi raftin', + 'bookshelf_update_notification' => 'Rafti u përditësua me sukses', + 'bookshelf_delete' => 'fshiu raftin', + 'bookshelf_delete_notification' => 'Rafti u fshi me sukses', // Revisions - 'revision_restore' => 'restored revision', - 'revision_delete' => 'deleted revision', - 'revision_delete_notification' => 'Revision successfully deleted', + 'revision_restore' => 'riktheu rishikimin', + 'revision_delete' => 'fshiu rishikimin', + 'revision_delete_notification' => 'Rishikimi u fshi me sukses', // Favourites - 'favourite_add_notification' => '":name" has been added to your favourites', - 'favourite_remove_notification' => '":name" has been removed from your favourites', + 'favourite_add_notification' => '":emri" është shtuar në listën tuaj të të preferuarve', + 'favourite_remove_notification' => '":emri" është hequr nga lista juaj e të preferuarve', // Watching - 'watch_update_level_notification' => 'Watch preferences successfully updated', + 'watch_update_level_notification' => 'Preferencat e orës u përditësuan me sukses', // Auth - 'auth_login' => 'logged in', - 'auth_register' => 'registered as new user', - 'auth_password_reset_request' => 'requested user password reset', - 'auth_password_reset_update' => 'reset user password', - 'mfa_setup_method' => 'configured MFA method', - 'mfa_setup_method_notification' => 'Multi-factor method successfully configured', - 'mfa_remove_method' => 'removed MFA method', - 'mfa_remove_method_notification' => 'Multi-factor method successfully removed', + 'auth_login' => 'loguar', + 'auth_register' => 'regjistruar si përdorues i ri', + 'auth_password_reset_request' => 'kërkoi rivendosjen e fjalëkalimit të përdoruesit', + 'auth_password_reset_update' => 'rivendos fjalëkalimin e përdoruesit', + 'mfa_setup_method' => 'konfiguroi metodën MFA', + 'mfa_setup_method_notification' => 'Metoda Multi-factor u konfigurua me sukses', + 'mfa_remove_method' => 'hoqi metodën MFA', + 'mfa_remove_method_notification' => 'Metoda Multi-factor u hoq me sukses', // Settings - 'settings_update' => 'updated settings', - 'settings_update_notification' => 'Settings successfully updated', - 'maintenance_action_run' => 'ran maintenance action', + 'settings_update' => 'përditësoi cilësimet', + 'settings_update_notification' => 'Cilësimet u përditësuan me sukses', + 'maintenance_action_run' => 'u zhvillua veprim i mirëmbajtjes', // Webhooks - 'webhook_create' => 'created webhook', - 'webhook_create_notification' => 'Webhook successfully created', - 'webhook_update' => 'updated webhook', - 'webhook_update_notification' => 'Webhook successfully updated', - 'webhook_delete' => 'deleted webhook', - 'webhook_delete_notification' => 'Webhook successfully deleted', + 'webhook_create' => 'u krijua uebhook', + 'webhook_create_notification' => 'Uebhook-u u krijua me sukses', + 'webhook_update' => 'përditësoi uebhook', + 'webhook_update_notification' => 'Uebhook-u u përditësua me sukses', + 'webhook_delete' => 'fshiu uebhook', + 'webhook_delete_notification' => 'Uebhook-u u fshi me sukses', // Users - 'user_create' => 'created user', - 'user_create_notification' => 'User successfully created', - 'user_update' => 'updated user', - 'user_update_notification' => 'User successfully updated', - 'user_delete' => 'deleted user', - 'user_delete_notification' => 'User successfully removed', + 'user_create' => 'krijoi përdorues', + 'user_create_notification' => 'Përdoruesi u krijua me sukses', + 'user_update' => 'përditësoi përdorues', + 'user_update_notification' => 'Përdoruesi u përditësua me sukses', + 'user_delete' => 'fshi përdorues', + 'user_delete_notification' => 'Përdoruesi u fshi me sukses', // API Tokens - 'api_token_create' => 'created api token', - 'api_token_create_notification' => 'API token successfully created', - 'api_token_update' => 'updated api token', - 'api_token_update_notification' => 'API token successfully updated', - 'api_token_delete' => 'deleted api token', - 'api_token_delete_notification' => 'API token successfully deleted', + 'api_token_create' => 'krijoi token api', + 'api_token_create_notification' => 'Token API u krijua me sukses', + 'api_token_update' => 'përditësoi token api', + 'api_token_update_notification' => 'Token API u përditësua me sukses', + 'api_token_delete' => 'fshiu token api', + 'api_token_delete_notification' => 'Token API u fshi me sukses', // Roles - 'role_create' => 'created role', - 'role_create_notification' => 'Role successfully created', - 'role_update' => 'updated role', - 'role_update_notification' => 'Role successfully updated', - 'role_delete' => 'deleted role', - 'role_delete_notification' => 'Role successfully deleted', + 'role_create' => 'krijoi rol', + 'role_create_notification' => 'Roli u krijua me sukses', + 'role_update' => 'përditësoi rol', + 'role_update_notification' => 'Roli u përditësua me sukses', + 'role_delete' => 'fshiu rol', + 'role_delete_notification' => 'Roli u fshi me sukses', // Recycle Bin - 'recycle_bin_empty' => 'emptied recycle bin', - 'recycle_bin_restore' => 'restored from recycle bin', - 'recycle_bin_destroy' => 'removed from recycle bin', + 'recycle_bin_empty' => 'boshatisi koshin e riciklimit', + 'recycle_bin_restore' => 'riktheu nga koshi i riciklimit', + 'recycle_bin_destroy' => 'fshiu nga koshi i riciklimit', // Comments - 'commented_on' => 'commented on', - 'comment_create' => 'added comment', - 'comment_update' => 'updated comment', - 'comment_delete' => 'deleted comment', + 'commented_on' => 'komentoi në', + 'comment_create' => 'shtoi koment', + 'comment_update' => 'përditësoi koment', + 'comment_delete' => 'fshiu koment', // Other - 'permissions_update' => 'updated permissions', + 'permissions_update' => 'përditësoi lejet', ]; diff --git a/lang/sq/auth.php b/lang/sq/auth.php index dc4b242a0..08af155e8 100644 --- a/lang/sq/auth.php +++ b/lang/sq/auth.php @@ -6,37 +6,37 @@ */ return [ - 'failed' => 'These credentials do not match our records.', - 'throttle' => 'Too many login attempts. Please try again in :seconds seconds.', + 'failed' => 'Këto kredenciale nuk përputhen me të dhënat tona.', + 'throttle' => 'Shumë përpjekje për hyrje. Ju lutemi provoni përsëri në :seconds sekonda.', // Login & Register - 'sign_up' => 'Sign up', - 'log_in' => 'Log in', - 'log_in_with' => 'Login with :socialDriver', - 'sign_up_with' => 'Sign up with :socialDriver', - 'logout' => 'Logout', + 'sign_up' => 'Regjistrohu', + 'log_in' => 'Logohu', + 'log_in_with' => 'Logohu me :socialDriver', + 'sign_up_with' => 'Regjistrohu me :socialDriver', + 'logout' => 'Shkyçu', - 'name' => 'Name', - 'username' => 'Username', + 'name' => 'Emri', + 'username' => 'Emri i përdoruesit', 'email' => 'Email', - 'password' => 'Password', - 'password_confirm' => 'Confirm Password', - 'password_hint' => 'Must be at least 8 characters', - 'forgot_password' => 'Forgot Password?', - 'remember_me' => 'Remember Me', - 'ldap_email_hint' => 'Please enter an email to use for this account.', - 'create_account' => 'Create Account', - 'already_have_account' => 'Already have an account?', - 'dont_have_account' => 'Don\'t have an account?', + 'password' => 'Fjalkalimi', + 'password_confirm' => 'Konfirmo fjalëkalimin', + 'password_hint' => 'Duhet të jetë të paktën 8 karaktere', + 'forgot_password' => 'Keni harruar fjalëkalimin?', + 'remember_me' => 'Më mbaj mend', + 'ldap_email_hint' => 'Ju lutem fusni një email që do përdorni për këtë llogari.', + 'create_account' => 'Krijo një llogari', + 'already_have_account' => 'Keni një llogari?', + 'dont_have_account' => 'Nuk keni akoma llogari?', 'social_login' => 'Social Login', 'social_registration' => 'Social Registration', - 'social_registration_text' => 'Register and sign in using another service.', + 'social_registration_text' => 'Regjistrohu dhe logohu duhet përdorur një shërbim tjetër.', - 'register_thanks' => 'Thanks for registering!', - 'register_confirm' => 'Please check your email and click the confirmation button to access :appName.', - 'registrations_disabled' => 'Registrations are currently disabled', - 'registration_email_domain_invalid' => 'That email domain does not have access to this application', - 'register_success' => 'Thanks for signing up! You are now registered and signed in.', + 'register_thanks' => 'Faleminderit që u regjistruat!', + 'register_confirm' => 'Ju lutem kontrolloni emai-in tuaj dhe klikoni te butoni i konfirmimit për të aksesuar :appName.', + 'registrations_disabled' => 'Regjistrimet janë të mbyllura', + 'registration_email_domain_invalid' => 'Ky domain email-i nuk ka akses te ky aplikacion', + 'register_success' => 'Faleminderit që u regjistruar! Ju tani jeni të regjistruar dhe të loguar.', // Login auto-initiation 'auto_init_starting' => 'Attempting Login', From eff7aa0f73a08c495f39c4eb4dc151201fa0cef2 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 24 Jan 2024 10:25:24 +0000 Subject: [PATCH 039/124] Updated translator attribution before v23.12.2 release --- .github/translators.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/translators.txt b/.github/translators.txt index ef16da257..950c9d695 100644 --- a/.github/translators.txt +++ b/.github/translators.txt @@ -388,3 +388,8 @@ diegobenitez :: Spanish Marc Hagen (MarcHagen) :: Dutch Kasper Alsøe (zeonos) :: Danish sultani :: Persian +renge :: Korean +TheGatesDev (thegatesdev) :: Dutch +Irdi (irdiOL) :: Albanian +KateBarber :: Welsh +Twister (theuncles75) :: Hebrew From 8fb9d9d4c2c2916430cd4ed7fa14840c4cc897cc Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 24 Jan 2024 10:27:09 +0000 Subject: [PATCH 040/124] Dependancies: Updated PHP deps via composer --- composer.lock | 268 ++++++++++++++++++++++++-------------------------- 1 file changed, 131 insertions(+), 137 deletions(-) diff --git a/composer.lock b/composer.lock index 56d33e78e..3bea1a66a 100644 --- a/composer.lock +++ b/composer.lock @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.294.5", + "version": "3.296.8", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "2e34d45e970c77775e4c298e08732d64b647c41c" + "reference": "f84fe470709e5ab9515649a1a0891e279693d1b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2e34d45e970c77775e4c298e08732d64b647c41c", - "reference": "2e34d45e970c77775e4c298e08732d64b647c41c", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f84fe470709e5ab9515649a1a0891e279693d1b3", + "reference": "f84fe470709e5ab9515649a1a0891e279693d1b3", "shasum": "" }, "require": { @@ -151,9 +151,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.294.5" + "source": "https://github.com/aws/aws-sdk-php/tree/3.296.8" }, - "time": "2023-12-21T19:10:21+00:00" + "time": "2024-01-23T20:32:59+00:00" }, { "name": "bacon/bacon-qr-code", @@ -708,16 +708,16 @@ }, { "name": "doctrine/dbal", - "version": "3.7.2", + "version": "3.7.3", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "0ac3c270590e54910715e9a1a044cc368df282b2" + "reference": "ce594cbc39a4866c544f1a970d285ff0548221ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/0ac3c270590e54910715e9a1a044cc368df282b2", - "reference": "0ac3c270590e54910715e9a1a044cc368df282b2", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/ce594cbc39a4866c544f1a970d285ff0548221ad", + "reference": "ce594cbc39a4866c544f1a970d285ff0548221ad", "shasum": "" }, "require": { @@ -733,14 +733,14 @@ "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "1.10.42", + "phpstan/phpstan": "1.10.56", "phpstan/phpstan-strict-rules": "^1.5", - "phpunit/phpunit": "9.6.13", + "phpunit/phpunit": "9.6.15", "psalm/plugin-phpunit": "0.18.4", "slevomat/coding-standard": "8.13.1", - "squizlabs/php_codesniffer": "3.7.2", - "symfony/cache": "^5.4|^6.0", - "symfony/console": "^4.4|^5.4|^6.0", + "squizlabs/php_codesniffer": "3.8.1", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/console": "^4.4|^5.4|^6.0|^7.0", "vimeo/psalm": "4.30.0" }, "suggest": { @@ -801,7 +801,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.7.2" + "source": "https://github.com/doctrine/dbal/tree/3.7.3" }, "funding": [ { @@ -817,7 +817,7 @@ "type": "tidelift" } ], - "time": "2023-11-19T08:06:58+00:00" + "time": "2024-01-21T07:53:09+00:00" }, { "name": "doctrine/deprecations", @@ -960,16 +960,16 @@ }, { "name": "doctrine/inflector", - "version": "2.0.8", + "version": "2.0.9", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff" + "reference": "2930cd5ef353871c821d5c43ed030d39ac8cfe65" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/f9301a5b2fb1216b2b08f02ba04dc45423db6bff", - "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/2930cd5ef353871c821d5c43ed030d39ac8cfe65", + "reference": "2930cd5ef353871c821d5c43ed030d39ac8cfe65", "shasum": "" }, "require": { @@ -1031,7 +1031,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.8" + "source": "https://github.com/doctrine/inflector/tree/2.0.9" }, "funding": [ { @@ -1047,7 +1047,7 @@ "type": "tidelift" } ], - "time": "2023-06-16T13:40:37+00:00" + "time": "2024-01-15T18:05:13+00:00" }, { "name": "doctrine/lexer", @@ -2349,25 +2349,25 @@ }, { "name": "laravel/tinker", - "version": "v2.8.2", + "version": "v2.9.0", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "b936d415b252b499e8c3b1f795cd4fc20f57e1f3" + "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/b936d415b252b499e8c3b1f795cd4fc20f57e1f3", - "reference": "b936d415b252b499e8c3b1f795cd4fc20f57e1f3", + "url": "https://api.github.com/repos/laravel/tinker/zipball/502e0fe3f0415d06d5db1f83a472f0f3b754bafe", + "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe", "shasum": "" }, "require": { - "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0", + "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "php": "^7.2.5|^8.0", - "psy/psysh": "^0.10.4|^0.11.1", - "symfony/var-dumper": "^4.3.4|^5.0|^6.0" + "psy/psysh": "^0.11.1|^0.12.0", + "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0" }, "require-dev": { "mockery/mockery": "~1.3.3|^1.4.2", @@ -2375,13 +2375,10 @@ "phpunit/phpunit": "^8.5.8|^9.3.3" }, "suggest": { - "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0)." + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0|^11.0)." }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - }, "laravel": { "providers": [ "Laravel\\Tinker\\TinkerServiceProvider" @@ -2412,9 +2409,9 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.8.2" + "source": "https://github.com/laravel/tinker/tree/v2.9.0" }, - "time": "2023-08-15T14:27:00+00:00" + "time": "2024-01-04T16:10:04+00:00" }, { "name": "league/commonmark", @@ -3348,16 +3345,16 @@ }, { "name": "nesbot/carbon", - "version": "2.72.1", + "version": "2.72.2", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "2b3b3db0a2d0556a177392ff1a3bf5608fa09f78" + "reference": "3e7edc41b58d65509baeb0d4a14c8fa41d627130" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/2b3b3db0a2d0556a177392ff1a3bf5608fa09f78", - "reference": "2b3b3db0a2d0556a177392ff1a3bf5608fa09f78", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/3e7edc41b58d65509baeb0d4a14c8fa41d627130", + "reference": "3e7edc41b58d65509baeb0d4a14c8fa41d627130", "shasum": "" }, "require": { @@ -3451,7 +3448,7 @@ "type": "tidelift" } ], - "time": "2023-12-08T23:47:49+00:00" + "time": "2024-01-19T00:21:53+00:00" }, { "name": "nette/schema", @@ -3517,16 +3514,16 @@ }, { "name": "nette/utils", - "version": "v4.0.3", + "version": "v4.0.4", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "a9d127dd6a203ce6d255b2e2db49759f7506e015" + "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/a9d127dd6a203ce6d255b2e2db49759f7506e015", - "reference": "a9d127dd6a203ce6d255b2e2db49759f7506e015", + "url": "https://api.github.com/repos/nette/utils/zipball/d3ad0aa3b9f934602cb3e3902ebccf10be34d218", + "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218", "shasum": "" }, "require": { @@ -3597,31 +3594,33 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.3" + "source": "https://github.com/nette/utils/tree/v4.0.4" }, - "time": "2023-10-29T21:02:13+00:00" + "time": "2024-01-17T16:50:36+00:00" }, { "name": "nikic/php-parser", - "version": "v4.18.0", + "version": "v5.0.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" + "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/4a21235f7e56e713259a6f76bf4b5ea08502b9dc", + "reference": "4a21235f7e56e713259a6f76bf4b5ea08502b9dc", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/php-parse" @@ -3629,7 +3628,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -3653,9 +3652,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.0" }, - "time": "2023-12-10T21:03:43+00:00" + "time": "2024-01-07T17:17:35+00:00" }, { "name": "nunomaduro/termwind", @@ -3918,23 +3917,23 @@ }, { "name": "phenx/php-font-lib", - "version": "0.5.4", + "version": "0.5.5", "source": { "type": "git", "url": "https://github.com/dompdf/php-font-lib.git", - "reference": "dd448ad1ce34c63d09baccd05415e361300c35b4" + "reference": "671df0f3516252011aa94f9e8e3b3b66199339f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/dd448ad1ce34c63d09baccd05415e361300c35b4", - "reference": "dd448ad1ce34c63d09baccd05415e361300c35b4", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/671df0f3516252011aa94f9e8e3b3b66199339f8", + "reference": "671df0f3516252011aa94f9e8e3b3b66199339f8", "shasum": "" }, "require": { "ext-mbstring": "*" }, "require-dev": { - "symfony/phpunit-bridge": "^3 || ^4 || ^5" + "symfony/phpunit-bridge": "^3 || ^4 || ^5 || ^6" }, "type": "library", "autoload": { @@ -3944,7 +3943,7 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "LGPL-3.0" + "LGPL-2.1-or-later" ], "authors": [ { @@ -3956,9 +3955,9 @@ "homepage": "https://github.com/PhenX/php-font-lib", "support": { "issues": "https://github.com/dompdf/php-font-lib/issues", - "source": "https://github.com/dompdf/php-font-lib/tree/0.5.4" + "source": "https://github.com/dompdf/php-font-lib/tree/0.5.5" }, - "time": "2021-12-17T19:44:54+00:00" + "time": "2024-01-07T18:13:29+00:00" }, { "name": "phenx/php-svg-lib", @@ -4083,16 +4082,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.34", + "version": "3.0.35", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "56c79f16a6ae17e42089c06a2144467acc35348a" + "reference": "4b1827beabce71953ca479485c0ae9c51287f2fe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/56c79f16a6ae17e42089c06a2144467acc35348a", - "reference": "56c79f16a6ae17e42089c06a2144467acc35348a", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/4b1827beabce71953ca479485c0ae9c51287f2fe", + "reference": "4b1827beabce71953ca479485c0ae9c51287f2fe", "shasum": "" }, "require": { @@ -4173,7 +4172,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.34" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.35" }, "funding": [ { @@ -4189,7 +4188,7 @@ "type": "tidelift" } ], - "time": "2023-11-27T11:13:31+00:00" + "time": "2023-12-29T01:59:53+00:00" }, { "name": "pragmarx/google2fa", @@ -4767,25 +4766,25 @@ }, { "name": "psy/psysh", - "version": "v0.11.22", + "version": "v0.12.0", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "128fa1b608be651999ed9789c95e6e2a31b5802b" + "reference": "750bf031a48fd07c673dbe3f11f72362ea306d0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/128fa1b608be651999ed9789c95e6e2a31b5802b", - "reference": "128fa1b608be651999ed9789c95e6e2a31b5802b", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/750bf031a48fd07c673dbe3f11f72362ea306d0d", + "reference": "750bf031a48fd07c673dbe3f11f72362ea306d0d", "shasum": "" }, "require": { "ext-json": "*", "ext-tokenizer": "*", - "nikic/php-parser": "^4.0 || ^3.1", - "php": "^8.0 || ^7.0.8", - "symfony/console": "^6.0 || ^5.0 || ^4.0 || ^3.4", - "symfony/var-dumper": "^6.0 || ^5.0 || ^4.0 || ^3.4" + "nikic/php-parser": "^5.0 || ^4.0", + "php": "^8.0 || ^7.4", + "symfony/console": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" }, "conflict": { "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" @@ -4796,8 +4795,7 @@ "suggest": { "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", "ext-pdo-sqlite": "The doc command requires SQLite to work.", - "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", - "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history." + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." }, "bin": [ "bin/psysh" @@ -4805,7 +4803,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-0.11": "0.11.x-dev" + "dev-main": "0.12.x-dev" }, "bamarni-bin": { "bin-links": false, @@ -4841,9 +4839,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.22" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.0" }, - "time": "2023-10-14T21:56:36+00:00" + "time": "2023-12-20T15:28:09+00:00" }, { "name": "ralouphie/getallheaders", @@ -8112,16 +8110,16 @@ }, { "name": "fakerphp/faker", - "version": "v1.23.0", + "version": "v1.23.1", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "e3daa170d00fde61ea7719ef47bb09bb8f1d9b01" + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e3daa170d00fde61ea7719ef47bb09bb8f1d9b01", - "reference": "e3daa170d00fde61ea7719ef47bb09bb8f1d9b01", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b", + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b", "shasum": "" }, "require": { @@ -8147,11 +8145,6 @@ "ext-mbstring": "Required for multibyte Unicode string functionality." }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "v1.21-dev" - } - }, "autoload": { "psr-4": { "Faker\\": "src/Faker/" @@ -8174,9 +8167,9 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.23.0" + "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" }, - "time": "2023-06-12T08:44:38+00:00" + "time": "2024-01-02T13:46:09+00:00" }, { "name": "filp/whoops", @@ -8370,36 +8363,36 @@ }, { "name": "larastan/larastan", - "version": "v2.7.0", + "version": "v2.8.1", "source": { "type": "git", "url": "https://github.com/larastan/larastan.git", - "reference": "a2610d46b9999cf558d9900ccb641962d1442f55" + "reference": "b7cc6a29c457a7d4f3de90466392ae9ad3e17022" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/larastan/larastan/zipball/a2610d46b9999cf558d9900ccb641962d1442f55", - "reference": "a2610d46b9999cf558d9900ccb641962d1442f55", + "url": "https://api.github.com/repos/larastan/larastan/zipball/b7cc6a29c457a7d4f3de90466392ae9ad3e17022", + "reference": "b7cc6a29c457a7d4f3de90466392ae9ad3e17022", "shasum": "" }, "require": { "ext-json": "*", - "illuminate/console": "^9.52.16 || ^10.28.0", - "illuminate/container": "^9.52.16 || ^10.28.0", - "illuminate/contracts": "^9.52.16 || ^10.28.0", - "illuminate/database": "^9.52.16 || ^10.28.0", - "illuminate/http": "^9.52.16 || ^10.28.0", - "illuminate/pipeline": "^9.52.16 || ^10.28.0", - "illuminate/support": "^9.52.16 || ^10.28.0", + "illuminate/console": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/container": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/contracts": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/database": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/http": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/pipeline": "^9.52.16 || ^10.28.0 || ^11.0", + "illuminate/support": "^9.52.16 || ^10.28.0 || ^11.0", "php": "^8.0.2", "phpmyadmin/sql-parser": "^5.8.2", - "phpstan/phpstan": "^1.10.41" + "phpstan/phpstan": "^1.10.50" }, "require-dev": { "nikic/php-parser": "^4.17.1", - "orchestra/canvas": "^7.11.1 || ^8.11.0", - "orchestra/testbench": "^7.33.0 || ^8.13.0", - "phpunit/phpunit": "^9.6.13" + "orchestra/canvas": "^7.11.1 || ^8.11.0 || ^9.0.0", + "orchestra/testbench": "^7.33.0 || ^8.13.0 || ^9.0.0", + "phpunit/phpunit": "^9.6.13 || ^10.5" }, "suggest": { "orchestra/testbench": "Using Larastan for analysing a package needs Testbench" @@ -8447,7 +8440,7 @@ ], "support": { "issues": "https://github.com/larastan/larastan/issues", - "source": "https://github.com/larastan/larastan/tree/v2.7.0" + "source": "https://github.com/larastan/larastan/tree/v2.8.1" }, "funding": [ { @@ -8467,7 +8460,7 @@ "type": "patreon" } ], - "time": "2023-12-04T19:21:38+00:00" + "time": "2024-01-08T09:11:17+00:00" }, { "name": "mockery/mockery", @@ -8812,16 +8805,16 @@ }, { "name": "phpmyadmin/sql-parser", - "version": "5.8.2", + "version": "5.9.0", "source": { "type": "git", "url": "https://github.com/phpmyadmin/sql-parser.git", - "reference": "f1720ae19abe6294cb5599594a8a57bc3c8cc287" + "reference": "011fa18a4e55591fac6545a821921dd1d61c6984" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/f1720ae19abe6294cb5599594a8a57bc3c8cc287", - "reference": "f1720ae19abe6294cb5599594a8a57bc3c8cc287", + "url": "https://api.github.com/repos/phpmyadmin/sql-parser/zipball/011fa18a4e55591fac6545a821921dd1d61c6984", + "reference": "011fa18a4e55591fac6545a821921dd1d61c6984", "shasum": "" }, "require": { @@ -8852,6 +8845,7 @@ "bin": [ "bin/highlight-query", "bin/lint-query", + "bin/sql-parser", "bin/tokenize-query" ], "type": "library", @@ -8895,20 +8889,20 @@ "type": "other" } ], - "time": "2023-09-19T12:34:29+00:00" + "time": "2024-01-20T20:34:02+00:00" }, { "name": "phpstan/phpstan", - "version": "1.10.50", + "version": "1.10.56", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4" + "reference": "27816a01aea996191ee14d010f325434c0ee76fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/06a98513ac72c03e8366b5a0cb00750b487032e4", - "reference": "06a98513ac72c03e8366b5a0cb00750b487032e4", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/27816a01aea996191ee14d010f325434c0ee76fa", + "reference": "27816a01aea996191ee14d010f325434c0ee76fa", "shasum": "" }, "require": { @@ -8957,7 +8951,7 @@ "type": "tidelift" } ], - "time": "2023-12-13T10:59:42+00:00" + "time": "2024-01-15T10:43:00+00:00" }, { "name": "phpunit/php-code-coverage", @@ -9280,16 +9274,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.15", + "version": "9.6.16", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1" + "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/05017b80304e0eb3f31d90194a563fd53a6021f1", - "reference": "05017b80304e0eb3f31d90194a563fd53a6021f1", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3767b2c56ce02d01e3491046f33466a1ae60a37f", + "reference": "3767b2c56ce02d01e3491046f33466a1ae60a37f", "shasum": "" }, "require": { @@ -9363,7 +9357,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.15" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.16" }, "funding": [ { @@ -9379,7 +9373,7 @@ "type": "tidelift" } ], - "time": "2023-12-01T16:55:19+00:00" + "time": "2024-01-19T07:03:14+00:00" }, { "name": "sebastian/cli-parser", @@ -10347,16 +10341,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.8.0", + "version": "3.8.1", "source": { "type": "git", "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", - "reference": "5805f7a4e4958dbb5e944ef1e6edae0a303765e7" + "reference": "14f5fff1e64118595db5408e946f3a22c75807f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/5805f7a4e4958dbb5e944ef1e6edae0a303765e7", - "reference": "5805f7a4e4958dbb5e944ef1e6edae0a303765e7", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/14f5fff1e64118595db5408e946f3a22c75807f7", + "reference": "14f5fff1e64118595db5408e946f3a22c75807f7", "shasum": "" }, "require": { @@ -10366,11 +10360,11 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" }, "bin": [ - "bin/phpcs", - "bin/phpcbf" + "bin/phpcbf", + "bin/phpcs" ], "type": "library", "extra": { @@ -10423,7 +10417,7 @@ "type": "open_collective" } ], - "time": "2023-12-08T12:32:31+00:00" + "time": "2024-01-11T20:47:48+00:00" }, { "name": "ssddanbrown/asserthtml", From 3e9e196cdada5a6c515d5bbab971c80a90d333ab Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 25 Jan 2024 14:24:46 +0000 Subject: [PATCH 041/124] OIDC: Added PKCE functionality Related to #4734. Uses core logic from League AbstractProvider. --- app/Access/Oidc/OidcOAuthProvider.php | 31 +++++++++++---------------- app/Access/Oidc/OidcService.php | 12 ++++++++++- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/app/Access/Oidc/OidcOAuthProvider.php b/app/Access/Oidc/OidcOAuthProvider.php index d2dc829b7..371bfcecb 100644 --- a/app/Access/Oidc/OidcOAuthProvider.php +++ b/app/Access/Oidc/OidcOAuthProvider.php @@ -83,15 +83,9 @@ class OidcOAuthProvider extends AbstractProvider /** * Checks a provider response for errors. - * - * @param ResponseInterface $response - * @param array|string $data Parsed response data - * * @throws IdentityProviderException - * - * @return void */ - protected function checkResponse(ResponseInterface $response, $data) + protected function checkResponse(ResponseInterface $response, $data): void { if ($response->getStatusCode() >= 400 || isset($data['error'])) { throw new IdentityProviderException( @@ -105,13 +99,8 @@ class OidcOAuthProvider extends AbstractProvider /** * Generates a resource owner object from a successful resource owner * details request. - * - * @param array $response - * @param AccessToken $token - * - * @return ResourceOwnerInterface */ - protected function createResourceOwner(array $response, AccessToken $token) + protected function createResourceOwner(array $response, AccessToken $token): ResourceOwnerInterface { return new GenericResourceOwner($response, ''); } @@ -121,14 +110,18 @@ class OidcOAuthProvider extends AbstractProvider * * The grant that was used to fetch the response can be used to provide * additional context. - * - * @param array $response - * @param AbstractGrant $grant - * - * @return OidcAccessToken */ - protected function createAccessToken(array $response, AbstractGrant $grant) + protected function createAccessToken(array $response, AbstractGrant $grant): OidcAccessToken { return new OidcAccessToken($response); } + + /** + * Get the method used for PKCE code verifier hashing, which is passed + * in the "code_challenge_method" parameter in the authorization request. + */ + protected function getPkceMethod(): string + { + return static::PKCE_METHOD_S256; + } } diff --git a/app/Access/Oidc/OidcService.php b/app/Access/Oidc/OidcService.php index f1e5b25af..036c9fc47 100644 --- a/app/Access/Oidc/OidcService.php +++ b/app/Access/Oidc/OidcService.php @@ -33,6 +33,8 @@ class OidcService /** * Initiate an authorization flow. + * Provides back an authorize redirect URL, in addition to other + * details which may be required for the auth flow. * * @throws OidcException * @@ -42,8 +44,12 @@ class OidcService { $settings = $this->getProviderSettings(); $provider = $this->getProvider($settings); + + $url = $provider->getAuthorizationUrl(); + session()->put('oidc_pkce_code', $provider->getPkceCode() ?? ''); + return [ - 'url' => $provider->getAuthorizationUrl(), + 'url' => $url, 'state' => $provider->getState(), ]; } @@ -63,6 +69,10 @@ class OidcService $settings = $this->getProviderSettings(); $provider = $this->getProvider($settings); + // Set PKCE code flashed at login + $pkceCode = session()->pull('oidc_pkce_code', ''); + $provider->setPkceCode($pkceCode); + // Try to exchange authorization code for access token $accessToken = $provider->getAccessToken('authorization_code', [ 'code' => $authorizationCode, From 1dc094ffafc216e15c8355b400b21524137de5e4 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sat, 27 Jan 2024 16:41:15 +0000 Subject: [PATCH 042/124] OIDC: Added testing of PKCE flow Also compared full flow to RFC spec during this process --- tests/Auth/OidcTest.php | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/tests/Auth/OidcTest.php b/tests/Auth/OidcTest.php index dbf26f1bd..345d1dc78 100644 --- a/tests/Auth/OidcTest.php +++ b/tests/Auth/OidcTest.php @@ -655,6 +655,34 @@ class OidcTest extends TestCase ]); } + public function test_pkce_used_on_authorize_and_access() + { + // Start auth + $resp = $this->post('/oidc/login'); + $state = session()->get('oidc_state'); + + $pkceCode = session()->get('oidc_pkce_code'); + $this->assertGreaterThan(30, strlen($pkceCode)); + + $expectedCodeChallenge = trim(strtr(base64_encode(hash('sha256', $pkceCode, true)), '+/', '-_'), '='); + $redirect = $resp->headers->get('Location'); + $redirectParams = []; + parse_str(parse_url($redirect, PHP_URL_QUERY), $redirectParams); + $this->assertEquals($expectedCodeChallenge, $redirectParams['code_challenge']); + $this->assertEquals('S256', $redirectParams['code_challenge_method']); + + $transactions = $this->mockHttpClient([$this->getMockAuthorizationResponse([ + 'email' => 'benny@example.com', + 'sub' => 'benny1010101', + ])]); + + $this->get('/oidc/callback?code=SplxlOBeZQQYbYS6WxSbIA&state=' . $state); + $tokenRequest = $transactions->latestRequest(); + $bodyParams = []; + parse_str($tokenRequest->getBody(), $bodyParams); + $this->assertEquals($pkceCode, $bodyParams['code_verifier']); + } + protected function withAutodiscovery() { config()->set([ From 2a849894bee6ac7846261938c9606fb18a4a1761 Mon Sep 17 00:00:00 2001 From: Sascha Date: Mon, 29 Jan 2024 19:37:59 +0100 Subject: [PATCH 043/124] Update entities.php changed text of `pages_delete_warning_template` to include chapters --- lang/en/entities.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lang/en/entities.php b/lang/en/entities.php index 4ab9de47d..21ef93fed 100644 --- a/lang/en/entities.php +++ b/lang/en/entities.php @@ -210,7 +210,7 @@ return [ 'pages_delete_draft' => 'Delete Draft Page', 'pages_delete_success' => 'Page deleted', 'pages_delete_draft_success' => 'Draft page deleted', - 'pages_delete_warning_template' => 'This page is in active use as a book default page template. These books will no longer have a default page template assigned after this page is deleted.', + 'pages_delete_warning_template' => 'This page is in active use as a book or chapter default page template. These books or chapters will no longer have a default page template assigned after this page is deleted.', 'pages_delete_confirm' => 'Are you sure you want to delete this page?', 'pages_delete_draft_confirm' => 'Are you sure you want to delete this draft page?', 'pages_editing_named' => 'Editing Page :pageName', From 64c783c6f8efb351dd73a1cfb4b5f214a731bd57 Mon Sep 17 00:00:00 2001 From: Sascha Date: Mon, 29 Jan 2024 19:55:39 +0100 Subject: [PATCH 044/124] extraded template form to own file and changed translations --- lang/en/entities.php | 9 +++------ resources/views/books/parts/form.blade.php | 18 ++---------------- resources/views/chapters/parts/form.blade.php | 18 ++---------------- .../views/entities/template-selector.blade.php | 14 ++++++++++++++ 4 files changed, 21 insertions(+), 38 deletions(-) create mode 100644 resources/views/entities/template-selector.blade.php diff --git a/lang/en/entities.php b/lang/en/entities.php index 21ef93fed..8860e243e 100644 --- a/lang/en/entities.php +++ b/lang/en/entities.php @@ -39,6 +39,9 @@ return [ 'export_pdf' => 'PDF File', 'export_text' => 'Plain Text File', 'export_md' => 'Markdown File', + 'default_template' => 'Default Page Template', + 'default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book/chapter. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'default_template_select' => 'Select a template page', // Permissions and restrictions 'permissions' => 'Permissions', @@ -132,9 +135,6 @@ return [ 'books_edit_named' => 'Edit Book :bookName', 'books_form_book_name' => 'Book Name', 'books_save' => 'Save Book', - 'books_default_template' => 'Default Page Template', - 'books_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book. Keep in mind this will only be used if the page creator has view access to those chosen template page.', - 'books_default_template_select' => 'Select a template page', 'books_permissions' => 'Book Permissions', 'books_permissions_updated' => 'Book Permissions Updated', 'books_empty_contents' => 'No pages or chapters have been created for this book.', @@ -192,9 +192,6 @@ return [ 'chapters_permissions_success' => 'Chapter Permissions Updated', 'chapters_search_this' => 'Search this chapter', 'chapter_sort_book' => 'Sort Book', - 'chapter_default_template' => 'Default Page Template', - 'chapter_default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this chapter. Keep in mind this will only be used if the page creator has view access to those chosen template page.', - 'chapter_default_template_select' => 'Select a template page', // Pages 'page' => 'Page', diff --git a/resources/views/books/parts/form.blade.php b/resources/views/books/parts/form.blade.php index fa8f16e52..ee261e72d 100644 --- a/resources/views/books/parts/form.blade.php +++ b/resources/views/books/parts/form.blade.php @@ -40,24 +40,10 @@
-
-

- {{ trans('entities.books_default_template_explain') }} -

- -
- @include('form.page-picker', [ - 'name' => 'default_template_id', - 'placeholder' => trans('entities.books_default_template_select'), - 'value' => $book->default_template_id ?? null, - 'selectorEndpoint' => '/search/entity-selector-templates', - ]) -
-
- + @include('entities.template-selector', ['entity' => $book ?? null])
diff --git a/resources/views/chapters/parts/form.blade.php b/resources/views/chapters/parts/form.blade.php index ea7f84bc8..602693916 100644 --- a/resources/views/chapters/parts/form.blade.php +++ b/resources/views/chapters/parts/form.blade.php @@ -24,24 +24,10 @@
-
-

- {{ trans('entities.chapter_default_template_explain') }} -

- -
- @include('form.page-picker', [ - 'name' => 'default_template_id', - 'placeholder' => trans('entities.chapter_default_template_select'), - 'value' => $chapter->default_template_id ?? null, - 'selectorEndpoint' => '/search/entity-selector-templates', - ]) -
-
- + @include('entities.template-selector', ['entity' => $chapter ?? null])
diff --git a/resources/views/entities/template-selector.blade.php b/resources/views/entities/template-selector.blade.php new file mode 100644 index 000000000..80b2f49b2 --- /dev/null +++ b/resources/views/entities/template-selector.blade.php @@ -0,0 +1,14 @@ +
+

+ {{ trans('entities.default_template_explain') }} +

+ +
+ @include('form.page-picker', [ + 'name' => 'default_template_id', + 'placeholder' => trans('entities.default_template_select'), + 'value' => $entity->default_template_id ?? null, + 'selectorEndpoint' => '/search/entity-selector-templates', + ]) +
+
\ No newline at end of file From 4a8f70240fd01f28980dfd4031c7df2b1db4949a Mon Sep 17 00:00:00 2001 From: Sascha Date: Mon, 29 Jan 2024 19:59:03 +0100 Subject: [PATCH 045/124] added template to chapter API controller --- .../Controllers/ChapterApiController.php | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/app/Entities/Controllers/ChapterApiController.php b/app/Entities/Controllers/ChapterApiController.php index c21323262..27b820659 100644 --- a/app/Entities/Controllers/ChapterApiController.php +++ b/app/Entities/Controllers/ChapterApiController.php @@ -15,20 +15,22 @@ class ChapterApiController extends ApiController { protected $rules = [ 'create' => [ - 'book_id' => ['required', 'integer'], - 'name' => ['required', 'string', 'max:255'], - 'description' => ['string', 'max:1900'], - 'description_html' => ['string', 'max:2000'], - 'tags' => ['array'], - 'priority' => ['integer'], + 'book_id' => ['required', 'integer'], + 'name' => ['required', 'string', 'max:255'], + 'description' => ['string', 'max:1900'], + 'description_html' => ['string', 'max:2000'], + 'tags' => ['array'], + 'priority' => ['integer'], + 'default_template_id' => ['nullable', 'integer'], ], 'update' => [ - 'book_id' => ['integer'], - 'name' => ['string', 'min:1', 'max:255'], - 'description' => ['string', 'max:1900'], - 'description_html' => ['string', 'max:2000'], - 'tags' => ['array'], - 'priority' => ['integer'], + 'book_id' => ['integer'], + 'name' => ['string', 'min:1', 'max:255'], + 'description' => ['string', 'max:1900'], + 'description_html' => ['string', 'max:2000'], + 'tags' => ['array'], + 'priority' => ['integer'], + 'default_template_id' => ['nullable', 'integer'], ], ]; From 24e6dc4b37f857ffe6f8eab85ca177ed290bb38a Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 30 Jan 2024 11:38:47 +0000 Subject: [PATCH 046/124] WYSIWYG: Altered how custom head added to editors Updated to parse and add as DOM nodes instead of innerHTML to avoid triggering an update of all head content, which would throw warnings in chromium in regard to setting the base URI. For #4814 --- resources/js/wysiwyg/config.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index 6c96e47e9..36d78b325 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -143,16 +143,23 @@ function gatherPlugins(options) { } /** - * Fetch custom HTML head content from the parent page head into the editor. + * Fetch custom HTML head content nodes from the outer page head + * and add them to the given editor document. + * @param {Document} editorDoc */ -function fetchCustomHeadContent() { +function addCustomHeadContent(editorDoc) { const headContentLines = document.head.innerHTML.split('\n'); const startLineIndex = headContentLines.findIndex(line => line.trim() === ''); const endLineIndex = headContentLines.findIndex(line => line.trim() === ''); if (startLineIndex === -1 || endLineIndex === -1) { - return ''; + return; } - return headContentLines.slice(startLineIndex + 1, endLineIndex).join('\n'); + + const customHeadHtml = headContentLines.slice(startLineIndex + 1, endLineIndex).join('\n'); + const el = editorDoc.createElement('div'); + el.innerHTML = customHeadHtml; + + editorDoc.head.append(...el.children); } /** @@ -284,8 +291,7 @@ export function buildForEditor(options) { } }, init_instance_callback(editor) { - const head = editor.getDoc().querySelector('head'); - head.innerHTML += fetchCustomHeadContent(); + addCustomHeadContent(editor.getDoc()); }, setup(editor) { registerCustomIcons(editor); @@ -335,8 +341,7 @@ export function buildForInput(options) { file_picker_types: 'file', file_picker_callback: filePickerCallback, init_instance_callback(editor) { - const head = editor.getDoc().querySelector('head'); - head.innerHTML += fetchCustomHeadContent(); + addCustomHeadContent(editor.getDoc()); editor.contentDocument.documentElement.classList.toggle('dark-mode', options.darkMode); }, From 5c92b72fdd419ccb6f77bfdf0a1cb1358c51a9d8 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 30 Jan 2024 14:27:09 +0000 Subject: [PATCH 047/124] Comments: Added input wysiwyg for creating/updating comments Not supporting old content, existing HTML or updating yet. --- app/Activity/Tools/CommentTree.php | 11 ++++++++ resources/js/components/page-comment.js | 27 ++++++++++++++++--- resources/js/components/page-comments.js | 30 ++++++++++++++++++--- resources/js/components/wysiwyg-input.js | 7 ++--- resources/sass/_tinymce.scss | 7 +++++ resources/views/comments/comment.blade.php | 2 ++ resources/views/comments/comments.blade.php | 10 ++++++- resources/views/layouts/base.blade.php | 3 +++ 8 files changed, 85 insertions(+), 12 deletions(-) diff --git a/app/Activity/Tools/CommentTree.php b/app/Activity/Tools/CommentTree.php index 3303add39..16f6804ea 100644 --- a/app/Activity/Tools/CommentTree.php +++ b/app/Activity/Tools/CommentTree.php @@ -41,6 +41,17 @@ class CommentTree return $this->tree; } + public function canUpdateAny(): bool + { + foreach ($this->comments as $comment) { + if (userCan('comment-update', $comment)) { + return true; + } + } + + return false; + } + /** * @param Comment[] $comments */ diff --git a/resources/js/components/page-comment.js b/resources/js/components/page-comment.js index 8284d7f20..dc6ca8264 100644 --- a/resources/js/components/page-comment.js +++ b/resources/js/components/page-comment.js @@ -1,5 +1,6 @@ import {Component} from './component'; import {getLoading, htmlToDom} from '../services/dom'; +import {buildForInput} from "../wysiwyg/config"; export class PageComment extends Component { @@ -11,7 +12,12 @@ export class PageComment extends Component { this.deletedText = this.$opts.deletedText; this.updatedText = this.$opts.updatedText; - // Element References + // Editor reference and text options + this.wysiwygEditor = null; + this.wysiwygLanguage = this.$opts.wysiwygLanguage; + this.wysiwygTextDirection = this.$opts.wysiwygTextDirection; + + // Element references this.container = this.$el; this.contentContainer = this.$refs.contentContainer; this.form = this.$refs.form; @@ -50,8 +56,23 @@ export class PageComment extends Component { startEdit() { this.toggleEditMode(true); - const lineCount = this.$refs.input.value.split('\n').length; - this.$refs.input.style.height = `${(lineCount * 20) + 40}px`; + + if (this.wysiwygEditor) { + return; + } + + const config = buildForInput({ + language: this.wysiwygLanguage, + containerElement: this.input, + darkMode: document.documentElement.classList.contains('dark-mode'), + textDirection: this.wysiwygTextDirection, + translations: {}, + translationMap: window.editor_translations, + }); + + window.tinymce.init(config).then(editors => { + this.wysiwygEditor = editors[0]; + }); } async update(event) { diff --git a/resources/js/components/page-comments.js b/resources/js/components/page-comments.js index e2911afc6..ebcc95f07 100644 --- a/resources/js/components/page-comments.js +++ b/resources/js/components/page-comments.js @@ -1,5 +1,6 @@ import {Component} from './component'; import {getLoading, htmlToDom} from '../services/dom'; +import {buildForInput} from "../wysiwyg/config"; export class PageComments extends Component { @@ -21,6 +22,11 @@ export class PageComments extends Component { this.hideFormButton = this.$refs.hideFormButton; this.removeReplyToButton = this.$refs.removeReplyToButton; + // WYSIWYG options + this.wysiwygLanguage = this.$opts.wysiwygLanguage; + this.wysiwygTextDirection = this.$opts.wysiwygTextDirection; + this.wysiwygEditor = null; + // Translations this.createdText = this.$opts.createdText; this.countText = this.$opts.countText; @@ -96,9 +102,7 @@ export class PageComments extends Component { this.formContainer.toggleAttribute('hidden', false); this.addButtonContainer.toggleAttribute('hidden', true); this.formContainer.scrollIntoView({behavior: 'smooth', block: 'nearest'}); - setTimeout(() => { - this.formInput.focus(); - }, 100); + this.loadEditor(); } hideForm() { @@ -112,6 +116,26 @@ export class PageComments extends Component { this.addButtonContainer.toggleAttribute('hidden', false); } + loadEditor() { + if (this.wysiwygEditor) { + return; + } + + const config = buildForInput({ + language: this.wysiwygLanguage, + containerElement: this.formInput, + darkMode: document.documentElement.classList.contains('dark-mode'), + textDirection: this.wysiwygTextDirection, + translations: {}, + translationMap: window.editor_translations, + }); + + window.tinymce.init(config).then(editors => { + this.wysiwygEditor = editors[0]; + this.wysiwygEditor.focus(); + }); + } + getCommentCount() { return this.container.querySelectorAll('[component="page-comment"]').length; } diff --git a/resources/js/components/wysiwyg-input.js b/resources/js/components/wysiwyg-input.js index 88c06a334..ad964aed2 100644 --- a/resources/js/components/wysiwyg-input.js +++ b/resources/js/components/wysiwyg-input.js @@ -10,11 +10,8 @@ export class WysiwygInput extends Component { language: this.$opts.language, containerElement: this.elem, darkMode: document.documentElement.classList.contains('dark-mode'), - textDirection: this.textDirection, - translations: { - imageUploadErrorText: this.$opts.imageUploadErrorText, - serverUploadLimitText: this.$opts.serverUploadLimitText, - }, + textDirection: this.$opts.textDirection, + translations: {}, translationMap: window.editor_translations, }); diff --git a/resources/sass/_tinymce.scss b/resources/sass/_tinymce.scss index c4336da7c..fb5ea7e6f 100644 --- a/resources/sass/_tinymce.scss +++ b/resources/sass/_tinymce.scss @@ -30,6 +30,13 @@ display: block; } +.wysiwyg-input.mce-content-body:before { + padding: 1rem; + top: 4px; + font-style: italic; + color: rgba(34,47,62,.5) +} + // Default styles for our custom root nodes .page-content.mce-content-body doc-root { display: block; diff --git a/resources/views/comments/comment.blade.php b/resources/views/comments/comment.blade.php index 1cb709160..4340cfdf5 100644 --- a/resources/views/comments/comment.blade.php +++ b/resources/views/comments/comment.blade.php @@ -4,6 +4,8 @@ option:page-comment:comment-parent-id="{{ $comment->parent_id }}" option:page-comment:updated-text="{{ trans('entities.comment_updated_success') }}" option:page-comment:deleted-text="{{ trans('entities.comment_deleted_success') }}" + option:page-comment:wysiwyg-language="{{ $locale->htmlLang() }}" + option:page-comment:wysiwyg-text-direction="{{ $locale->htmlDirection() }}" id="comment{{$comment->local_id}}" class="comment-box">
diff --git a/resources/views/comments/comments.blade.php b/resources/views/comments/comments.blade.php index 26d286290..2c314864b 100644 --- a/resources/views/comments/comments.blade.php +++ b/resources/views/comments/comments.blade.php @@ -2,6 +2,8 @@ option:page-comments:page-id="{{ $page->id }}" option:page-comments:created-text="{{ trans('entities.comment_created_success') }}" option:page-comments:count-text="{{ trans('entities.comment_count') }}" + option:page-comments:wysiwyg-language="{{ $locale->htmlLang() }}" + option:page-comments:wysiwyg-text-direction="{{ $locale->htmlDirection() }}" class="comments-list" aria-label="{{ trans('entities.comments') }}"> @@ -24,7 +26,6 @@ @if(userCan('comment-create-all')) @include('comments.create') - @if (!$commentTree->empty())
@yield('bottom') + + @if($cspNonce ?? false) @endif @yield('scripts') + @stack('post-app-scripts') @include('layouts.parts.base-body-end') From adf0baebb9ffc61cc944c0572ec6dbb12a5b41a0 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 30 Jan 2024 15:16:58 +0000 Subject: [PATCH 048/124] Comments: Added back-end HTML support, fixed editor focus Also fixed handling of editors when moved in DOM, to properly remove then re-init before & after move to avoid issues. --- app/Activity/CommentRepo.php | 26 ++++--------------- .../Controllers/CommentController.php | 17 +++++++----- resources/js/components/page-comment.js | 6 +++-- resources/js/components/page-comments.js | 17 +++++++++--- resources/views/comments/comment.blade.php | 2 +- resources/views/comments/create.blade.php | 2 +- 6 files changed, 34 insertions(+), 36 deletions(-) diff --git a/app/Activity/CommentRepo.php b/app/Activity/CommentRepo.php index ce2950e4d..3336e17e9 100644 --- a/app/Activity/CommentRepo.php +++ b/app/Activity/CommentRepo.php @@ -5,7 +5,7 @@ namespace BookStack\Activity; use BookStack\Activity\Models\Comment; use BookStack\Entities\Models\Entity; use BookStack\Facades\Activity as ActivityService; -use League\CommonMark\CommonMarkConverter; +use BookStack\Util\HtmlDescriptionFilter; class CommentRepo { @@ -20,13 +20,12 @@ class CommentRepo /** * Create a new comment on an entity. */ - public function create(Entity $entity, string $text, ?int $parent_id): Comment + public function create(Entity $entity, string $html, ?int $parent_id): Comment { $userId = user()->id; $comment = new Comment(); - $comment->text = $text; - $comment->html = $this->commentToHtml($text); + $comment->html = HtmlDescriptionFilter::filterFromString($html); $comment->created_by = $userId; $comment->updated_by = $userId; $comment->local_id = $this->getNextLocalId($entity); @@ -42,11 +41,10 @@ class CommentRepo /** * Update an existing comment. */ - public function update(Comment $comment, string $text): Comment + public function update(Comment $comment, string $html): Comment { $comment->updated_by = user()->id; - $comment->text = $text; - $comment->html = $this->commentToHtml($text); + $comment->html = HtmlDescriptionFilter::filterFromString($html); $comment->save(); ActivityService::add(ActivityType::COMMENT_UPDATE, $comment); @@ -64,20 +62,6 @@ class CommentRepo ActivityService::add(ActivityType::COMMENT_DELETE, $comment); } - /** - * Convert the given comment Markdown to HTML. - */ - public function commentToHtml(string $commentText): string - { - $converter = new CommonMarkConverter([ - 'html_input' => 'strip', - 'max_nesting_level' => 10, - 'allow_unsafe_links' => false, - ]); - - return $converter->convert($commentText); - } - /** * Get the next local ID relative to the linked entity. */ diff --git a/app/Activity/Controllers/CommentController.php b/app/Activity/Controllers/CommentController.php index 516bcac75..340524cd0 100644 --- a/app/Activity/Controllers/CommentController.php +++ b/app/Activity/Controllers/CommentController.php @@ -22,8 +22,8 @@ class CommentController extends Controller */ public function savePageComment(Request $request, int $pageId) { - $this->validate($request, [ - 'text' => ['required', 'string'], + $input = $this->validate($request, [ + 'html' => ['required', 'string'], 'parent_id' => ['nullable', 'integer'], ]); @@ -39,7 +39,7 @@ class CommentController extends Controller // Create a new comment. $this->checkPermission('comment-create-all'); - $comment = $this->commentRepo->create($page, $request->get('text'), $request->get('parent_id')); + $comment = $this->commentRepo->create($page, $input['html'], $input['parent_id'] ?? null); return view('comments.comment-branch', [ 'readOnly' => false, @@ -57,17 +57,20 @@ class CommentController extends Controller */ public function update(Request $request, int $commentId) { - $this->validate($request, [ - 'text' => ['required', 'string'], + $input = $this->validate($request, [ + 'html' => ['required', 'string'], ]); $comment = $this->commentRepo->getById($commentId); $this->checkOwnablePermission('page-view', $comment->entity); $this->checkOwnablePermission('comment-update', $comment); - $comment = $this->commentRepo->update($comment, $request->get('text')); + $comment = $this->commentRepo->update($comment, $input['html']); - return view('comments.comment', ['comment' => $comment, 'readOnly' => false]); + return view('comments.comment', [ + 'comment' => $comment, + 'readOnly' => false, + ]); } /** diff --git a/resources/js/components/page-comment.js b/resources/js/components/page-comment.js index dc6ca8264..79c9d3c2c 100644 --- a/resources/js/components/page-comment.js +++ b/resources/js/components/page-comment.js @@ -1,6 +1,6 @@ import {Component} from './component'; import {getLoading, htmlToDom} from '../services/dom'; -import {buildForInput} from "../wysiwyg/config"; +import {buildForInput} from '../wysiwyg/config'; export class PageComment extends Component { @@ -58,6 +58,7 @@ export class PageComment extends Component { this.toggleEditMode(true); if (this.wysiwygEditor) { + this.wysiwygEditor.focus(); return; } @@ -72,6 +73,7 @@ export class PageComment extends Component { window.tinymce.init(config).then(editors => { this.wysiwygEditor = editors[0]; + setTimeout(() => this.wysiwygEditor.focus(), 50); }); } @@ -81,7 +83,7 @@ export class PageComment extends Component { this.form.toggleAttribute('hidden', true); const reqData = { - text: this.input.value, + html: this.wysiwygEditor.getContent(), parent_id: this.parentId || null, }; diff --git a/resources/js/components/page-comments.js b/resources/js/components/page-comments.js index ebcc95f07..cfb0634a9 100644 --- a/resources/js/components/page-comments.js +++ b/resources/js/components/page-comments.js @@ -1,6 +1,6 @@ import {Component} from './component'; import {getLoading, htmlToDom} from '../services/dom'; -import {buildForInput} from "../wysiwyg/config"; +import {buildForInput} from '../wysiwyg/config'; export class PageComments extends Component { @@ -65,9 +65,8 @@ export class PageComments extends Component { this.form.after(loading); this.form.toggleAttribute('hidden', true); - const text = this.formInput.value; const reqData = { - text, + html: this.wysiwygEditor.getContent(), parent_id: this.parentId || null, }; @@ -92,6 +91,7 @@ export class PageComments extends Component { } resetForm() { + this.removeEditor(); this.formInput.value = ''; this.parentId = null; this.replyToRow.toggleAttribute('hidden', true); @@ -99,6 +99,7 @@ export class PageComments extends Component { } showForm() { + this.removeEditor(); this.formContainer.toggleAttribute('hidden', false); this.addButtonContainer.toggleAttribute('hidden', true); this.formContainer.scrollIntoView({behavior: 'smooth', block: 'nearest'}); @@ -118,6 +119,7 @@ export class PageComments extends Component { loadEditor() { if (this.wysiwygEditor) { + this.wysiwygEditor.focus(); return; } @@ -132,10 +134,17 @@ export class PageComments extends Component { window.tinymce.init(config).then(editors => { this.wysiwygEditor = editors[0]; - this.wysiwygEditor.focus(); + setTimeout(() => this.wysiwygEditor.focus(), 50); }); } + removeEditor() { + if (this.wysiwygEditor) { + this.wysiwygEditor.remove(); + this.wysiwygEditor = null; + } + } + getCommentCount() { return this.container.querySelectorAll('[component="page-comment"]').length; } diff --git a/resources/views/comments/comment.blade.php b/resources/views/comments/comment.blade.php index 4340cfdf5..e00307f0f 100644 --- a/resources/views/comments/comment.blade.php +++ b/resources/views/comments/comment.blade.php @@ -77,7 +77,7 @@ @if(!$readOnly && userCan('comment-update', $comment))
- +
diff --git a/resources/views/comments/create.blade.php b/resources/views/comments/create.blade.php index cb7905ddc..417f0c606 100644 --- a/resources/views/comments/create.blade.php +++ b/resources/views/comments/create.blade.php @@ -16,7 +16,7 @@
-
From e9a19d587857ba5afcaa411718af61b62aaff1ac Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 31 Jan 2024 14:22:04 +0000 Subject: [PATCH 049/124] Comments: Added wysiwyg link selector, updated tests, removed command - Updated existing tests with recent back-end changes, mainly to use HTML data. - Removed old comment regen command that's no longer required. --- .../RegenerateCommentContentCommand.php | 49 ----------- .../Activity/Models/CommentFactory.php | 1 + resources/views/comments/comments.blade.php | 3 + resources/views/layouts/base.blade.php | 2 +- tests/Activity/WatchTest.php | 10 +-- .../RegenerateCommentContentCommandTest.php | 31 ------- tests/Entity/CommentTest.php | 82 +++++++++++-------- 7 files changed, 58 insertions(+), 120 deletions(-) delete mode 100644 app/Console/Commands/RegenerateCommentContentCommand.php delete mode 100644 tests/Commands/RegenerateCommentContentCommandTest.php diff --git a/app/Console/Commands/RegenerateCommentContentCommand.php b/app/Console/Commands/RegenerateCommentContentCommand.php deleted file mode 100644 index f7ec42620..000000000 --- a/app/Console/Commands/RegenerateCommentContentCommand.php +++ /dev/null @@ -1,49 +0,0 @@ -option('database') !== null) { - DB::setDefaultConnection($this->option('database')); - } - - Comment::query()->chunk(100, function ($comments) use ($commentRepo) { - foreach ($comments as $comment) { - $comment->html = $commentRepo->commentToHtml($comment->text); - $comment->save(); - } - }); - - DB::setDefaultConnection($connection); - $this->comment('Comment HTML content has been regenerated'); - - return 0; - } -} diff --git a/database/factories/Activity/Models/CommentFactory.php b/database/factories/Activity/Models/CommentFactory.php index 4b76cd11d..35d6db9bd 100644 --- a/database/factories/Activity/Models/CommentFactory.php +++ b/database/factories/Activity/Models/CommentFactory.php @@ -27,6 +27,7 @@ class CommentFactory extends Factory 'html' => $html, 'text' => $text, 'parent_id' => null, + 'local_id' => 1, ]; } } diff --git a/resources/views/comments/comments.blade.php b/resources/views/comments/comments.blade.php index 2c314864b..37329c42c 100644 --- a/resources/views/comments/comments.blade.php +++ b/resources/views/comments/comments.blade.php @@ -40,6 +40,9 @@ @include('form.editor-translations') @endpush + @push('post-app-html') + @include('entities.selector-popup') + @endpush @endif \ No newline at end of file diff --git a/resources/views/layouts/base.blade.php b/resources/views/layouts/base.blade.php index 43cca6b14..4d4d07dc2 100644 --- a/resources/views/layouts/base.blade.php +++ b/resources/views/layouts/base.blade.php @@ -68,7 +68,7 @@
@yield('bottom') - + @stack('post-app-html') @if($cspNonce ?? false) diff --git a/tests/Activity/WatchTest.php b/tests/Activity/WatchTest.php index 38935bbf5..605b60fd4 100644 --- a/tests/Activity/WatchTest.php +++ b/tests/Activity/WatchTest.php @@ -196,7 +196,7 @@ class WatchTest extends TestCase $notifications = Notification::fake(); $this->asAdmin()->post("/comment/{$entities['page']->id}", [ - 'text' => 'My new comment' + 'html' => '

My new comment

' ]); $notifications->assertSentTo($editor, CommentCreationNotification::class); } @@ -217,12 +217,12 @@ class WatchTest extends TestCase $notifications = Notification::fake(); $this->actingAs($editor)->post("/comment/{$entities['page']->id}", [ - 'text' => 'My new comment' + 'html' => '

My new comment

' ]); $comment = $entities['page']->comments()->orderBy('id', 'desc')->first(); $this->asAdmin()->post("/comment/{$entities['page']->id}", [ - 'text' => 'My new comment response', + 'html' => '

My new comment response

', 'parent_id' => $comment->local_id, ]); $notifications->assertSentTo($editor, CommentCreationNotification::class); @@ -257,7 +257,7 @@ class WatchTest extends TestCase // Comment post $this->actingAs($admin)->post("/comment/{$entities['page']->id}", [ - 'text' => 'My new comment response', + 'html' => '

My new comment response

', ]); $notifications->assertSentTo($editor, function (CommentCreationNotification $notification) use ($editor, $admin, $entities) { @@ -376,7 +376,7 @@ class WatchTest extends TestCase $this->permissions->disableEntityInheritedPermissions($page); $this->asAdmin()->post("/comment/{$page->id}", [ - 'text' => 'My new comment response', + 'html' => '

My new comment response

', ])->assertOk(); $notifications->assertNothingSentTo($editor); diff --git a/tests/Commands/RegenerateCommentContentCommandTest.php b/tests/Commands/RegenerateCommentContentCommandTest.php deleted file mode 100644 index 4940d66c3..000000000 --- a/tests/Commands/RegenerateCommentContentCommandTest.php +++ /dev/null @@ -1,31 +0,0 @@ -forceCreate([ - 'html' => 'some_old_content', - 'text' => 'some_fresh_content', - ]); - - $this->assertDatabaseHas('comments', [ - 'html' => 'some_old_content', - ]); - - $exitCode = \Artisan::call('bookstack:regenerate-comment-content'); - $this->assertTrue($exitCode === 0, 'Command executed successfully'); - - $this->assertDatabaseMissing('comments', [ - 'html' => 'some_old_content', - ]); - $this->assertDatabaseHas('comments', [ - 'html' => "

some_fresh_content

\n", - ]); - } -} diff --git a/tests/Entity/CommentTest.php b/tests/Entity/CommentTest.php index 23fc68197..c080359bc 100644 --- a/tests/Entity/CommentTest.php +++ b/tests/Entity/CommentTest.php @@ -27,7 +27,7 @@ class CommentTest extends TestCase 'local_id' => 1, 'entity_id' => $page->id, 'entity_type' => Page::newModelInstance()->getMorphClass(), - 'text' => $comment->text, + 'text' => null, 'parent_id' => 2, ]); @@ -43,17 +43,17 @@ class CommentTest extends TestCase $this->postJson("/comment/$page->id", $comment->getAttributes()); $comment = $page->comments()->first(); - $newText = 'updated text content'; + $newHtml = '

updated text content

'; $resp = $this->putJson("/comment/$comment->id", [ - 'text' => $newText, + 'html' => $newHtml, ]); $resp->assertStatus(200); - $resp->assertSee($newText); - $resp->assertDontSee($comment->text); + $resp->assertSee($newHtml, false); + $resp->assertDontSee($comment->html, false); $this->assertDatabaseHas('comments', [ - 'text' => $newText, + 'html' => $newHtml, 'entity_id' => $page->id, ]); @@ -80,46 +80,28 @@ class CommentTest extends TestCase $this->assertActivityExists(ActivityType::COMMENT_DELETE); } - public function test_comments_converts_markdown_input_to_html() - { - $page = $this->entities->page(); - $this->asAdmin()->postJson("/comment/$page->id", [ - 'text' => '# My Title', - ]); - - $this->assertDatabaseHas('comments', [ - 'entity_id' => $page->id, - 'entity_type' => $page->getMorphClass(), - 'text' => '# My Title', - 'html' => "

My Title

\n", - ]); - - $pageView = $this->get($page->getUrl()); - $pageView->assertSee('

My Title

', false); - } - - public function test_html_cannot_be_injected_via_comment_content() + public function test_scripts_cannot_be_injected_via_comment_html() { $this->asAdmin(); $page = $this->entities->page(); - $script = '\n\n# sometextinthecomment'; + $script = '

My lovely comment

'; $this->postJson("/comment/$page->id", [ - 'text' => $script, + 'html' => $script, ]); $pageView = $this->get($page->getUrl()); $pageView->assertDontSee($script, false); - $pageView->assertSee('sometextinthecomment'); + $pageView->assertSee('

My lovely comment

', false); $comment = $page->comments()->first(); $this->putJson("/comment/$comment->id", [ - 'text' => $script . 'updated', + 'html' => $script . '

updated

', ]); $pageView = $this->get($page->getUrl()); $pageView->assertDontSee($script, false); - $pageView->assertSee('sometextinthecommentupdated'); + $pageView->assertSee('

My lovely comment

updated

'); } public function test_reply_comments_are_nested() @@ -127,15 +109,17 @@ class CommentTest extends TestCase $this->asAdmin(); $page = $this->entities->page(); - $this->postJson("/comment/$page->id", ['text' => 'My new comment']); - $this->postJson("/comment/$page->id", ['text' => 'My new comment']); + $this->postJson("/comment/$page->id", ['html' => '

My new comment

']); + $this->postJson("/comment/$page->id", ['html' => '

My new comment

']); $respHtml = $this->withHtml($this->get($page->getUrl())); $respHtml->assertElementCount('.comment-branch', 3); $respHtml->assertElementNotExists('.comment-branch .comment-branch'); $comment = $page->comments()->first(); - $resp = $this->postJson("/comment/$page->id", ['text' => 'My nested comment', 'parent_id' => $comment->local_id]); + $resp = $this->postJson("/comment/$page->id", [ + 'html' => '

My nested comment

', 'parent_id' => $comment->local_id + ]); $resp->assertStatus(200); $respHtml = $this->withHtml($this->get($page->getUrl())); @@ -147,7 +131,7 @@ class CommentTest extends TestCase { $page = $this->entities->page(); - $this->asAdmin()->postJson("/comment/$page->id", ['text' => 'My great comment to see in the editor']); + $this->asAdmin()->postJson("/comment/$page->id", ['html' => '

My great comment to see in the editor

']); $respHtml = $this->withHtml($this->get($page->getUrl('/edit'))); $respHtml->assertElementContains('.comment-box .content', 'My great comment to see in the editor'); @@ -164,4 +148,34 @@ class CommentTest extends TestCase $pageResp = $this->asAdmin()->get($page->getUrl()); $pageResp->assertSee('Wolfeschlegels…'); } + + public function test_comment_editor_js_loaded_with_create_or_edit_permissions() + { + $editor = $this->users->editor(); + $page = $this->entities->page(); + + $resp = $this->actingAs($editor)->get($page->getUrl()); + $resp->assertSee('tinymce.min.js?', false); + $resp->assertSee('window.editor_translations', false); + $resp->assertSee('component="entity-selector"', false); + + $this->permissions->removeUserRolePermissions($editor, ['comment-create-all']); + $this->permissions->grantUserRolePermissions($editor, ['comment-update-own']); + + $resp = $this->actingAs($editor)->get($page->getUrl()); + $resp->assertDontSee('tinymce.min.js?', false); + $resp->assertDontSee('window.editor_translations', false); + $resp->assertDontSee('component="entity-selector"', false); + + Comment::factory()->create([ + 'created_by' => $editor->id, + 'entity_type' => 'page', + 'entity_id' => $page->id, + ]); + + $resp = $this->actingAs($editor)->get($page->getUrl()); + $resp->assertSee('tinymce.min.js?', false); + $resp->assertSee('window.editor_translations', false); + $resp->assertSee('component="entity-selector"', false); + } } From 06901b878f2c8057a6f9b7d2e0adfda425c68dee Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 31 Jan 2024 16:20:22 +0000 Subject: [PATCH 050/124] Comments: Added HTML filter on load, tinymce elem filtering - Added filter on load to help prevent potentially dangerous comment HTML in DB at load time (if it gets passed input filtering, or is existing). - Added TinyMCE valid_elements for input wysiwygs, to gracefully degrade content at point of user-view, rather than surprising the user by stripping content, which TinyMCE would show, post-save. --- app/Activity/Models/Comment.php | 6 ++++++ resources/js/wysiwyg/config.js | 1 + resources/views/comments/comment.blade.php | 7 +++++-- tests/Entity/CommentTest.php | 17 +++++++++++++++-- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/app/Activity/Models/Comment.php b/app/Activity/Models/Comment.php index 6efa3df6f..038788afb 100644 --- a/app/Activity/Models/Comment.php +++ b/app/Activity/Models/Comment.php @@ -4,6 +4,7 @@ namespace BookStack\Activity\Models; use BookStack\App\Model; use BookStack\Users\Models\HasCreatorAndUpdater; +use BookStack\Util\HtmlContentFilter; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\MorphTo; @@ -73,4 +74,9 @@ class Comment extends Model implements Loggable { return "Comment #{$this->local_id} (ID: {$this->id}) for {$this->entity_type} (ID: {$this->entity_id})"; } + + public function safeHtml(): string + { + return HtmlContentFilter::removeScriptsFromHtmlString($this->html ?? ''); + } } diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index 36d78b325..fa2df9c11 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -339,6 +339,7 @@ export function buildForInput(options) { toolbar: 'bold italic link bullist numlist', content_style: getContentStyle(options), file_picker_types: 'file', + valid_elements: 'p,a[href|title],ol,ul,li,strong,em,br', file_picker_callback: filePickerCallback, init_instance_callback(editor) { addCustomHeadContent(editor.getDoc()); diff --git a/resources/views/comments/comment.blade.php b/resources/views/comments/comment.blade.php index e00307f0f..b507a810b 100644 --- a/resources/views/comments/comment.blade.php +++ b/resources/views/comments/comment.blade.php @@ -1,3 +1,6 @@ +@php + $commentHtml = $comment->safeHtml(); +@endphp
@icon('reply'){{ trans('entities.comment_in_reply_to', ['commentId' => '#' . $comment->parent_id]) }}

@endif - {!! $comment->html !!} + {!! $commentHtml !!}
@if(!$readOnly && userCan('comment-update', $comment))
- +
diff --git a/tests/Entity/CommentTest.php b/tests/Entity/CommentTest.php index c080359bc..76e014e80 100644 --- a/tests/Entity/CommentTest.php +++ b/tests/Entity/CommentTest.php @@ -82,11 +82,10 @@ class CommentTest extends TestCase public function test_scripts_cannot_be_injected_via_comment_html() { - $this->asAdmin(); $page = $this->entities->page(); $script = '

My lovely comment

'; - $this->postJson("/comment/$page->id", [ + $this->asAdmin()->postJson("/comment/$page->id", [ 'html' => $script, ]); @@ -104,6 +103,20 @@ class CommentTest extends TestCase $pageView->assertSee('

My lovely comment

updated

'); } + public function test_scripts_are_removed_even_if_already_in_db() + { + $page = $this->entities->page(); + Comment::factory()->create([ + 'html' => '

scriptincommentest

', + 'entity_type' => 'page', 'entity_id' => $page + ]); + + $resp = $this->asAdmin()->get($page->getUrl()); + $resp->assertSee('scriptincommentest', false); + $resp->assertDontSee('superbadscript', false); + $resp->assertDontSee('superbadonclick', false); + } + public function test_reply_comments_are_nested() { $this->asAdmin(); From fee9045dacd5deca3ae88b17a22b3fff60c01195 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 31 Jan 2024 16:35:58 +0000 Subject: [PATCH 051/124] Comments: Removed remaining uses of redundant 'text' field Opened #4821 to remove the DB field in a few releases time. --- app/Activity/Models/Comment.php | 4 ++-- database/factories/Activity/Models/CommentFactory.php | 1 - tests/Entity/CommentTest.php | 4 ++-- tests/Permissions/RolePermissionsTest.php | 8 ++------ 4 files changed, 6 insertions(+), 11 deletions(-) diff --git a/app/Activity/Models/Comment.php b/app/Activity/Models/Comment.php index 038788afb..7d1c54646 100644 --- a/app/Activity/Models/Comment.php +++ b/app/Activity/Models/Comment.php @@ -11,7 +11,7 @@ use Illuminate\Database\Eloquent\Relations\MorphTo; /** * @property int $id - * @property string $text + * @property string $text - Deprecated & now unused (#4821) * @property string $html * @property int|null $parent_id - Relates to local_id, not id * @property int $local_id @@ -25,7 +25,7 @@ class Comment extends Model implements Loggable use HasFactory; use HasCreatorAndUpdater; - protected $fillable = ['text', 'parent_id']; + protected $fillable = ['parent_id']; protected $appends = ['created', 'updated']; /** diff --git a/database/factories/Activity/Models/CommentFactory.php b/database/factories/Activity/Models/CommentFactory.php index 35d6db9bd..efbd183b3 100644 --- a/database/factories/Activity/Models/CommentFactory.php +++ b/database/factories/Activity/Models/CommentFactory.php @@ -25,7 +25,6 @@ class CommentFactory extends Factory return [ 'html' => $html, - 'text' => $text, 'parent_id' => null, 'local_id' => 1, ]; diff --git a/tests/Entity/CommentTest.php b/tests/Entity/CommentTest.php index 76e014e80..eb4bccb7c 100644 --- a/tests/Entity/CommentTest.php +++ b/tests/Entity/CommentTest.php @@ -18,10 +18,10 @@ class CommentTest extends TestCase $resp = $this->postJson("/comment/$page->id", $comment->getAttributes()); $resp->assertStatus(200); - $resp->assertSee($comment->text); + $resp->assertSee($comment->html, false); $pageResp = $this->get($page->getUrl()); - $pageResp->assertSee($comment->text); + $pageResp->assertSee($comment->html, false); $this->assertDatabaseHas('comments', [ 'local_id' => 1, diff --git a/tests/Permissions/RolePermissionsTest.php b/tests/Permissions/RolePermissionsTest.php index ccb158faf..d3146bd47 100644 --- a/tests/Permissions/RolePermissionsTest.php +++ b/tests/Permissions/RolePermissionsTest.php @@ -738,16 +738,12 @@ class RolePermissionsTest extends TestCase private function addComment(Page $page): TestResponse { - $comment = Comment::factory()->make(); - - return $this->postJson("/comment/$page->id", $comment->only('text', 'html')); + return $this->postJson("/comment/$page->id", ['html' => '

New comment content

']); } private function updateComment(Comment $comment): TestResponse { - $commentData = Comment::factory()->make(); - - return $this->putJson("/comment/{$comment->id}", $commentData->only('text', 'html')); + return $this->putJson("/comment/{$comment->id}", ['html' => '

Updated comment content

']); } private function deleteComment(Comment $comment): TestResponse From 47f082c085ab8440b294f701b57289f101ddd1f4 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 31 Jan 2024 16:47:58 +0000 Subject: [PATCH 052/124] Comments: Added HTML filter test, fixed placeholder in dark mode --- resources/sass/_tinymce.scss | 2 +- tests/Entity/CommentTest.php | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/resources/sass/_tinymce.scss b/resources/sass/_tinymce.scss index fb5ea7e6f..b6a2156df 100644 --- a/resources/sass/_tinymce.scss +++ b/resources/sass/_tinymce.scss @@ -34,7 +34,7 @@ padding: 1rem; top: 4px; font-style: italic; - color: rgba(34,47,62,.5) + @include lightDark(color, rgba(34,47,62,.5), rgba(155,155,155,.5)) } // Default styles for our custom root nodes diff --git a/tests/Entity/CommentTest.php b/tests/Entity/CommentTest.php index eb4bccb7c..73136235c 100644 --- a/tests/Entity/CommentTest.php +++ b/tests/Entity/CommentTest.php @@ -117,6 +117,29 @@ class CommentTest extends TestCase $resp->assertDontSee('superbadonclick', false); } + public function test_comment_html_is_limited() + { + $page = $this->entities->page(); + $input = '

Test

Contenta

Hello

'; + $expected = '

Contenta

'; + + $resp = $this->asAdmin()->post("/comment/{$page->id}", ['html' => $input]); + $resp->assertOk(); + $this->assertDatabaseHas('comments', [ + 'entity_type' => 'page', + 'entity_id' => $page->id, + 'html' => $expected, + ]); + + $comment = $page->comments()->first(); + $resp = $this->put("/comment/{$comment->id}", ['html' => $input]); + $resp->assertOk(); + $this->assertDatabaseHas('comments', [ + 'id' => $comment->id, + 'html' => $expected, + ]); + } + public function test_reply_comments_are_nested() { $this->asAdmin(); From 4137cf9c8fc1f9088f8792b64fcc889f787e8958 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 1 Feb 2024 12:22:16 +0000 Subject: [PATCH 053/124] Default chapter templates: Updated api docs and tests Also applied minor tweaks to some wording and logic. During review of #4750 --- app/Api/ListingResponseBuilder.php | 2 + app/Entities/Repos/PageRepo.php | 8 +-- ...04542_add_default_template_to_chapters.php | 64 +++++++++---------- dev/api/requests/chapters-create.json | 1 + dev/api/requests/chapters-update.json | 1 + dev/api/responses/chapters-create.json | 1 + dev/api/responses/chapters-read.json | 1 + dev/api/responses/chapters-update.json | 1 + lang/en/entities.php | 2 +- tests/Api/ChaptersApiTest.php | 5 ++ 10 files changed, 46 insertions(+), 40 deletions(-) diff --git a/app/Api/ListingResponseBuilder.php b/app/Api/ListingResponseBuilder.php index 44117bad9..329f5ce1c 100644 --- a/app/Api/ListingResponseBuilder.php +++ b/app/Api/ListingResponseBuilder.php @@ -61,6 +61,8 @@ class ListingResponseBuilder } }); + dd($data->first()); + return response()->json([ 'data' => $data, 'total' => $total, diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index d9bda0198..85237a752 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -136,13 +136,7 @@ class PageRepo $page->book_id = $parent->id; } - // check for chapter - if ($page->chapter_id) { - $defaultTemplate = $page->chapter->defaultTemplate; - } else { - $defaultTemplate = $page->book->defaultTemplate; - } - + $defaultTemplate = $page->chapter->defaultTemplate ?? $page->book->defaultTemplate; if ($defaultTemplate && userCan('view', $defaultTemplate)) { $page->forceFill([ 'html' => $defaultTemplate->html, diff --git a/database/migrations/2024_01_01_104542_add_default_template_to_chapters.php b/database/migrations/2024_01_01_104542_add_default_template_to_chapters.php index 5e9ea1de3..b3a103a01 100644 --- a/database/migrations/2024_01_01_104542_add_default_template_to_chapters.php +++ b/database/migrations/2024_01_01_104542_add_default_template_to_chapters.php @@ -1,32 +1,32 @@ -integer('default_template_id')->nullable()->default(null); - }); - } - - /** - * Reverse the migrations. - * - * @return void - */ - public function down() - { - Schema::table('chapters', function (Blueprint $table) { - $table->dropColumn('default_template_id'); - }); - } -} +integer('default_template_id')->nullable()->default(null); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('chapters', function (Blueprint $table) { + $table->dropColumn('default_template_id'); + }); + } +} diff --git a/dev/api/requests/chapters-create.json b/dev/api/requests/chapters-create.json index e9d903387..02aee9eea 100644 --- a/dev/api/requests/chapters-create.json +++ b/dev/api/requests/chapters-create.json @@ -3,6 +3,7 @@ "name": "My fantastic new chapter", "description_html": "

This is a great new chapter that I've created via the API

", "priority": 15, + "default_template_id": 25, "tags": [ {"name": "Category", "value": "Top Content"}, {"name": "Rating", "value": "Highest"} diff --git a/dev/api/requests/chapters-update.json b/dev/api/requests/chapters-update.json index be675772b..cf9c89eac 100644 --- a/dev/api/requests/chapters-update.json +++ b/dev/api/requests/chapters-update.json @@ -3,6 +3,7 @@ "name": "My fantastic updated chapter", "description_html": "

This is an updated chapter that I've altered via the API

", "priority": 16, + "default_template_id": 2428, "tags": [ {"name": "Category", "value": "Kinda Good Content"}, {"name": "Rating", "value": "Medium"} diff --git a/dev/api/responses/chapters-create.json b/dev/api/responses/chapters-create.json index 183186b0b..1e81a1e63 100644 --- a/dev/api/responses/chapters-create.json +++ b/dev/api/responses/chapters-create.json @@ -11,6 +11,7 @@ "updated_by": 1, "owned_by": 1, "description_html": "

This is a great new chapter<\/strong> that I've created via the API<\/p>", + "default_template_id": 25, "tags": [ { "name": "Category", diff --git a/dev/api/responses/chapters-read.json b/dev/api/responses/chapters-read.json index 192ffce7c..01a2f4b9f 100644 --- a/dev/api/responses/chapters-read.json +++ b/dev/api/responses/chapters-read.json @@ -5,6 +5,7 @@ "name": "Content Creation", "description": "How to create documentation on whatever subject you need to write about.", "description_html": "

How to create documentation on whatever subject you need to write about.

", + "default_template_id": 25, "priority": 3, "created_at": "2019-05-05T21:49:56.000000Z", "updated_at": "2019-09-28T11:24:23.000000Z", diff --git a/dev/api/responses/chapters-update.json b/dev/api/responses/chapters-update.json index 5ac3c64c1..4d8d0024e 100644 --- a/dev/api/responses/chapters-update.json +++ b/dev/api/responses/chapters-update.json @@ -11,6 +11,7 @@ "updated_by": 1, "owned_by": 1, "description_html": "

This is an updated chapter<\/strong> that I've altered via the API<\/p>", + "default_template_id": 2428, "tags": [ { "name": "Category", diff --git a/lang/en/entities.php b/lang/en/entities.php index 8860e243e..9e620b24e 100644 --- a/lang/en/entities.php +++ b/lang/en/entities.php @@ -40,7 +40,7 @@ return [ 'export_text' => 'Plain Text File', 'export_md' => 'Markdown File', 'default_template' => 'Default Page Template', - 'default_template_explain' => 'Assign a page template that will be used as the default content for all new pages in this book/chapter. Keep in mind this will only be used if the page creator has view access to those chosen template page.', + 'default_template_explain' => 'Assign a page template that will be used as the default content for all pages created within this item. Keep in mind this will only be used if the page creator has view access to the chosen template page.', 'default_template_select' => 'Select a template page', // Permissions and restrictions diff --git a/tests/Api/ChaptersApiTest.php b/tests/Api/ChaptersApiTest.php index 81a918877..fdb80a497 100644 --- a/tests/Api/ChaptersApiTest.php +++ b/tests/Api/ChaptersApiTest.php @@ -35,6 +35,7 @@ class ChaptersApiTest extends TestCase { $this->actingAsApiEditor(); $book = $this->entities->book(); + $templatePage = $this->entities->templatePage(); $details = [ 'name' => 'My API chapter', 'description' => 'A chapter created via the API', @@ -46,6 +47,7 @@ class ChaptersApiTest extends TestCase ], ], 'priority' => 15, + 'default_template_id' => $templatePage->id, ]; $resp = $this->postJson($this->baseEndpoint, $details); @@ -147,6 +149,7 @@ class ChaptersApiTest extends TestCase 'name' => $page->name, ], ], + 'default_template_id' => null, ]); $resp->assertJsonCount($chapter->pages()->count(), 'pages'); } @@ -155,6 +158,7 @@ class ChaptersApiTest extends TestCase { $this->actingAsApiEditor(); $chapter = $this->entities->chapter(); + $templatePage = $this->entities->templatePage(); $details = [ 'name' => 'My updated API chapter', 'description' => 'A chapter updated via the API', @@ -165,6 +169,7 @@ class ChaptersApiTest extends TestCase ], ], 'priority' => 15, + 'default_template_id' => $templatePage->id, ]; $resp = $this->putJson($this->baseEndpoint . "/{$chapter->id}", $details); From 43a72fb9a5dd40885c70c41bc578bffbec9c25ee Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 1 Feb 2024 12:51:47 +0000 Subject: [PATCH 054/124] Default chapter templates: Added tests, extracted repo logic - Updated existing book tests to be generic to all default templates, and updated with chapter testing. - Extracted repeated logic in the Book/Chapter repos to be shared in the BaseRepo. Review of #4750 --- app/Entities/Repos/BaseRepo.php | 30 ++ app/Entities/Repos/BookRepo.php | 31 +-- app/Entities/Repos/ChapterRepo.php | 32 +-- tests/Entity/BookDefaultTemplateTest.php | 185 ------------ tests/Entity/DefaultTemplateTest.php | 341 +++++++++++++++++++++++ 5 files changed, 375 insertions(+), 244 deletions(-) delete mode 100644 tests/Entity/BookDefaultTemplateTest.php create mode 100644 tests/Entity/DefaultTemplateTest.php diff --git a/app/Entities/Repos/BaseRepo.php b/app/Entities/Repos/BaseRepo.php index 27bf00161..17208ae03 100644 --- a/app/Entities/Repos/BaseRepo.php +++ b/app/Entities/Repos/BaseRepo.php @@ -3,9 +3,12 @@ namespace BookStack\Entities\Repos; use BookStack\Activity\TagRepo; +use BookStack\Entities\Models\Book; +use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\HasCoverImage; use BookStack\Entities\Models\HasHtmlDescription; +use BookStack\Entities\Models\Page; use BookStack\Exceptions\ImageUploadException; use BookStack\References\ReferenceStore; use BookStack\References\ReferenceUpdater; @@ -104,6 +107,33 @@ class BaseRepo } } + /** + * Update the default page template used for this item. + * Checks that, if changing, the provided value is a valid template and the user + * has visibility of the provided page template id. + */ + public function updateDefaultTemplate(Book|Chapter $entity, int $templateId): void + { + $changing = $templateId !== intval($entity->default_template_id); + if (!$changing) { + return; + } + + if ($templateId === 0) { + $entity->default_template_id = null; + $entity->save(); + return; + } + + $templateExists = Page::query()->visible() + ->where('template', '=', true) + ->where('id', '=', $templateId) + ->exists(); + + $entity->default_template_id = $templateExists ? $templateId : null; + $entity->save(); + } + protected function updateDescription(Entity $entity, array $input): void { if (!in_array(HasHtmlDescription::class, class_uses($entity))) { diff --git a/app/Entities/Repos/BookRepo.php b/app/Entities/Repos/BookRepo.php index 03e1118b1..bf765b22d 100644 --- a/app/Entities/Repos/BookRepo.php +++ b/app/Entities/Repos/BookRepo.php @@ -86,7 +86,7 @@ class BookRepo $book = new Book(); $this->baseRepo->create($book, $input); $this->baseRepo->updateCoverImage($book, $input['image'] ?? null); - $this->updateBookDefaultTemplate($book, intval($input['default_template_id'] ?? null)); + $this->baseRepo->updateDefaultTemplate($book, intval($input['default_template_id'] ?? null)); Activity::add(ActivityType::BOOK_CREATE, $book); return $book; @@ -100,7 +100,7 @@ class BookRepo $this->baseRepo->update($book, $input); if (array_key_exists('default_template_id', $input)) { - $this->updateBookDefaultTemplate($book, intval($input['default_template_id'])); + $this->baseRepo->updateDefaultTemplate($book, intval($input['default_template_id'])); } if (array_key_exists('image', $input)) { @@ -112,33 +112,6 @@ class BookRepo return $book; } - /** - * Update the default page template used for this book. - * Checks that, if changing, the provided value is a valid template and the user - * has visibility of the provided page template id. - */ - protected function updateBookDefaultTemplate(Book $book, int $templateId): void - { - $changing = $templateId !== intval($book->default_template_id); - if (!$changing) { - return; - } - - if ($templateId === 0) { - $book->default_template_id = null; - $book->save(); - return; - } - - $templateExists = Page::query()->visible() - ->where('template', '=', true) - ->where('id', '=', $templateId) - ->exists(); - - $book->default_template_id = $templateExists ? $templateId : null; - $book->save(); - } - /** * Update the given book's cover image, or clear it. * diff --git a/app/Entities/Repos/ChapterRepo.php b/app/Entities/Repos/ChapterRepo.php index 9534a4060..50b554d68 100644 --- a/app/Entities/Repos/ChapterRepo.php +++ b/app/Entities/Repos/ChapterRepo.php @@ -6,7 +6,6 @@ use BookStack\Activity\ActivityType; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Page; use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Entity; use BookStack\Entities\Tools\BookContents; use BookStack\Entities\Tools\TrashCan; use BookStack\Exceptions\MoveOperationException; @@ -47,7 +46,7 @@ class ChapterRepo $chapter->book_id = $parentBook->id; $chapter->priority = (new BookContents($parentBook))->getLastPriority() + 1; $this->baseRepo->create($chapter, $input); - $this->updateChapterDefaultTemplate($chapter, intval($input['default_template_id'] ?? null)); + $this->baseRepo->updateDefaultTemplate($chapter, intval($input['default_template_id'] ?? null)); Activity::add(ActivityType::CHAPTER_CREATE, $chapter); return $chapter; @@ -61,7 +60,7 @@ class ChapterRepo $this->baseRepo->update($chapter, $input); if (array_key_exists('default_template_id', $input)) { - $this->updateChapterDefaultTemplate($chapter, intval($input['default_template_id'])); + $this->baseRepo->updateDefaultTemplate($chapter, intval($input['default_template_id'])); } Activity::add(ActivityType::CHAPTER_UPDATE, $chapter); @@ -108,33 +107,6 @@ class ChapterRepo return $parent; } - /** - * Update the default page template used for this chapter. - * Checks that, if changing, the provided value is a valid template and the user - * has visibility of the provided page template id. - */ - protected function updateChapterDefaultTemplate(Chapter $chapter, int $templateId): void - { - $changing = $templateId !== intval($chapter->default_template_id); - if (!$changing) { - return; - } - - if ($templateId === 0) { - $chapter->default_template_id = null; - $chapter->save(); - return; - } - - $templateExists = Page::query()->visible() - ->where('template', '=', true) - ->where('id', '=', $templateId) - ->exists(); - - $chapter->default_template_id = $templateExists ? $templateId : null; - $chapter->save(); - } - /** * Find a page parent entity via an identifier string in the format: * {type}:{id} diff --git a/tests/Entity/BookDefaultTemplateTest.php b/tests/Entity/BookDefaultTemplateTest.php deleted file mode 100644 index d4cd5b2c3..000000000 --- a/tests/Entity/BookDefaultTemplateTest.php +++ /dev/null @@ -1,185 +0,0 @@ -entities->templatePage(); - $details = [ - 'name' => 'My book with default template', - 'default_template_id' => $templatePage->id, - ]; - - $this->asEditor()->post('/books', $details); - $this->assertDatabaseHas('books', $details); - } - - public function test_updating_book_with_default_template() - { - $book = $this->entities->book(); - $templatePage = $this->entities->templatePage(); - - $this->asEditor()->put("/books/{$book->slug}", ['name' => $book->name, 'default_template_id' => strval($templatePage->id)]); - $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => $templatePage->id]); - - $this->asEditor()->put("/books/{$book->slug}", ['name' => $book->name, 'default_template_id' => '']); - $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => null]); - } - - public function test_default_template_cannot_be_set_if_not_a_template() - { - $book = $this->entities->book(); - $page = $this->entities->page(); - $this->assertFalse($page->template); - - $this->asEditor()->put("/books/{$book->slug}", ['name' => $book->name, 'default_template_id' => $page->id]); - $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => null]); - } - - public function test_default_template_cannot_be_set_if_not_have_access() - { - $book = $this->entities->book(); - $templatePage = $this->entities->templatePage(); - $this->permissions->disableEntityInheritedPermissions($templatePage); - - $this->asEditor()->put("/books/{$book->slug}", ['name' => $book->name, 'default_template_id' => $templatePage->id]); - $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => null]); - } - - public function test_inaccessible_default_template_can_be_set_if_unchanged() - { - $templatePage = $this->entities->templatePage(); - $book = $this->bookUsingDefaultTemplate($templatePage); - $this->permissions->disableEntityInheritedPermissions($templatePage); - - $this->asEditor()->put("/books/{$book->slug}", ['name' => $book->name, 'default_template_id' => $templatePage->id]); - $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => $templatePage->id]); - } - - public function test_default_page_template_option_shows_on_book_form() - { - $templatePage = $this->entities->templatePage(); - $book = $this->bookUsingDefaultTemplate($templatePage); - - $resp = $this->asEditor()->get($book->getUrl('/edit')); - $this->withHtml($resp)->assertElementExists('input[name="default_template_id"][value="' . $templatePage->id . '"]'); - } - - public function test_default_page_template_option_only_shows_template_name_if_visible() - { - $templatePage = $this->entities->templatePage(); - $book = $this->bookUsingDefaultTemplate($templatePage); - - $resp = $this->asEditor()->get($book->getUrl('/edit')); - $this->withHtml($resp)->assertElementContains('#template-control a.text-page', "#{$templatePage->id}, {$templatePage->name}"); - - $this->permissions->disableEntityInheritedPermissions($templatePage); - - $resp = $this->asEditor()->get($book->getUrl('/edit')); - $this->withHtml($resp)->assertElementNotContains('#template-control a.text-page', "#{$templatePage->id}, {$templatePage->name}"); - $this->withHtml($resp)->assertElementContains('#template-control a.text-page', "#{$templatePage->id}"); - } - - public function test_creating_book_page_uses_default_template() - { - $templatePage = $this->entities->templatePage(); - $templatePage->forceFill(['html' => '

My template page

', 'markdown' => '# My template page'])->save(); - $book = $this->bookUsingDefaultTemplate($templatePage); - - $this->asEditor()->get($book->getUrl('/create-page')); - $latestPage = $book->pages() - ->where('draft', '=', true) - ->where('template', '=', false) - ->latest()->first(); - - $this->assertEquals('

My template page

', $latestPage->html); - $this->assertEquals('# My template page', $latestPage->markdown); - } - - public function test_creating_chapter_page_uses_default_template() - { - $templatePage = $this->entities->templatePage(); - $templatePage->forceFill(['html' => '

My template page in chapter

', 'markdown' => '# My template page in chapter'])->save(); - $book = $this->bookUsingDefaultTemplate($templatePage); - $chapter = $book->chapters()->first(); - - $this->asEditor()->get($chapter->getUrl('/create-page')); - $latestPage = $chapter->pages() - ->where('draft', '=', true) - ->where('template', '=', false) - ->latest()->first(); - - $this->assertEquals('

My template page in chapter

', $latestPage->html); - $this->assertEquals('# My template page in chapter', $latestPage->markdown); - } - - public function test_creating_book_page_as_guest_uses_default_template() - { - $templatePage = $this->entities->templatePage(); - $templatePage->forceFill(['html' => '

My template page

', 'markdown' => '# My template page'])->save(); - $book = $this->bookUsingDefaultTemplate($templatePage); - $guest = $this->users->guest(); - - $this->permissions->makeAppPublic(); - $this->permissions->grantUserRolePermissions($guest, ['page-create-all', 'page-update-all']); - - $resp = $this->post($book->getUrl('/create-guest-page'), [ - 'name' => 'My guest page with template' - ]); - $latestPage = $book->pages() - ->where('draft', '=', false) - ->where('template', '=', false) - ->where('created_by', '=', $guest->id) - ->latest()->first(); - - $this->assertEquals('

My template page

', $latestPage->html); - $this->assertEquals('# My template page', $latestPage->markdown); - } - - public function test_creating_book_page_does_not_use_template_if_not_visible() - { - $templatePage = $this->entities->templatePage(); - $templatePage->forceFill(['html' => '

My template page

', 'markdown' => '# My template page'])->save(); - $book = $this->bookUsingDefaultTemplate($templatePage); - $this->permissions->disableEntityInheritedPermissions($templatePage); - - $this->asEditor()->get($book->getUrl('/create-page')); - $latestPage = $book->pages() - ->where('draft', '=', true) - ->where('template', '=', false) - ->latest()->first(); - - $this->assertEquals('', $latestPage->html); - $this->assertEquals('', $latestPage->markdown); - } - - public function test_template_page_delete_removes_book_template_usage() - { - $templatePage = $this->entities->templatePage(); - $book = $this->bookUsingDefaultTemplate($templatePage); - - $book->refresh(); - $this->assertEquals($templatePage->id, $book->default_template_id); - - $this->asEditor()->delete($templatePage->getUrl()); - $this->asAdmin()->post('/settings/recycle-bin/empty'); - - $book->refresh(); - $this->assertEquals(null, $book->default_template_id); - } - - protected function bookUsingDefaultTemplate(Page $page): Book - { - $book = $this->entities->book(); - $book->default_template_id = $page->id; - $book->save(); - - return $book; - } -} diff --git a/tests/Entity/DefaultTemplateTest.php b/tests/Entity/DefaultTemplateTest.php new file mode 100644 index 000000000..5369a5430 --- /dev/null +++ b/tests/Entity/DefaultTemplateTest.php @@ -0,0 +1,341 @@ +entities->templatePage(); + $details = [ + 'name' => 'My book with default template', + 'default_template_id' => $templatePage->id, + ]; + + $this->asEditor()->post('/books', $details); + $this->assertDatabaseHas('books', $details); + } + + public function test_creating_chapter_with_default_template() + { + $templatePage = $this->entities->templatePage(); + $book = $this->entities->book(); + $details = [ + 'name' => 'My chapter with default template', + 'default_template_id' => $templatePage->id, + ]; + + $this->asEditor()->post($book->getUrl('/create-chapter'), $details); + $this->assertDatabaseHas('chapters', $details); + } + + public function test_updating_book_with_default_template() + { + $book = $this->entities->book(); + $templatePage = $this->entities->templatePage(); + + $this->asEditor()->put($book->getUrl(), ['name' => $book->name, 'default_template_id' => strval($templatePage->id)]); + $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => $templatePage->id]); + + $this->asEditor()->put($book->getUrl(), ['name' => $book->name, 'default_template_id' => '']); + $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => null]); + } + + public function test_updating_chapter_with_default_template() + { + $chapter = $this->entities->chapter(); + $templatePage = $this->entities->templatePage(); + + $this->asEditor()->put($chapter->getUrl(), ['name' => $chapter->name, 'default_template_id' => strval($templatePage->id)]); + $this->assertDatabaseHas('chapters', ['id' => $chapter->id, 'default_template_id' => $templatePage->id]); + + $this->asEditor()->put($chapter->getUrl(), ['name' => $chapter->name, 'default_template_id' => '']); + $this->assertDatabaseHas('chapters', ['id' => $chapter->id, 'default_template_id' => null]); + } + + public function test_default_book_template_cannot_be_set_if_not_a_template() + { + $book = $this->entities->book(); + $page = $this->entities->page(); + $this->assertFalse($page->template); + + $this->asEditor()->put("/books/{$book->slug}", ['name' => $book->name, 'default_template_id' => $page->id]); + $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => null]); + } + + public function test_default_chapter_template_cannot_be_set_if_not_a_template() + { + $chapter = $this->entities->chapter(); + $page = $this->entities->page(); + $this->assertFalse($page->template); + + $this->asEditor()->put("/chapters/{$chapter->slug}", ['name' => $chapter->name, 'default_template_id' => $page->id]); + $this->assertDatabaseHas('chapters', ['id' => $chapter->id, 'default_template_id' => null]); + } + + + public function test_default_book_template_cannot_be_set_if_not_have_access() + { + $book = $this->entities->book(); + $templatePage = $this->entities->templatePage(); + $this->permissions->disableEntityInheritedPermissions($templatePage); + + $this->asEditor()->put("/books/{$book->slug}", ['name' => $book->name, 'default_template_id' => $templatePage->id]); + $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => null]); + } + + public function test_default_chapter_template_cannot_be_set_if_not_have_access() + { + $chapter = $this->entities->chapter(); + $templatePage = $this->entities->templatePage(); + $this->permissions->disableEntityInheritedPermissions($templatePage); + + $this->asEditor()->put("/chapters/{$chapter->slug}", ['name' => $chapter->name, 'default_template_id' => $templatePage->id]); + $this->assertDatabaseHas('chapters', ['id' => $chapter->id, 'default_template_id' => null]); + } + + public function test_inaccessible_book_default_template_can_be_set_if_unchanged() + { + $templatePage = $this->entities->templatePage(); + $book = $this->bookUsingDefaultTemplate($templatePage); + $this->permissions->disableEntityInheritedPermissions($templatePage); + + $this->asEditor()->put("/books/{$book->slug}", ['name' => $book->name, 'default_template_id' => $templatePage->id]); + $this->assertDatabaseHas('books', ['id' => $book->id, 'default_template_id' => $templatePage->id]); + } + + public function test_inaccessible_chapter_default_template_can_be_set_if_unchanged() + { + $templatePage = $this->entities->templatePage(); + $chapter = $this->chapterUsingDefaultTemplate($templatePage); + $this->permissions->disableEntityInheritedPermissions($templatePage); + + $this->asEditor()->put("/chapters/{$chapter->slug}", ['name' => $chapter->name, 'default_template_id' => $templatePage->id]); + $this->assertDatabaseHas('chapters', ['id' => $chapter->id, 'default_template_id' => $templatePage->id]); + } + + public function test_default_page_template_option_shows_on_book_form() + { + $templatePage = $this->entities->templatePage(); + $book = $this->bookUsingDefaultTemplate($templatePage); + + $resp = $this->asEditor()->get($book->getUrl('/edit')); + $this->withHtml($resp)->assertElementExists('input[name="default_template_id"][value="' . $templatePage->id . '"]'); + } + + public function test_default_page_template_option_shows_on_chapter_form() + { + $templatePage = $this->entities->templatePage(); + $chapter = $this->chapterUsingDefaultTemplate($templatePage); + + $resp = $this->asEditor()->get($chapter->getUrl('/edit')); + $this->withHtml($resp)->assertElementExists('input[name="default_template_id"][value="' . $templatePage->id . '"]'); + } + + public function test_book_default_page_template_option_only_shows_template_name_if_visible() + { + $templatePage = $this->entities->templatePage(); + $book = $this->bookUsingDefaultTemplate($templatePage); + + $resp = $this->asEditor()->get($book->getUrl('/edit')); + $this->withHtml($resp)->assertElementContains('#template-control a.text-page', "#{$templatePage->id}, {$templatePage->name}"); + + $this->permissions->disableEntityInheritedPermissions($templatePage); + + $resp = $this->asEditor()->get($book->getUrl('/edit')); + $this->withHtml($resp)->assertElementNotContains('#template-control a.text-page', "#{$templatePage->id}, {$templatePage->name}"); + $this->withHtml($resp)->assertElementContains('#template-control a.text-page', "#{$templatePage->id}"); + } + + public function test_chapter_default_page_template_option_only_shows_template_name_if_visible() + { + $templatePage = $this->entities->templatePage(); + $chapter = $this->chapterUsingDefaultTemplate($templatePage); + + $resp = $this->asEditor()->get($chapter->getUrl('/edit')); + $this->withHtml($resp)->assertElementContains('#template-control a.text-page', "#{$templatePage->id}, {$templatePage->name}"); + + $this->permissions->disableEntityInheritedPermissions($templatePage); + + $resp = $this->asEditor()->get($chapter->getUrl('/edit')); + $this->withHtml($resp)->assertElementNotContains('#template-control a.text-page', "#{$templatePage->id}, {$templatePage->name}"); + $this->withHtml($resp)->assertElementContains('#template-control a.text-page', "#{$templatePage->id}"); + } + + public function test_creating_book_page_uses_book_default_template() + { + $templatePage = $this->entities->templatePage(); + $templatePage->forceFill(['html' => '

My template page

', 'markdown' => '# My template page'])->save(); + $book = $this->bookUsingDefaultTemplate($templatePage); + + $this->asEditor()->get($book->getUrl('/create-page')); + $latestPage = $book->pages() + ->where('draft', '=', true) + ->where('template', '=', false) + ->latest()->first(); + + $this->assertEquals('

My template page

', $latestPage->html); + $this->assertEquals('# My template page', $latestPage->markdown); + } + + public function test_creating_chapter_page_uses_chapter_default_template() + { + $templatePage = $this->entities->templatePage(); + $templatePage->forceFill(['html' => '

My chapter template page

', 'markdown' => '# My chapter template page'])->save(); + $chapter = $this->chapterUsingDefaultTemplate($templatePage); + + $this->asEditor()->get($chapter->getUrl('/create-page')); + $latestPage = $chapter->pages() + ->where('draft', '=', true) + ->where('template', '=', false) + ->latest()->first(); + + $this->assertEquals('

My chapter template page

', $latestPage->html); + $this->assertEquals('# My chapter template page', $latestPage->markdown); + } + + public function test_creating_chapter_page_uses_book_default_template_if_no_chapter_template_set() + { + $templatePage = $this->entities->templatePage(); + $templatePage->forceFill(['html' => '

My template page in chapter

', 'markdown' => '# My template page in chapter'])->save(); + $book = $this->bookUsingDefaultTemplate($templatePage); + $chapter = $book->chapters()->first(); + + $this->asEditor()->get($chapter->getUrl('/create-page')); + $latestPage = $chapter->pages() + ->where('draft', '=', true) + ->where('template', '=', false) + ->latest()->first(); + + $this->assertEquals('

My template page in chapter

', $latestPage->html); + $this->assertEquals('# My template page in chapter', $latestPage->markdown); + } + + public function test_creating_chapter_page_uses_chapter_template_instead_of_book_template() + { + $bookTemplatePage = $this->entities->templatePage(); + $bookTemplatePage->forceFill(['html' => '

My book template

', 'markdown' => '# My book template'])->save(); + $book = $this->bookUsingDefaultTemplate($bookTemplatePage); + + $chapterTemplatePage = $this->entities->templatePage(); + $chapterTemplatePage->forceFill(['html' => '

My chapter template

', 'markdown' => '# My chapter template'])->save(); + $chapter = $book->chapters()->first(); + $chapter->default_template_id = $chapterTemplatePage->id; + $chapter->save(); + + $this->asEditor()->get($chapter->getUrl('/create-page')); + $latestPage = $chapter->pages() + ->where('draft', '=', true) + ->where('template', '=', false) + ->latest()->first(); + + $this->assertEquals('

My chapter template

', $latestPage->html); + $this->assertEquals('# My chapter template', $latestPage->markdown); + } + + public function test_creating_page_as_guest_uses_default_template() + { + $templatePage = $this->entities->templatePage(); + $templatePage->forceFill(['html' => '

My template page

', 'markdown' => '# My template page'])->save(); + $book = $this->bookUsingDefaultTemplate($templatePage); + $chapter = $this->chapterUsingDefaultTemplate($templatePage); + $guest = $this->users->guest(); + + $this->permissions->makeAppPublic(); + $this->permissions->grantUserRolePermissions($guest, ['page-create-all', 'page-update-all']); + + $this->post($book->getUrl('/create-guest-page'), [ + 'name' => 'My guest page with template' + ]); + $latestBookPage = $book->pages() + ->where('draft', '=', false) + ->where('template', '=', false) + ->where('created_by', '=', $guest->id) + ->latest()->first(); + + $this->assertEquals('

My template page

', $latestBookPage->html); + $this->assertEquals('# My template page', $latestBookPage->markdown); + + $this->post($chapter->getUrl('/create-guest-page'), [ + 'name' => 'My guest page with template' + ]); + $latestChapterPage = $chapter->pages() + ->where('draft', '=', false) + ->where('template', '=', false) + ->where('created_by', '=', $guest->id) + ->latest()->first(); + + $this->assertEquals('

My template page

', $latestChapterPage->html); + $this->assertEquals('# My template page', $latestChapterPage->markdown); + } + + public function test_templates_not_used_if_not_visible() + { + $templatePage = $this->entities->templatePage(); + $templatePage->forceFill(['html' => '

My template page

', 'markdown' => '# My template page'])->save(); + $book = $this->bookUsingDefaultTemplate($templatePage); + $chapter = $this->chapterUsingDefaultTemplate($templatePage); + + $this->permissions->disableEntityInheritedPermissions($templatePage); + + $this->asEditor()->get($book->getUrl('/create-page')); + $latestBookPage = $book->pages() + ->where('draft', '=', true) + ->where('template', '=', false) + ->latest()->first(); + + $this->assertEquals('', $latestBookPage->html); + $this->assertEquals('', $latestBookPage->markdown); + + $this->asEditor()->get($chapter->getUrl('/create-page')); + $latestChapterPage = $chapter->pages() + ->where('draft', '=', true) + ->where('template', '=', false) + ->latest()->first(); + + $this->assertEquals('', $latestChapterPage->html); + $this->assertEquals('', $latestChapterPage->markdown); + } + + public function test_template_page_delete_removes_template_usage() + { + $templatePage = $this->entities->templatePage(); + $book = $this->bookUsingDefaultTemplate($templatePage); + $chapter = $this->chapterUsingDefaultTemplate($templatePage); + + $book->refresh(); + $this->assertEquals($templatePage->id, $book->default_template_id); + $this->assertEquals($templatePage->id, $chapter->default_template_id); + + $this->asEditor()->delete($templatePage->getUrl()); + $this->asAdmin()->post('/settings/recycle-bin/empty'); + + $book->refresh(); + $chapter->refresh(); + $this->assertEquals(null, $book->default_template_id); + $this->assertEquals(null, $chapter->default_template_id); + } + + protected function bookUsingDefaultTemplate(Page $page): Book + { + $book = $this->entities->book(); + $book->default_template_id = $page->id; + $book->save(); + + return $book; + } + + protected function chapterUsingDefaultTemplate(Page $page): Chapter + { + $chapter = $this->entities->chapter(); + $chapter->default_template_id = $page->id; + $chapter->save(); + + return $chapter; + } +} From 2460e7c56e4d0d8368cd37e09e68d25f24e705a3 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 1 Feb 2024 12:57:26 +0000 Subject: [PATCH 055/124] Plonker Remediation: Removed dd line left in from debugging --- app/Api/ListingResponseBuilder.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/Api/ListingResponseBuilder.php b/app/Api/ListingResponseBuilder.php index 329f5ce1c..44117bad9 100644 --- a/app/Api/ListingResponseBuilder.php +++ b/app/Api/ListingResponseBuilder.php @@ -61,8 +61,6 @@ class ListingResponseBuilder } }); - dd($data->first()); - return response()->json([ 'data' => $data, 'total' => $total, From a70ed81908a8bcd67e6c449eb7d0cdd6f26ef998 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 4 Feb 2024 14:39:01 +0000 Subject: [PATCH 056/124] DB: Started update of entity loading to avoid global selects Removes page/chpater addSelect global query, to load book slug, and instead extracts base queries to be managed in new static class, while updating specific entitiy relation loading to use our more efficient MixedEntityListLoader where appropriate. Related to #4823 --- app/Activity/ActivityQueries.php | 14 ++++---- app/App/HomeController.php | 10 +++--- app/Entities/Models/BookChild.php | 14 -------- app/Entities/Queries/EntityQuery.php | 6 ++++ app/Entities/Queries/PageQueries.php | 31 ++++++++++++++++++ app/Entities/Queries/RecentlyViewed.php | 12 ++++--- app/Entities/Queries/TopFavourites.php | 10 +++--- app/Entities/Tools/MixedEntityListLoader.php | 25 ++++++++++++--- app/References/ReferenceFetcher.php | 2 +- ...4_02_04_141358_add_views_updated_index.php | 32 +++++++++++++++++++ 10 files changed, 115 insertions(+), 41 deletions(-) create mode 100644 app/Entities/Queries/PageQueries.php create mode 100644 database/migrations/2024_02_04_141358_add_views_updated_index.php diff --git a/app/Activity/ActivityQueries.php b/app/Activity/ActivityQueries.php index c69cf7a36..dae0791b1 100644 --- a/app/Activity/ActivityQueries.php +++ b/app/Activity/ActivityQueries.php @@ -7,6 +7,7 @@ use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; +use BookStack\Entities\Tools\MixedEntityListLoader; use BookStack\Permissions\PermissionApplicator; use BookStack\Users\Models\User; use Illuminate\Database\Eloquent\Builder; @@ -14,11 +15,10 @@ use Illuminate\Database\Eloquent\Relations\Relation; class ActivityQueries { - protected PermissionApplicator $permissions; - - public function __construct(PermissionApplicator $permissions) - { - $this->permissions = $permissions; + public function __construct( + protected PermissionApplicator $permissions, + protected MixedEntityListLoader $listLoader, + ) { } /** @@ -29,11 +29,13 @@ class ActivityQueries $activityList = $this->permissions ->restrictEntityRelationQuery(Activity::query(), 'activities', 'entity_id', 'entity_type') ->orderBy('created_at', 'desc') - ->with(['user', 'entity']) + ->with(['user']) ->skip($count * $page) ->take($count) ->get(); + $this->listLoader->loadIntoRelations($activityList->all(), 'entity', false); + return $this->filterSimilar($activityList); } diff --git a/app/App/HomeController.php b/app/App/HomeController.php index 8188ad010..48d60b8e4 100644 --- a/app/App/HomeController.php +++ b/app/App/HomeController.php @@ -5,6 +5,7 @@ namespace BookStack\App; use BookStack\Activity\ActivityQueries; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Queries\RecentlyViewed; use BookStack\Entities\Queries\TopFavourites; use BookStack\Entities\Repos\BookRepo; @@ -26,9 +27,7 @@ class HomeController extends Controller $draftPages = []; if ($this->isSignedIn()) { - $draftPages = Page::visible() - ->where('draft', '=', true) - ->where('created_by', '=', user()->id) + $draftPages = PageQueries::currentUserDraftsForList() ->orderBy('updated_at', 'desc') ->with('book') ->take(6) @@ -40,11 +39,10 @@ class HomeController extends Controller (new RecentlyViewed())->run(12 * $recentFactor, 1) : Book::visible()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get(); $favourites = (new TopFavourites())->run(6); - $recentlyUpdatedPages = Page::visible()->with('book') + $recentlyUpdatedPages = PageQueries::visibleForList() ->where('draft', false) ->orderBy('updated_at', 'desc') ->take($favourites->count() > 0 ? 5 : 10) - ->select(Page::$listAttributes) ->get(); $homepageOptions = ['default', 'books', 'bookshelves', 'page']; @@ -95,7 +93,7 @@ class HomeController extends Controller $homepageSetting = setting('app-homepage', '0:'); $id = intval(explode(':', $homepageSetting)[0]); /** @var Page $customHomepage */ - $customHomepage = Page::query()->where('draft', '=', false)->findOrFail($id); + $customHomepage = PageQueries::start()->where('draft', '=', false)->findOrFail($id); $pageContent = new PageContent($customHomepage); $customHomepage->html = $pageContent->render(false); diff --git a/app/Entities/Models/BookChild.php b/app/Entities/Models/BookChild.php index 18735e56b..d19a2466a 100644 --- a/app/Entities/Models/BookChild.php +++ b/app/Entities/Models/BookChild.php @@ -18,20 +18,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; */ abstract class BookChild extends Entity { - protected static function boot() - { - parent::boot(); - - // Load book slugs onto these models by default during query-time - static::addGlobalScope('book_slug', function (Builder $builder) { - $builder->addSelect(['book_slug' => function ($builder) { - $builder->select('slug') - ->from('books') - ->whereColumn('books.id', '=', 'book_id'); - }]); - }); - } - /** * Scope a query to find items where the child has the given childSlug * where its parent has the bookSlug. diff --git a/app/Entities/Queries/EntityQuery.php b/app/Entities/Queries/EntityQuery.php index 2246e13b1..bd7a98b5e 100644 --- a/app/Entities/Queries/EntityQuery.php +++ b/app/Entities/Queries/EntityQuery.php @@ -3,10 +3,16 @@ namespace BookStack\Entities\Queries; use BookStack\Entities\EntityProvider; +use BookStack\Entities\Tools\MixedEntityListLoader; use BookStack\Permissions\PermissionApplicator; abstract class EntityQuery { + protected function mixedEntityListLoader(): MixedEntityListLoader + { + return app()->make(MixedEntityListLoader::class); + } + protected function permissionService(): PermissionApplicator { return app()->make(PermissionApplicator::class); diff --git a/app/Entities/Queries/PageQueries.php b/app/Entities/Queries/PageQueries.php new file mode 100644 index 000000000..7b7ac1e5e --- /dev/null +++ b/app/Entities/Queries/PageQueries.php @@ -0,0 +1,31 @@ +select(array_merge(Page::$listAttributes, ['book_slug' => function ($builder) { + $builder->select('slug') + ->from('books') + ->whereColumn('books.id', '=', 'pages.book_id'); + }])); + } + + public static function currentUserDraftsForList(): Builder + { + return static::visibleForList() + ->where('draft', '=', true) + ->where('created_by', '=', user()->id); + } +} diff --git a/app/Entities/Queries/RecentlyViewed.php b/app/Entities/Queries/RecentlyViewed.php index 5895b97a2..fed15ca5a 100644 --- a/app/Entities/Queries/RecentlyViewed.php +++ b/app/Entities/Queries/RecentlyViewed.php @@ -10,7 +10,7 @@ class RecentlyViewed extends EntityQuery public function run(int $count, int $page): Collection { $user = user(); - if ($user === null || $user->isGuest()) { + if ($user->isGuest()) { return collect(); } @@ -23,11 +23,13 @@ class RecentlyViewed extends EntityQuery ->orderBy('views.updated_at', 'desc') ->where('user_id', '=', user()->id); - return $query->with('viewable') + $views = $query ->skip(($page - 1) * $count) ->take($count) - ->get() - ->pluck('viewable') - ->filter(); + ->get(); + + $this->mixedEntityListLoader()->loadIntoRelations($views->all(), 'viewable', false); + + return $views->pluck('viewable')->filter(); } } diff --git a/app/Entities/Queries/TopFavourites.php b/app/Entities/Queries/TopFavourites.php index a2f8d9ea1..47d4b77f7 100644 --- a/app/Entities/Queries/TopFavourites.php +++ b/app/Entities/Queries/TopFavourites.php @@ -25,11 +25,13 @@ class TopFavourites extends EntityQuery ->orderBy('views.views', 'desc') ->where('favourites.user_id', '=', user()->id); - return $query->with('favouritable') + $favourites = $query ->skip($skip) ->take($count) - ->get() - ->pluck('favouritable') - ->filter(); + ->get(); + + $this->mixedEntityListLoader()->loadIntoRelations($favourites->all(), 'favouritable', false); + + return $favourites->pluck('favouritable')->filter(); } } diff --git a/app/Entities/Tools/MixedEntityListLoader.php b/app/Entities/Tools/MixedEntityListLoader.php index 50079e3bf..a0df791db 100644 --- a/app/Entities/Tools/MixedEntityListLoader.php +++ b/app/Entities/Tools/MixedEntityListLoader.php @@ -26,7 +26,7 @@ class MixedEntityListLoader * This will look for a model id and type via 'name_id' and 'name_type'. * @param Model[] $relations */ - public function loadIntoRelations(array $relations, string $relationName): void + public function loadIntoRelations(array $relations, string $relationName, bool $loadParents): void { $idsByType = []; foreach ($relations as $relation) { @@ -40,7 +40,7 @@ class MixedEntityListLoader $idsByType[$type][] = $id; } - $modelMap = $this->idsByTypeToModelMap($idsByType); + $modelMap = $this->idsByTypeToModelMap($idsByType, $loadParents); foreach ($relations as $relation) { $type = $relation->getAttribute($relationName . '_type'); @@ -56,7 +56,7 @@ class MixedEntityListLoader * @param array $idsByType * @return array> */ - protected function idsByTypeToModelMap(array $idsByType): array + protected function idsByTypeToModelMap(array $idsByType, bool $eagerLoadParents): array { $modelMap = []; @@ -67,10 +67,10 @@ class MixedEntityListLoader $instance = $this->entityProvider->get($type); $models = $instance->newQuery() - ->select($this->listAttributes[$type]) + ->select(array_merge($this->listAttributes[$type], $this->getSubSelectsForQuery($type))) ->scopes('visible') ->whereIn('id', $ids) - ->with($this->getRelationsToEagerLoad($type)) + ->with($eagerLoadParents ? $this->getRelationsToEagerLoad($type) : []) ->get(); if (count($models) > 0) { @@ -100,4 +100,19 @@ class MixedEntityListLoader return $toLoad; } + + protected function getSubSelectsForQuery(string $type): array + { + $subSelects = []; + + if ($type === 'chapter' || $type === 'page') { + $subSelects['book_slug'] = function ($builder) { + $builder->select('slug') + ->from('books') + ->whereColumn('books.id', '=', 'book_id'); + }; + } + + return $subSelects; + } } diff --git a/app/References/ReferenceFetcher.php b/app/References/ReferenceFetcher.php index 0d9883a3e..655ea7c09 100644 --- a/app/References/ReferenceFetcher.php +++ b/app/References/ReferenceFetcher.php @@ -23,7 +23,7 @@ class ReferenceFetcher public function getReferencesToEntity(Entity $entity): Collection { $references = $this->queryReferencesToEntity($entity)->get(); - $this->mixedEntityListLoader->loadIntoRelations($references->all(), 'from'); + $this->mixedEntityListLoader->loadIntoRelations($references->all(), 'from', true); return $references; } diff --git a/database/migrations/2024_02_04_141358_add_views_updated_index.php b/database/migrations/2024_02_04_141358_add_views_updated_index.php new file mode 100644 index 000000000..a643b3a1e --- /dev/null +++ b/database/migrations/2024_02_04_141358_add_views_updated_index.php @@ -0,0 +1,32 @@ +index(['updated_at'], 'views_updated_at_index'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('views', function (Blueprint $table) { + $table->dropIndex('views_updated_at_index'); + }); + } +}; From 1559b0acd1018edff3f173df0c87f631549462fa Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 4 Feb 2024 17:35:16 +0000 Subject: [PATCH 057/124] Queries: Migrated BookRepo queries to new query class Also moved to a non-static approach, and added a high-level class to allow easy access to all other entity queries, for use in mixed-entity scenarios and easier/simpler injection. --- app/App/HomeController.php | 18 ++++-- app/Entities/Controllers/BookController.php | 30 +++++----- .../Controllers/BookExportController.php | 24 +++----- .../Controllers/BookSortController.php | 16 +++--- app/Entities/Queries/BookQueries.php | 56 ++++++++++++++++++ app/Entities/Queries/EntityQueries.php | 12 ++++ app/Entities/Queries/PageQueries.php | 10 ++-- app/Entities/Repos/BookRepo.php | 57 ------------------- 8 files changed, 118 insertions(+), 105 deletions(-) create mode 100644 app/Entities/Queries/BookQueries.php create mode 100644 app/Entities/Queries/EntityQueries.php diff --git a/app/App/HomeController.php b/app/App/HomeController.php index 48d60b8e4..dacd4bcc2 100644 --- a/app/App/HomeController.php +++ b/app/App/HomeController.php @@ -5,10 +5,9 @@ namespace BookStack\App; use BookStack\Activity\ActivityQueries; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Page; -use BookStack\Entities\Queries\PageQueries; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Entities\Queries\RecentlyViewed; use BookStack\Entities\Queries\TopFavourites; -use BookStack\Entities\Repos\BookRepo; use BookStack\Entities\Repos\BookshelfRepo; use BookStack\Entities\Tools\PageContent; use BookStack\Http\Controller; @@ -18,6 +17,11 @@ use Illuminate\Http\Request; class HomeController extends Controller { + public function __construct( + protected EntityQueries $queries, + ) { + } + /** * Display the homepage. */ @@ -27,7 +31,7 @@ class HomeController extends Controller $draftPages = []; if ($this->isSignedIn()) { - $draftPages = PageQueries::currentUserDraftsForList() + $draftPages = $this->queries->pages->currentUserDraftsForList() ->orderBy('updated_at', 'desc') ->with('book') ->take(6) @@ -39,7 +43,7 @@ class HomeController extends Controller (new RecentlyViewed())->run(12 * $recentFactor, 1) : Book::visible()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get(); $favourites = (new TopFavourites())->run(6); - $recentlyUpdatedPages = PageQueries::visibleForList() + $recentlyUpdatedPages = $this->queries->pages->visibleForList() ->where('draft', false) ->orderBy('updated_at', 'desc') ->take($favourites->count() > 0 ? 5 : 10) @@ -83,7 +87,9 @@ class HomeController extends Controller } if ($homepageOption === 'books') { - $books = app()->make(BookRepo::class)->getAllPaginated(18, $commonData['listOptions']->getSort(), $commonData['listOptions']->getOrder()); + $books = $this->queries->books->visibleForListWithCover() + ->orderBy($commonData['listOptions']->getSort(), $commonData['listOptions']->getOrder()) + ->paginate(18); $data = array_merge($commonData, ['books' => $books]); return view('home.books', $data); @@ -93,7 +99,7 @@ class HomeController extends Controller $homepageSetting = setting('app-homepage', '0:'); $id = intval(explode(':', $homepageSetting)[0]); /** @var Page $customHomepage */ - $customHomepage = PageQueries::start()->where('draft', '=', false)->findOrFail($id); + $customHomepage = $this->queries->pages->start()->where('draft', '=', false)->findOrFail($id); $pageContent = new PageContent($customHomepage); $customHomepage->html = $pageContent->render(false); diff --git a/app/Entities/Controllers/BookController.php b/app/Entities/Controllers/BookController.php index 412feca2f..a956a47d6 100644 --- a/app/Entities/Controllers/BookController.php +++ b/app/Entities/Controllers/BookController.php @@ -7,6 +7,7 @@ use BookStack\Activity\ActivityType; use BookStack\Activity\Models\View; use BookStack\Activity\Tools\UserEntityWatchOptions; use BookStack\Entities\Models\Bookshelf; +use BookStack\Entities\Queries\BookQueries; use BookStack\Entities\Repos\BookRepo; use BookStack\Entities\Tools\BookContents; use BookStack\Entities\Tools\Cloner; @@ -27,7 +28,8 @@ class BookController extends Controller public function __construct( protected ShelfContext $shelfContext, protected BookRepo $bookRepo, - protected ReferenceFetcher $referenceFetcher + protected BookQueries $queries, + protected ReferenceFetcher $referenceFetcher, ) { } @@ -43,10 +45,12 @@ class BookController extends Controller 'updated_at' => trans('common.sort_updated_at'), ]); - $books = $this->bookRepo->getAllPaginated(18, $listOptions->getSort(), $listOptions->getOrder()); - $recents = $this->isSignedIn() ? $this->bookRepo->getRecentlyViewed(4) : false; - $popular = $this->bookRepo->getPopular(4); - $new = $this->bookRepo->getRecentlyCreated(4); + $books = $this->queries->visibleForListWithCover() + ->orderBy($listOptions->getSort(), $listOptions->getOrder()) + ->paginate(18); + $recents = $this->isSignedIn() ? $this->queries->recentlyViewedForCurrentUser()->take(4)->get() : false; + $popular = $this->queries->popularForList()->take(4)->get(); + $new = $this->queries->visibleForList()->orderBy('created_at', 'desc')->take(4)->get(); $this->shelfContext->clearShelfContext(); @@ -120,7 +124,7 @@ class BookController extends Controller */ public function show(Request $request, ActivityQueries $activities, string $slug) { - $book = $this->bookRepo->getBySlug($slug); + $book = $this->queries->findVisibleBySlug($slug); $bookChildren = (new BookContents($book))->getTree(true); $bookParentShelves = $book->shelves()->scopes('visible')->get(); @@ -147,7 +151,7 @@ class BookController extends Controller */ public function edit(string $slug) { - $book = $this->bookRepo->getBySlug($slug); + $book = $this->queries->findVisibleBySlug($slug); $this->checkOwnablePermission('book-update', $book); $this->setPageTitle(trans('entities.books_edit_named', ['bookName' => $book->getShortName()])); @@ -163,7 +167,7 @@ class BookController extends Controller */ public function update(Request $request, string $slug) { - $book = $this->bookRepo->getBySlug($slug); + $book = $this->queries->findVisibleBySlug($slug); $this->checkOwnablePermission('book-update', $book); $validated = $this->validate($request, [ @@ -190,7 +194,7 @@ class BookController extends Controller */ public function showDelete(string $bookSlug) { - $book = $this->bookRepo->getBySlug($bookSlug); + $book = $this->queries->findVisibleBySlug($bookSlug); $this->checkOwnablePermission('book-delete', $book); $this->setPageTitle(trans('entities.books_delete_named', ['bookName' => $book->getShortName()])); @@ -204,7 +208,7 @@ class BookController extends Controller */ public function destroy(string $bookSlug) { - $book = $this->bookRepo->getBySlug($bookSlug); + $book = $this->queries->findVisibleBySlug($bookSlug); $this->checkOwnablePermission('book-delete', $book); $this->bookRepo->destroy($book); @@ -219,7 +223,7 @@ class BookController extends Controller */ public function showCopy(string $bookSlug) { - $book = $this->bookRepo->getBySlug($bookSlug); + $book = $this->queries->findVisibleBySlug($bookSlug); $this->checkOwnablePermission('book-view', $book); session()->flashInput(['name' => $book->name]); @@ -236,7 +240,7 @@ class BookController extends Controller */ public function copy(Request $request, Cloner $cloner, string $bookSlug) { - $book = $this->bookRepo->getBySlug($bookSlug); + $book = $this->queries->findVisibleBySlug($bookSlug); $this->checkOwnablePermission('book-view', $book); $this->checkPermission('book-create-all'); @@ -252,7 +256,7 @@ class BookController extends Controller */ public function convertToShelf(HierarchyTransformer $transformer, string $bookSlug) { - $book = $this->bookRepo->getBySlug($bookSlug); + $book = $this->queries->findVisibleBySlug($bookSlug); $this->checkOwnablePermission('book-update', $book); $this->checkOwnablePermission('book-delete', $book); $this->checkPermission('bookshelf-create-all'); diff --git a/app/Entities/Controllers/BookExportController.php b/app/Entities/Controllers/BookExportController.php index 1a6b20db9..6540df978 100644 --- a/app/Entities/Controllers/BookExportController.php +++ b/app/Entities/Controllers/BookExportController.php @@ -2,23 +2,17 @@ namespace BookStack\Entities\Controllers; -use BookStack\Entities\Repos\BookRepo; +use BookStack\Entities\Queries\BookQueries; use BookStack\Entities\Tools\ExportFormatter; use BookStack\Http\Controller; use Throwable; class BookExportController extends Controller { - protected $bookRepo; - protected $exportFormatter; - - /** - * BookExportController constructor. - */ - public function __construct(BookRepo $bookRepo, ExportFormatter $exportFormatter) - { - $this->bookRepo = $bookRepo; - $this->exportFormatter = $exportFormatter; + public function __construct( + protected BookQueries $queries, + protected ExportFormatter $exportFormatter, + ) { $this->middleware('can:content-export'); } @@ -29,7 +23,7 @@ class BookExportController extends Controller */ public function pdf(string $bookSlug) { - $book = $this->bookRepo->getBySlug($bookSlug); + $book = $this->queries->findVisibleBySlug($bookSlug); $pdfContent = $this->exportFormatter->bookToPdf($book); return $this->download()->directly($pdfContent, $bookSlug . '.pdf'); @@ -42,7 +36,7 @@ class BookExportController extends Controller */ public function html(string $bookSlug) { - $book = $this->bookRepo->getBySlug($bookSlug); + $book = $this->queries->findVisibleBySlug($bookSlug); $htmlContent = $this->exportFormatter->bookToContainedHtml($book); return $this->download()->directly($htmlContent, $bookSlug . '.html'); @@ -53,7 +47,7 @@ class BookExportController extends Controller */ public function plainText(string $bookSlug) { - $book = $this->bookRepo->getBySlug($bookSlug); + $book = $this->queries->findVisibleBySlug($bookSlug); $textContent = $this->exportFormatter->bookToPlainText($book); return $this->download()->directly($textContent, $bookSlug . '.txt'); @@ -64,7 +58,7 @@ class BookExportController extends Controller */ public function markdown(string $bookSlug) { - $book = $this->bookRepo->getBySlug($bookSlug); + $book = $this->queries->findVisibleBySlug($bookSlug); $textContent = $this->exportFormatter->bookToMarkdown($book); return $this->download()->directly($textContent, $bookSlug . '.md'); diff --git a/app/Entities/Controllers/BookSortController.php b/app/Entities/Controllers/BookSortController.php index f2310e205..5d7024952 100644 --- a/app/Entities/Controllers/BookSortController.php +++ b/app/Entities/Controllers/BookSortController.php @@ -3,7 +3,7 @@ namespace BookStack\Entities\Controllers; use BookStack\Activity\ActivityType; -use BookStack\Entities\Repos\BookRepo; +use BookStack\Entities\Queries\BookQueries; use BookStack\Entities\Tools\BookContents; use BookStack\Entities\Tools\BookSortMap; use BookStack\Facades\Activity; @@ -12,11 +12,9 @@ use Illuminate\Http\Request; class BookSortController extends Controller { - protected $bookRepo; - - public function __construct(BookRepo $bookRepo) - { - $this->bookRepo = $bookRepo; + public function __construct( + protected BookQueries $queries, + ) { } /** @@ -24,7 +22,7 @@ class BookSortController extends Controller */ public function show(string $bookSlug) { - $book = $this->bookRepo->getBySlug($bookSlug); + $book = $this->queries->findVisibleBySlug($bookSlug); $this->checkOwnablePermission('book-update', $book); $bookChildren = (new BookContents($book))->getTree(false); @@ -40,7 +38,7 @@ class BookSortController extends Controller */ public function showItem(string $bookSlug) { - $book = $this->bookRepo->getBySlug($bookSlug); + $book = $this->queries->findVisibleBySlug($bookSlug); $bookChildren = (new BookContents($book))->getTree(); return view('books.parts.sort-box', ['book' => $book, 'bookChildren' => $bookChildren]); @@ -51,7 +49,7 @@ class BookSortController extends Controller */ public function update(Request $request, string $bookSlug) { - $book = $this->bookRepo->getBySlug($bookSlug); + $book = $this->queries->findVisibleBySlug($bookSlug); $this->checkOwnablePermission('book-update', $book); // Return if no map sent diff --git a/app/Entities/Queries/BookQueries.php b/app/Entities/Queries/BookQueries.php new file mode 100644 index 000000000..6de28f0c2 --- /dev/null +++ b/app/Entities/Queries/BookQueries.php @@ -0,0 +1,56 @@ +start() + ->scopes('visible') + ->where('slug', '=', $slug) + ->first(); + + if ($book === null) { + throw new NotFoundException(trans('errors.book_not_found')); + } + + return $book; + } + + public function visibleForList(): Builder + { + return $this->start()->scopes('visible'); + } + + public function visibleForListWithCover(): Builder + { + return $this->visibleForList()->with('cover'); + } + + public function recentlyViewedForCurrentUser(): Builder + { + return $this->visibleForList() + ->scopes('withLastView') + ->having('last_viewed_at', '>', 0) + ->orderBy('last_viewed_at', 'desc'); + } + + public function popularForList(): Builder + { + return $this->visibleForList() + ->scopes('withViewCount') + ->having('view_count', '>', 0) + ->orderBy('view_count', 'desc'); + } +} diff --git a/app/Entities/Queries/EntityQueries.php b/app/Entities/Queries/EntityQueries.php new file mode 100644 index 000000000..417db455c --- /dev/null +++ b/app/Entities/Queries/EntityQueries.php @@ -0,0 +1,12 @@ +start() ->select(array_merge(Page::$listAttributes, ['book_slug' => function ($builder) { $builder->select('slug') ->from('books') @@ -22,9 +22,9 @@ class PageQueries }])); } - public static function currentUserDraftsForList(): Builder + public function currentUserDraftsForList(): Builder { - return static::visibleForList() + return $this->visibleForList() ->where('draft', '=', true) ->where('created_by', '=', user()->id); } diff --git a/app/Entities/Repos/BookRepo.php b/app/Entities/Repos/BookRepo.php index bf765b22d..26b9414fb 100644 --- a/app/Entities/Repos/BookRepo.php +++ b/app/Entities/Repos/BookRepo.php @@ -5,16 +5,12 @@ namespace BookStack\Entities\Repos; use BookStack\Activity\ActivityType; use BookStack\Activity\TagRepo; use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Page; use BookStack\Entities\Tools\TrashCan; use BookStack\Exceptions\ImageUploadException; -use BookStack\Exceptions\NotFoundException; use BookStack\Facades\Activity; use BookStack\Uploads\ImageRepo; use Exception; -use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Http\UploadedFile; -use Illuminate\Support\Collection; class BookRepo { @@ -25,59 +21,6 @@ class BookRepo ) { } - /** - * Get all books in a paginated format. - */ - public function getAllPaginated(int $count = 20, string $sort = 'name', string $order = 'asc'): LengthAwarePaginator - { - return Book::visible()->with('cover')->orderBy($sort, $order)->paginate($count); - } - - /** - * Get the books that were most recently viewed by this user. - */ - public function getRecentlyViewed(int $count = 20): Collection - { - return Book::visible()->withLastView() - ->having('last_viewed_at', '>', 0) - ->orderBy('last_viewed_at', 'desc') - ->take($count)->get(); - } - - /** - * Get the most popular books in the system. - */ - public function getPopular(int $count = 20): Collection - { - return Book::visible()->withViewCount() - ->having('view_count', '>', 0) - ->orderBy('view_count', 'desc') - ->take($count)->get(); - } - - /** - * Get the most recently created books from the system. - */ - public function getRecentlyCreated(int $count = 20): Collection - { - return Book::visible()->orderBy('created_at', 'desc') - ->take($count)->get(); - } - - /** - * Get a book by its slug. - */ - public function getBySlug(string $slug): Book - { - $book = Book::visible()->where('slug', '=', $slug)->first(); - - if ($book === null) { - throw new NotFoundException(trans('errors.book_not_found')); - } - - return $book; - } - /** * Create a new book in the system. */ From 3886aedf54873a1bf90cfdaa9cafc6f22c1d03fd Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 4 Feb 2024 19:32:19 +0000 Subject: [PATCH 058/124] Queries: Migrated bookshelf repo queries to new class --- app/App/HomeController.php | 5 +- .../Controllers/BookshelfController.php | 27 ++++--- app/Entities/Queries/BookshelfQueries.php | 56 +++++++++++++++ app/Entities/Queries/EntityQueries.php | 1 + app/Entities/Repos/BookshelfRepo.php | 70 +------------------ 5 files changed, 80 insertions(+), 79 deletions(-) create mode 100644 app/Entities/Queries/BookshelfQueries.php diff --git a/app/App/HomeController.php b/app/App/HomeController.php index dacd4bcc2..f5a3c7ed6 100644 --- a/app/App/HomeController.php +++ b/app/App/HomeController.php @@ -8,7 +8,6 @@ use BookStack\Entities\Models\Page; use BookStack\Entities\Queries\EntityQueries; use BookStack\Entities\Queries\RecentlyViewed; use BookStack\Entities\Queries\TopFavourites; -use BookStack\Entities\Repos\BookshelfRepo; use BookStack\Entities\Tools\PageContent; use BookStack\Http\Controller; use BookStack\Uploads\FaviconHandler; @@ -80,7 +79,9 @@ class HomeController extends Controller } if ($homepageOption === 'bookshelves') { - $shelves = app()->make(BookshelfRepo::class)->getAllPaginated(18, $commonData['listOptions']->getSort(), $commonData['listOptions']->getOrder()); + $shelves = $this->queries->shelves->visibleForListWithCover() + ->orderBy($commonData['listOptions']->getSort(), $commonData['listOptions']->getOrder()) + ->paginate(18); $data = array_merge($commonData, ['shelves' => $shelves]); return view('home.shelves', $data); diff --git a/app/Entities/Controllers/BookshelfController.php b/app/Entities/Controllers/BookshelfController.php index 2f5461cdb..bc3fc2a6f 100644 --- a/app/Entities/Controllers/BookshelfController.php +++ b/app/Entities/Controllers/BookshelfController.php @@ -5,6 +5,7 @@ namespace BookStack\Entities\Controllers; use BookStack\Activity\ActivityQueries; use BookStack\Activity\Models\View; use BookStack\Entities\Models\Book; +use BookStack\Entities\Queries\BookshelfQueries; use BookStack\Entities\Repos\BookshelfRepo; use BookStack\Entities\Tools\ShelfContext; use BookStack\Exceptions\ImageUploadException; @@ -20,8 +21,9 @@ class BookshelfController extends Controller { public function __construct( protected BookshelfRepo $shelfRepo, + protected BookshelfQueries $queries, protected ShelfContext $shelfContext, - protected ReferenceFetcher $referenceFetcher + protected ReferenceFetcher $referenceFetcher, ) { } @@ -37,10 +39,15 @@ class BookshelfController extends Controller 'updated_at' => trans('common.sort_updated_at'), ]); - $shelves = $this->shelfRepo->getAllPaginated(18, $listOptions->getSort(), $listOptions->getOrder()); - $recents = $this->isSignedIn() ? $this->shelfRepo->getRecentlyViewed(4) : false; - $popular = $this->shelfRepo->getPopular(4); - $new = $this->shelfRepo->getRecentlyCreated(4); + $shelves = $this->queries->visibleForListWithCover() + ->orderBy($listOptions->getSort(), $listOptions->getOrder()) + ->paginate(18); + $recents = $this->isSignedIn() ? $this->queries->recentlyViewedForCurrentUser()->get() : false; + $popular = $this->queries->popularForList()->get(); + $new = $this->queries->visibleForList() + ->orderBy('created_at', 'desc') + ->take(4) + ->get(); $this->shelfContext->clearShelfContext(); $this->setPageTitle(trans('entities.shelves')); @@ -96,7 +103,7 @@ class BookshelfController extends Controller */ public function show(Request $request, ActivityQueries $activities, string $slug) { - $shelf = $this->shelfRepo->getBySlug($slug); + $shelf = $this->queries->findVisibleBySlug($slug); $this->checkOwnablePermission('bookshelf-view', $shelf); $listOptions = SimpleListOptions::fromRequest($request, 'shelf_books')->withSortOptions([ @@ -134,7 +141,7 @@ class BookshelfController extends Controller */ public function edit(string $slug) { - $shelf = $this->shelfRepo->getBySlug($slug); + $shelf = $this->queries->findVisibleBySlug($slug); $this->checkOwnablePermission('bookshelf-update', $shelf); $shelfBookIds = $shelf->books()->get(['id'])->pluck('id'); @@ -157,7 +164,7 @@ class BookshelfController extends Controller */ public function update(Request $request, string $slug) { - $shelf = $this->shelfRepo->getBySlug($slug); + $shelf = $this->queries->findVisibleBySlug($slug); $this->checkOwnablePermission('bookshelf-update', $shelf); $validated = $this->validate($request, [ 'name' => ['required', 'string', 'max:255'], @@ -183,7 +190,7 @@ class BookshelfController extends Controller */ public function showDelete(string $slug) { - $shelf = $this->shelfRepo->getBySlug($slug); + $shelf = $this->queries->findVisibleBySlug($slug); $this->checkOwnablePermission('bookshelf-delete', $shelf); $this->setPageTitle(trans('entities.shelves_delete_named', ['name' => $shelf->getShortName()])); @@ -198,7 +205,7 @@ class BookshelfController extends Controller */ public function destroy(string $slug) { - $shelf = $this->shelfRepo->getBySlug($slug); + $shelf = $this->queries->findVisibleBySlug($slug); $this->checkOwnablePermission('bookshelf-delete', $shelf); $this->shelfRepo->destroy($shelf); diff --git a/app/Entities/Queries/BookshelfQueries.php b/app/Entities/Queries/BookshelfQueries.php new file mode 100644 index 000000000..7edff45b9 --- /dev/null +++ b/app/Entities/Queries/BookshelfQueries.php @@ -0,0 +1,56 @@ +start() + ->scopes('visible') + ->where('slug', '=', $slug) + ->first(); + + if ($shelf === null) { + throw new NotFoundException(trans('errors.bookshelf_not_found')); + } + + return $shelf; + } + + public function visibleForList(): Builder + { + return $this->start()->scopes('visible'); + } + + public function visibleForListWithCover(): Builder + { + return $this->visibleForList()->with('cover'); + } + + public function recentlyViewedForCurrentUser(): Builder + { + return $this->visibleForList() + ->scopes('withLastView') + ->having('last_viewed_at', '>', 0) + ->orderBy('last_viewed_at', 'desc'); + } + + public function popularForList(): Builder + { + return $this->visibleForList() + ->scopes('withViewCount') + ->having('view_count', '>', 0) + ->orderBy('view_count', 'desc'); + } +} diff --git a/app/Entities/Queries/EntityQueries.php b/app/Entities/Queries/EntityQueries.php index 417db455c..b1973b0ea 100644 --- a/app/Entities/Queries/EntityQueries.php +++ b/app/Entities/Queries/EntityQueries.php @@ -5,6 +5,7 @@ namespace BookStack\Entities\Queries; class EntityQueries { public function __construct( + public BookshelfQueries $shelves, public BookQueries $books, public PageQueries $pages, ) { diff --git a/app/Entities/Repos/BookshelfRepo.php b/app/Entities/Repos/BookshelfRepo.php index 27333b5b1..479e6178f 100644 --- a/app/Entities/Repos/BookshelfRepo.php +++ b/app/Entities/Repos/BookshelfRepo.php @@ -6,78 +6,14 @@ use BookStack\Activity\ActivityType; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Tools\TrashCan; -use BookStack\Exceptions\NotFoundException; use BookStack\Facades\Activity; use Exception; -use Illuminate\Contracts\Pagination\LengthAwarePaginator; -use Illuminate\Support\Collection; class BookshelfRepo { - protected $baseRepo; - - /** - * BookshelfRepo constructor. - */ - public function __construct(BaseRepo $baseRepo) - { - $this->baseRepo = $baseRepo; - } - - /** - * Get all bookshelves in a paginated format. - */ - public function getAllPaginated(int $count = 20, string $sort = 'name', string $order = 'asc'): LengthAwarePaginator - { - return Bookshelf::visible() - ->with(['visibleBooks', 'cover']) - ->orderBy($sort, $order) - ->paginate($count); - } - - /** - * Get the bookshelves that were most recently viewed by this user. - */ - public function getRecentlyViewed(int $count = 20): Collection - { - return Bookshelf::visible()->withLastView() - ->having('last_viewed_at', '>', 0) - ->orderBy('last_viewed_at', 'desc') - ->take($count)->get(); - } - - /** - * Get the most popular bookshelves in the system. - */ - public function getPopular(int $count = 20): Collection - { - return Bookshelf::visible()->withViewCount() - ->having('view_count', '>', 0) - ->orderBy('view_count', 'desc') - ->take($count)->get(); - } - - /** - * Get the most recently created bookshelves from the system. - */ - public function getRecentlyCreated(int $count = 20): Collection - { - return Bookshelf::visible()->orderBy('created_at', 'desc') - ->take($count)->get(); - } - - /** - * Get a shelf by its slug. - */ - public function getBySlug(string $slug): Bookshelf - { - $shelf = Bookshelf::visible()->where('slug', '=', $slug)->first(); - - if ($shelf === null) { - throw new NotFoundException(trans('errors.bookshelf_not_found')); - } - - return $shelf; + public function __construct( + protected BaseRepo $baseRepo, + ) { } /** From 9fa68fd8aba200ebc51589432afaf3aaf8bb2e39 Mon Sep 17 00:00:00 2001 From: Mikhail Shashin Date: Mon, 5 Feb 2024 04:28:22 +0300 Subject: [PATCH 059/124] Update PWA manifest orientation to any Changed the orientation settings in PwaManifestBuilder.php from 'portrait' to 'any'. This allows the PWA to adjust to any screen orientation, enhancing user flexibility. --- app/App/PwaManifestBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/App/PwaManifestBuilder.php b/app/App/PwaManifestBuilder.php index ba4331a0f..81ab2fcd7 100644 --- a/app/App/PwaManifestBuilder.php +++ b/app/App/PwaManifestBuilder.php @@ -26,7 +26,7 @@ class PwaManifestBuilder "launch_handler" => [ "client_mode" => "focus-existing" ], - "orientation" => "portrait", + "orientation" => "any", "icons" => [ [ "src" => setting('app-icon-32') ?: url('/icon-32.png'), From 8e78b4c43eb980f47d9c207a0ce3330699d54103 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 5 Feb 2024 15:59:20 +0000 Subject: [PATCH 060/124] Queries: Extracted chapter repo queries to class Updated query classes to align to interface for common aligned operations. Extracted repeated string-identifier-based finding from page/chapter repos to shared higher-level entity queries. --- .../Controllers/ChapterController.php | 39 ++++++++------ .../Controllers/ChapterExportController.php | 24 ++++----- app/Entities/Controllers/PageController.php | 8 ++- app/Entities/Models/Chapter.php | 1 - app/Entities/Queries/BookQueries.php | 7 ++- app/Entities/Queries/BookshelfQueries.php | 7 ++- app/Entities/Queries/ChapterQueries.php | 53 +++++++++++++++++++ app/Entities/Queries/EntityQueries.php | 30 +++++++++++ app/Entities/Queries/PageQueries.php | 7 ++- .../Queries/ProvidesEntityQueries.php | 12 +++++ app/Entities/Repos/ChapterRepo.php | 46 ++-------------- app/Entities/Repos/PageRepo.php | 28 ++-------- 12 files changed, 160 insertions(+), 102 deletions(-) create mode 100644 app/Entities/Queries/ChapterQueries.php create mode 100644 app/Entities/Queries/ProvidesEntityQueries.php diff --git a/app/Entities/Controllers/ChapterController.php b/app/Entities/Controllers/ChapterController.php index 00616888a..4b0a525eb 100644 --- a/app/Entities/Controllers/ChapterController.php +++ b/app/Entities/Controllers/ChapterController.php @@ -5,6 +5,8 @@ namespace BookStack\Entities\Controllers; use BookStack\Activity\Models\View; use BookStack\Activity\Tools\UserEntityWatchOptions; use BookStack\Entities\Models\Book; +use BookStack\Entities\Queries\ChapterQueries; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Entities\Repos\ChapterRepo; use BookStack\Entities\Tools\BookContents; use BookStack\Entities\Tools\Cloner; @@ -24,7 +26,9 @@ class ChapterController extends Controller { public function __construct( protected ChapterRepo $chapterRepo, - protected ReferenceFetcher $referenceFetcher + protected ChapterQueries $queries, + protected EntityQueries $entityQueries, + protected ReferenceFetcher $referenceFetcher, ) { } @@ -33,12 +37,15 @@ class ChapterController extends Controller */ public function create(string $bookSlug) { - $book = Book::visible()->where('slug', '=', $bookSlug)->firstOrFail(); + $book = $this->entityQueries->books->findVisibleBySlug($bookSlug); $this->checkOwnablePermission('chapter-create', $book); $this->setPageTitle(trans('entities.chapters_create')); - return view('chapters.create', ['book' => $book, 'current' => $book]); + return view('chapters.create', [ + 'book' => $book, + 'current' => $book, + ]); } /** @@ -55,7 +62,7 @@ class ChapterController extends Controller 'default_template_id' => ['nullable', 'integer'], ]); - $book = Book::visible()->where('slug', '=', $bookSlug)->firstOrFail(); + $book = $this->entityQueries->books->findVisibleBySlug($bookSlug); $this->checkOwnablePermission('chapter-create', $book); $chapter = $this->chapterRepo->create($validated, $book); @@ -68,7 +75,7 @@ class ChapterController extends Controller */ public function show(string $bookSlug, string $chapterSlug) { - $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-view', $chapter); $sidebarTree = (new BookContents($chapter->book))->getTree(); @@ -96,7 +103,7 @@ class ChapterController extends Controller */ public function edit(string $bookSlug, string $chapterSlug) { - $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-update', $chapter); $this->setPageTitle(trans('entities.chapters_edit_named', ['chapterName' => $chapter->getShortName()])); @@ -118,7 +125,7 @@ class ChapterController extends Controller 'default_template_id' => ['nullable', 'integer'], ]); - $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-update', $chapter); $this->chapterRepo->update($chapter, $validated); @@ -133,7 +140,7 @@ class ChapterController extends Controller */ public function showDelete(string $bookSlug, string $chapterSlug) { - $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-delete', $chapter); $this->setPageTitle(trans('entities.chapters_delete_named', ['chapterName' => $chapter->getShortName()])); @@ -149,7 +156,7 @@ class ChapterController extends Controller */ public function destroy(string $bookSlug, string $chapterSlug) { - $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-delete', $chapter); $this->chapterRepo->destroy($chapter); @@ -164,7 +171,7 @@ class ChapterController extends Controller */ public function showMove(string $bookSlug, string $chapterSlug) { - $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); $this->setPageTitle(trans('entities.chapters_move_named', ['chapterName' => $chapter->getShortName()])); $this->checkOwnablePermission('chapter-update', $chapter); $this->checkOwnablePermission('chapter-delete', $chapter); @@ -182,7 +189,7 @@ class ChapterController extends Controller */ public function move(Request $request, string $bookSlug, string $chapterSlug) { - $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-update', $chapter); $this->checkOwnablePermission('chapter-delete', $chapter); @@ -211,7 +218,7 @@ class ChapterController extends Controller */ public function showCopy(string $bookSlug, string $chapterSlug) { - $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-view', $chapter); session()->flashInput(['name' => $chapter->name]); @@ -230,13 +237,13 @@ class ChapterController extends Controller */ public function copy(Request $request, Cloner $cloner, string $bookSlug, string $chapterSlug) { - $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-view', $chapter); $entitySelection = $request->get('entity_selection') ?: null; - $newParentBook = $entitySelection ? $this->chapterRepo->findParentByIdentifier($entitySelection) : $chapter->getParent(); + $newParentBook = $entitySelection ? $this->entityQueries->findVisibleByStringIdentifier($entitySelection) : $chapter->getParent(); - if (is_null($newParentBook)) { + if (!$newParentBook instanceof Book) { $this->showErrorNotification(trans('errors.selected_book_not_found')); return redirect($chapter->getUrl('/copy')); @@ -256,7 +263,7 @@ class ChapterController extends Controller */ public function convertToBook(HierarchyTransformer $transformer, string $bookSlug, string $chapterSlug) { - $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-update', $chapter); $this->checkOwnablePermission('chapter-delete', $chapter); $this->checkPermission('book-create-all'); diff --git a/app/Entities/Controllers/ChapterExportController.php b/app/Entities/Controllers/ChapterExportController.php index b67ec9b37..0e071fc82 100644 --- a/app/Entities/Controllers/ChapterExportController.php +++ b/app/Entities/Controllers/ChapterExportController.php @@ -2,7 +2,7 @@ namespace BookStack\Entities\Controllers; -use BookStack\Entities\Repos\ChapterRepo; +use BookStack\Entities\Queries\ChapterQueries; use BookStack\Entities\Tools\ExportFormatter; use BookStack\Exceptions\NotFoundException; use BookStack\Http\Controller; @@ -10,16 +10,10 @@ use Throwable; class ChapterExportController extends Controller { - protected $chapterRepo; - protected $exportFormatter; - - /** - * ChapterExportController constructor. - */ - public function __construct(ChapterRepo $chapterRepo, ExportFormatter $exportFormatter) - { - $this->chapterRepo = $chapterRepo; - $this->exportFormatter = $exportFormatter; + public function __construct( + protected ChapterQueries $queries, + protected ExportFormatter $exportFormatter, + ) { $this->middleware('can:content-export'); } @@ -31,7 +25,7 @@ class ChapterExportController extends Controller */ public function pdf(string $bookSlug, string $chapterSlug) { - $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); $pdfContent = $this->exportFormatter->chapterToPdf($chapter); return $this->download()->directly($pdfContent, $chapterSlug . '.pdf'); @@ -45,7 +39,7 @@ class ChapterExportController extends Controller */ public function html(string $bookSlug, string $chapterSlug) { - $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); $containedHtml = $this->exportFormatter->chapterToContainedHtml($chapter); return $this->download()->directly($containedHtml, $chapterSlug . '.html'); @@ -58,7 +52,7 @@ class ChapterExportController extends Controller */ public function plainText(string $bookSlug, string $chapterSlug) { - $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); $chapterText = $this->exportFormatter->chapterToPlainText($chapter); return $this->download()->directly($chapterText, $chapterSlug . '.txt'); @@ -71,7 +65,7 @@ class ChapterExportController extends Controller */ public function markdown(string $bookSlug, string $chapterSlug) { - $chapter = $this->chapterRepo->getBySlug($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); $chapterText = $this->exportFormatter->chapterToMarkdown($chapter); return $this->download()->directly($chapterText, $chapterSlug . '.md'); diff --git a/app/Entities/Controllers/PageController.php b/app/Entities/Controllers/PageController.php index eaad3c0b7..f2cd729c6 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -8,6 +8,8 @@ use BookStack\Activity\Tools\UserEntityWatchOptions; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; +use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Repos\PageRepo; use BookStack\Entities\Tools\BookContents; use BookStack\Entities\Tools\Cloner; @@ -29,6 +31,8 @@ class PageController extends Controller { public function __construct( protected PageRepo $pageRepo, + protected PageQueries $pageQueries, + protected EntityQueries $entityQueries, protected ReferenceFetcher $referenceFetcher ) { } @@ -435,9 +439,9 @@ class PageController extends Controller $this->checkOwnablePermission('page-view', $page); $entitySelection = $request->get('entity_selection') ?: null; - $newParent = $entitySelection ? $this->pageRepo->findParentByIdentifier($entitySelection) : $page->getParent(); + $newParent = $entitySelection ? $this->entityQueries->findVisibleByStringIdentifier($entitySelection) : $page->getParent(); - if (is_null($newParent)) { + if (!$newParent instanceof Book && !$newParent instanceof Chapter) { $this->showErrorNotification(trans('errors.selected_book_chapter_not_found')); return redirect($page->getUrl('/copy')); diff --git a/app/Entities/Models/Chapter.php b/app/Entities/Models/Chapter.php index d3a710111..422c82442 100644 --- a/app/Entities/Models/Chapter.php +++ b/app/Entities/Models/Chapter.php @@ -11,7 +11,6 @@ use Illuminate\Support\Collection; * Class Chapter. * * @property Collection $pages - * @property string $description * @property ?int $default_template_id * @property ?Page $defaultTemplate */ diff --git a/app/Entities/Queries/BookQueries.php b/app/Entities/Queries/BookQueries.php index 6de28f0c2..5e85523cf 100644 --- a/app/Entities/Queries/BookQueries.php +++ b/app/Entities/Queries/BookQueries.php @@ -6,13 +6,18 @@ use BookStack\Entities\Models\Book; use BookStack\Exceptions\NotFoundException; use Illuminate\Database\Eloquent\Builder; -class BookQueries +class BookQueries implements ProvidesEntityQueries { public function start(): Builder { return Book::query(); } + public function findVisibleById(int $id): ?Book + { + return $this->start()->scopes('visible')->find($id); + } + public function findVisibleBySlug(string $slug): Book { /** @var ?Book $book */ diff --git a/app/Entities/Queries/BookshelfQueries.php b/app/Entities/Queries/BookshelfQueries.php index 7edff45b9..52e123087 100644 --- a/app/Entities/Queries/BookshelfQueries.php +++ b/app/Entities/Queries/BookshelfQueries.php @@ -6,13 +6,18 @@ use BookStack\Entities\Models\Bookshelf; use BookStack\Exceptions\NotFoundException; use Illuminate\Database\Eloquent\Builder; -class BookshelfQueries +class BookshelfQueries implements ProvidesEntityQueries { public function start(): Builder { return Bookshelf::query(); } + public function findVisibleById(int $id): ?Bookshelf + { + return $this->start()->scopes('visible')->find($id); + } + public function findVisibleBySlug(string $slug): Bookshelf { /** @var ?Bookshelf $shelf */ diff --git a/app/Entities/Queries/ChapterQueries.php b/app/Entities/Queries/ChapterQueries.php new file mode 100644 index 000000000..dcfc4aad3 --- /dev/null +++ b/app/Entities/Queries/ChapterQueries.php @@ -0,0 +1,53 @@ +start()->scopes('visible')->find($id); + } + + public function findVisibleBySlugs(string $bookSlug, string $chapterSlug): Chapter + { + /** @var ?Chapter $chapter */ + $chapter = $this->start()->with('book') + ->whereHas('book', function (Builder $query) use ($bookSlug) { + $query->where('slug', '=', $bookSlug); + }) + ->where('slug', '=', $chapterSlug) + ->first(); + + if ($chapter === null) { + throw new NotFoundException(trans('errors.chapter_not_found')); + } + + return $chapter; + } + + public function visibleForList(): Builder + { + return $this->start() + ->select(array_merge(static::$listAttributes, ['book_slug' => function ($builder) { + $builder->select('slug') + ->from('books') + ->whereColumn('books.id', '=', 'chapters.book_id'); + }])); + } +} diff --git a/app/Entities/Queries/EntityQueries.php b/app/Entities/Queries/EntityQueries.php index b1973b0ea..39a21c913 100644 --- a/app/Entities/Queries/EntityQueries.php +++ b/app/Entities/Queries/EntityQueries.php @@ -2,12 +2,42 @@ namespace BookStack\Entities\Queries; +use BookStack\Entities\Models\Entity; + class EntityQueries { public function __construct( public BookshelfQueries $shelves, public BookQueries $books, + public ChapterQueries $chapters, public PageQueries $pages, ) { } + + /** + * Find an entity via an identifier string in the format: + * {type}:{id} + * Example: (book:5). + */ + public function findVisibleByStringIdentifier(string $identifier): ?Entity + { + $explodedId = explode(':', $identifier); + $entityType = $explodedId[0]; + $entityId = intval($explodedId[1]); + + /** @var ?ProvidesEntityQueries $queries */ + $queries = match ($entityType) { + 'page' => $this->pages, + 'chapter' => $this->chapters, + 'book' => $this->books, + 'bookshelf' => $this->shelves, + default => null, + }; + + if (is_null($queries)) { + return null; + } + + return $queries->findVisibleById($entityId); + } } diff --git a/app/Entities/Queries/PageQueries.php b/app/Entities/Queries/PageQueries.php index b9c64f63c..f1991626f 100644 --- a/app/Entities/Queries/PageQueries.php +++ b/app/Entities/Queries/PageQueries.php @@ -5,13 +5,18 @@ namespace BookStack\Entities\Queries; use BookStack\Entities\Models\Page; use Illuminate\Database\Eloquent\Builder; -class PageQueries +class PageQueries implements ProvidesEntityQueries { public function start(): Builder { return Page::query(); } + public function findVisibleById(int $id): ?Page + { + return $this->start()->scopes('visible')->find($id); + } + public function visibleForList(): Builder { return $this->start() diff --git a/app/Entities/Queries/ProvidesEntityQueries.php b/app/Entities/Queries/ProvidesEntityQueries.php new file mode 100644 index 000000000..5c37b02e4 --- /dev/null +++ b/app/Entities/Queries/ProvidesEntityQueries.php @@ -0,0 +1,12 @@ +whereSlugs($bookSlug, $chapterSlug)->first(); - - if ($chapter === null) { - throw new NotFoundException(trans('errors.chapter_not_found')); - } - - return $chapter; - } - /** * Create a new chapter in the system. */ @@ -91,8 +75,8 @@ class ChapterRepo */ public function move(Chapter $chapter, string $parentIdentifier): Book { - $parent = $this->findParentByIdentifier($parentIdentifier); - if (is_null($parent)) { + $parent = $this->entityQueries->findVisibleByStringIdentifier($parentIdentifier); + if (!$parent instanceof Book) { throw new MoveOperationException('Book to move chapter into not found'); } @@ -106,24 +90,4 @@ class ChapterRepo return $parent; } - - /** - * Find a page parent entity via an identifier string in the format: - * {type}:{id} - * Example: (book:5). - * - * @throws MoveOperationException - */ - public function findParentByIdentifier(string $identifier): ?Book - { - $stringExploded = explode(':', $identifier); - $entityType = $stringExploded[0]; - $entityId = intval($stringExploded[1]); - - if ($entityType !== 'book') { - throw new MoveOperationException('Chapters can only be in books'); - } - - return Book::visible()->where('id', '=', $entityId)->first(); - } } diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index 85237a752..929de528d 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -8,6 +8,7 @@ use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; use BookStack\Entities\Models\PageRevision; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Entities\Tools\BookContents; use BookStack\Entities\Tools\PageContent; use BookStack\Entities\Tools\PageEditorData; @@ -26,6 +27,7 @@ class PageRepo public function __construct( protected BaseRepo $baseRepo, protected RevisionRepo $revisionRepo, + protected EntityQueries $entityQueries, protected ReferenceStore $referenceStore, protected ReferenceUpdater $referenceUpdater ) { @@ -324,8 +326,8 @@ class PageRepo */ public function move(Page $page, string $parentIdentifier): Entity { - $parent = $this->findParentByIdentifier($parentIdentifier); - if (is_null($parent)) { + $parent = $this->entityQueries->findVisibleByStringIdentifier($parentIdentifier); + if (!$parent instanceof Chapter && !$parent instanceof Book) { throw new MoveOperationException('Book or chapter to move page into not found'); } @@ -343,28 +345,6 @@ class PageRepo return $parent; } - /** - * Find a page parent entity via an identifier string in the format: - * {type}:{id} - * Example: (book:5). - * - * @throws MoveOperationException - */ - public function findParentByIdentifier(string $identifier): ?Entity - { - $stringExploded = explode(':', $identifier); - $entityType = $stringExploded[0]; - $entityId = intval($stringExploded[1]); - - if ($entityType !== 'book' && $entityType !== 'chapter') { - throw new MoveOperationException('Pages can only be in books or chapters'); - } - - $parentClass = $entityType === 'book' ? Book::class : Chapter::class; - - return $parentClass::visible()->where('id', '=', $entityId)->first(); - } - /** * Get a new priority for a page. */ From 222c665018cd7fc231d2970307e3a7423e4a377f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Mon, 5 Feb 2024 17:35:49 +0000 Subject: [PATCH 061/124] Queries: Extracted PageRepo queries to own class Started new class for PageRevisions too as part of these changes --- app/Entities/Controllers/BookController.php | 16 ++-- .../Controllers/BookExportController.php | 8 +- .../Controllers/BookSortController.php | 6 +- .../Controllers/BookshelfController.php | 10 +-- .../Controllers/ChapterController.php | 24 ++--- .../Controllers/ChapterExportController.php | 8 +- .../Controllers/PageApiController.php | 10 ++- app/Entities/Controllers/PageController.php | 57 +++++++----- .../Controllers/PageExportController.php | 24 ++--- .../Controllers/PageRevisionController.php | 14 +-- .../Controllers/PageTemplateController.php | 29 ++++--- app/Entities/Queries/BookQueries.php | 2 +- app/Entities/Queries/BookshelfQueries.php | 2 +- app/Entities/Queries/ChapterQueries.php | 4 +- app/Entities/Queries/EntityQueries.php | 1 + app/Entities/Queries/PageQueries.php | 35 ++++++++ app/Entities/Queries/PageRevisionQueries.php | 41 +++++++++ .../Queries/ProvidesEntityQueries.php | 11 ++- app/Entities/Repos/PageRepo.php | 87 ------------------- app/Entities/Tools/PageEditorData.php | 14 +-- .../Controllers/AttachmentController.php | 12 +-- 21 files changed, 219 insertions(+), 196 deletions(-) create mode 100644 app/Entities/Queries/PageRevisionQueries.php diff --git a/app/Entities/Controllers/BookController.php b/app/Entities/Controllers/BookController.php index a956a47d6..0e9346dbd 100644 --- a/app/Entities/Controllers/BookController.php +++ b/app/Entities/Controllers/BookController.php @@ -124,7 +124,7 @@ class BookController extends Controller */ public function show(Request $request, ActivityQueries $activities, string $slug) { - $book = $this->queries->findVisibleBySlug($slug); + $book = $this->queries->findVisibleBySlugOrFail($slug); $bookChildren = (new BookContents($book))->getTree(true); $bookParentShelves = $book->shelves()->scopes('visible')->get(); @@ -151,7 +151,7 @@ class BookController extends Controller */ public function edit(string $slug) { - $book = $this->queries->findVisibleBySlug($slug); + $book = $this->queries->findVisibleBySlugOrFail($slug); $this->checkOwnablePermission('book-update', $book); $this->setPageTitle(trans('entities.books_edit_named', ['bookName' => $book->getShortName()])); @@ -167,7 +167,7 @@ class BookController extends Controller */ public function update(Request $request, string $slug) { - $book = $this->queries->findVisibleBySlug($slug); + $book = $this->queries->findVisibleBySlugOrFail($slug); $this->checkOwnablePermission('book-update', $book); $validated = $this->validate($request, [ @@ -194,7 +194,7 @@ class BookController extends Controller */ public function showDelete(string $bookSlug) { - $book = $this->queries->findVisibleBySlug($bookSlug); + $book = $this->queries->findVisibleBySlugOrFail($bookSlug); $this->checkOwnablePermission('book-delete', $book); $this->setPageTitle(trans('entities.books_delete_named', ['bookName' => $book->getShortName()])); @@ -208,7 +208,7 @@ class BookController extends Controller */ public function destroy(string $bookSlug) { - $book = $this->queries->findVisibleBySlug($bookSlug); + $book = $this->queries->findVisibleBySlugOrFail($bookSlug); $this->checkOwnablePermission('book-delete', $book); $this->bookRepo->destroy($book); @@ -223,7 +223,7 @@ class BookController extends Controller */ public function showCopy(string $bookSlug) { - $book = $this->queries->findVisibleBySlug($bookSlug); + $book = $this->queries->findVisibleBySlugOrFail($bookSlug); $this->checkOwnablePermission('book-view', $book); session()->flashInput(['name' => $book->name]); @@ -240,7 +240,7 @@ class BookController extends Controller */ public function copy(Request $request, Cloner $cloner, string $bookSlug) { - $book = $this->queries->findVisibleBySlug($bookSlug); + $book = $this->queries->findVisibleBySlugOrFail($bookSlug); $this->checkOwnablePermission('book-view', $book); $this->checkPermission('book-create-all'); @@ -256,7 +256,7 @@ class BookController extends Controller */ public function convertToShelf(HierarchyTransformer $transformer, string $bookSlug) { - $book = $this->queries->findVisibleBySlug($bookSlug); + $book = $this->queries->findVisibleBySlugOrFail($bookSlug); $this->checkOwnablePermission('book-update', $book); $this->checkOwnablePermission('book-delete', $book); $this->checkPermission('bookshelf-create-all'); diff --git a/app/Entities/Controllers/BookExportController.php b/app/Entities/Controllers/BookExportController.php index 6540df978..5c1a964c1 100644 --- a/app/Entities/Controllers/BookExportController.php +++ b/app/Entities/Controllers/BookExportController.php @@ -23,7 +23,7 @@ class BookExportController extends Controller */ public function pdf(string $bookSlug) { - $book = $this->queries->findVisibleBySlug($bookSlug); + $book = $this->queries->findVisibleBySlugOrFail($bookSlug); $pdfContent = $this->exportFormatter->bookToPdf($book); return $this->download()->directly($pdfContent, $bookSlug . '.pdf'); @@ -36,7 +36,7 @@ class BookExportController extends Controller */ public function html(string $bookSlug) { - $book = $this->queries->findVisibleBySlug($bookSlug); + $book = $this->queries->findVisibleBySlugOrFail($bookSlug); $htmlContent = $this->exportFormatter->bookToContainedHtml($book); return $this->download()->directly($htmlContent, $bookSlug . '.html'); @@ -47,7 +47,7 @@ class BookExportController extends Controller */ public function plainText(string $bookSlug) { - $book = $this->queries->findVisibleBySlug($bookSlug); + $book = $this->queries->findVisibleBySlugOrFail($bookSlug); $textContent = $this->exportFormatter->bookToPlainText($book); return $this->download()->directly($textContent, $bookSlug . '.txt'); @@ -58,7 +58,7 @@ class BookExportController extends Controller */ public function markdown(string $bookSlug) { - $book = $this->queries->findVisibleBySlug($bookSlug); + $book = $this->queries->findVisibleBySlugOrFail($bookSlug); $textContent = $this->exportFormatter->bookToMarkdown($book); return $this->download()->directly($textContent, $bookSlug . '.md'); diff --git a/app/Entities/Controllers/BookSortController.php b/app/Entities/Controllers/BookSortController.php index 5d7024952..5aefc5832 100644 --- a/app/Entities/Controllers/BookSortController.php +++ b/app/Entities/Controllers/BookSortController.php @@ -22,7 +22,7 @@ class BookSortController extends Controller */ public function show(string $bookSlug) { - $book = $this->queries->findVisibleBySlug($bookSlug); + $book = $this->queries->findVisibleBySlugOrFail($bookSlug); $this->checkOwnablePermission('book-update', $book); $bookChildren = (new BookContents($book))->getTree(false); @@ -38,7 +38,7 @@ class BookSortController extends Controller */ public function showItem(string $bookSlug) { - $book = $this->queries->findVisibleBySlug($bookSlug); + $book = $this->queries->findVisibleBySlugOrFail($bookSlug); $bookChildren = (new BookContents($book))->getTree(); return view('books.parts.sort-box', ['book' => $book, 'bookChildren' => $bookChildren]); @@ -49,7 +49,7 @@ class BookSortController extends Controller */ public function update(Request $request, string $bookSlug) { - $book = $this->queries->findVisibleBySlug($bookSlug); + $book = $this->queries->findVisibleBySlugOrFail($bookSlug); $this->checkOwnablePermission('book-update', $book); // Return if no map sent diff --git a/app/Entities/Controllers/BookshelfController.php b/app/Entities/Controllers/BookshelfController.php index bc3fc2a6f..3118325da 100644 --- a/app/Entities/Controllers/BookshelfController.php +++ b/app/Entities/Controllers/BookshelfController.php @@ -103,7 +103,7 @@ class BookshelfController extends Controller */ public function show(Request $request, ActivityQueries $activities, string $slug) { - $shelf = $this->queries->findVisibleBySlug($slug); + $shelf = $this->queries->findVisibleBySlugOrFail($slug); $this->checkOwnablePermission('bookshelf-view', $shelf); $listOptions = SimpleListOptions::fromRequest($request, 'shelf_books')->withSortOptions([ @@ -141,7 +141,7 @@ class BookshelfController extends Controller */ public function edit(string $slug) { - $shelf = $this->queries->findVisibleBySlug($slug); + $shelf = $this->queries->findVisibleBySlugOrFail($slug); $this->checkOwnablePermission('bookshelf-update', $shelf); $shelfBookIds = $shelf->books()->get(['id'])->pluck('id'); @@ -164,7 +164,7 @@ class BookshelfController extends Controller */ public function update(Request $request, string $slug) { - $shelf = $this->queries->findVisibleBySlug($slug); + $shelf = $this->queries->findVisibleBySlugOrFail($slug); $this->checkOwnablePermission('bookshelf-update', $shelf); $validated = $this->validate($request, [ 'name' => ['required', 'string', 'max:255'], @@ -190,7 +190,7 @@ class BookshelfController extends Controller */ public function showDelete(string $slug) { - $shelf = $this->queries->findVisibleBySlug($slug); + $shelf = $this->queries->findVisibleBySlugOrFail($slug); $this->checkOwnablePermission('bookshelf-delete', $shelf); $this->setPageTitle(trans('entities.shelves_delete_named', ['name' => $shelf->getShortName()])); @@ -205,7 +205,7 @@ class BookshelfController extends Controller */ public function destroy(string $slug) { - $shelf = $this->queries->findVisibleBySlug($slug); + $shelf = $this->queries->findVisibleBySlugOrFail($slug); $this->checkOwnablePermission('bookshelf-delete', $shelf); $this->shelfRepo->destroy($shelf); diff --git a/app/Entities/Controllers/ChapterController.php b/app/Entities/Controllers/ChapterController.php index 4b0a525eb..2e36a84b9 100644 --- a/app/Entities/Controllers/ChapterController.php +++ b/app/Entities/Controllers/ChapterController.php @@ -37,7 +37,7 @@ class ChapterController extends Controller */ public function create(string $bookSlug) { - $book = $this->entityQueries->books->findVisibleBySlug($bookSlug); + $book = $this->entityQueries->books->findVisibleBySlugOrFail($bookSlug); $this->checkOwnablePermission('chapter-create', $book); $this->setPageTitle(trans('entities.chapters_create')); @@ -62,7 +62,7 @@ class ChapterController extends Controller 'default_template_id' => ['nullable', 'integer'], ]); - $book = $this->entityQueries->books->findVisibleBySlug($bookSlug); + $book = $this->entityQueries->books->findVisibleBySlugOrFail($bookSlug); $this->checkOwnablePermission('chapter-create', $book); $chapter = $this->chapterRepo->create($validated, $book); @@ -75,7 +75,7 @@ class ChapterController extends Controller */ public function show(string $bookSlug, string $chapterSlug) { - $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-view', $chapter); $sidebarTree = (new BookContents($chapter->book))->getTree(); @@ -103,7 +103,7 @@ class ChapterController extends Controller */ public function edit(string $bookSlug, string $chapterSlug) { - $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-update', $chapter); $this->setPageTitle(trans('entities.chapters_edit_named', ['chapterName' => $chapter->getShortName()])); @@ -125,7 +125,7 @@ class ChapterController extends Controller 'default_template_id' => ['nullable', 'integer'], ]); - $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-update', $chapter); $this->chapterRepo->update($chapter, $validated); @@ -140,7 +140,7 @@ class ChapterController extends Controller */ public function showDelete(string $bookSlug, string $chapterSlug) { - $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-delete', $chapter); $this->setPageTitle(trans('entities.chapters_delete_named', ['chapterName' => $chapter->getShortName()])); @@ -156,7 +156,7 @@ class ChapterController extends Controller */ public function destroy(string $bookSlug, string $chapterSlug) { - $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-delete', $chapter); $this->chapterRepo->destroy($chapter); @@ -171,7 +171,7 @@ class ChapterController extends Controller */ public function showMove(string $bookSlug, string $chapterSlug) { - $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $this->setPageTitle(trans('entities.chapters_move_named', ['chapterName' => $chapter->getShortName()])); $this->checkOwnablePermission('chapter-update', $chapter); $this->checkOwnablePermission('chapter-delete', $chapter); @@ -189,7 +189,7 @@ class ChapterController extends Controller */ public function move(Request $request, string $bookSlug, string $chapterSlug) { - $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-update', $chapter); $this->checkOwnablePermission('chapter-delete', $chapter); @@ -218,7 +218,7 @@ class ChapterController extends Controller */ public function showCopy(string $bookSlug, string $chapterSlug) { - $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-view', $chapter); session()->flashInput(['name' => $chapter->name]); @@ -237,7 +237,7 @@ class ChapterController extends Controller */ public function copy(Request $request, Cloner $cloner, string $bookSlug, string $chapterSlug) { - $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-view', $chapter); $entitySelection = $request->get('entity_selection') ?: null; @@ -263,7 +263,7 @@ class ChapterController extends Controller */ public function convertToBook(HierarchyTransformer $transformer, string $bookSlug, string $chapterSlug) { - $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-update', $chapter); $this->checkOwnablePermission('chapter-delete', $chapter); $this->checkPermission('book-create-all'); diff --git a/app/Entities/Controllers/ChapterExportController.php b/app/Entities/Controllers/ChapterExportController.php index 0e071fc82..ead601ab4 100644 --- a/app/Entities/Controllers/ChapterExportController.php +++ b/app/Entities/Controllers/ChapterExportController.php @@ -25,7 +25,7 @@ class ChapterExportController extends Controller */ public function pdf(string $bookSlug, string $chapterSlug) { - $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $pdfContent = $this->exportFormatter->chapterToPdf($chapter); return $this->download()->directly($pdfContent, $chapterSlug . '.pdf'); @@ -39,7 +39,7 @@ class ChapterExportController extends Controller */ public function html(string $bookSlug, string $chapterSlug) { - $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $containedHtml = $this->exportFormatter->chapterToContainedHtml($chapter); return $this->download()->directly($containedHtml, $chapterSlug . '.html'); @@ -52,7 +52,7 @@ class ChapterExportController extends Controller */ public function plainText(string $bookSlug, string $chapterSlug) { - $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $chapterText = $this->exportFormatter->chapterToPlainText($chapter); return $this->download()->directly($chapterText, $chapterSlug . '.txt'); @@ -65,7 +65,7 @@ class ChapterExportController extends Controller */ public function markdown(string $bookSlug, string $chapterSlug) { - $chapter = $this->queries->findVisibleBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $chapterText = $this->exportFormatter->chapterToMarkdown($chapter); return $this->download()->directly($chapterText, $chapterSlug . '.md'); diff --git a/app/Entities/Controllers/PageApiController.php b/app/Entities/Controllers/PageApiController.php index d2947f1bb..6e3880aed 100644 --- a/app/Entities/Controllers/PageApiController.php +++ b/app/Entities/Controllers/PageApiController.php @@ -5,6 +5,7 @@ namespace BookStack\Entities\Controllers; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Repos\PageRepo; use BookStack\Exceptions\PermissionsException; use BookStack\Http\ApiController; @@ -35,7 +36,8 @@ class PageApiController extends ApiController ]; public function __construct( - protected PageRepo $pageRepo + protected PageRepo $pageRepo, + protected PageQueries $queries, ) { } @@ -97,7 +99,7 @@ class PageApiController extends ApiController */ public function read(string $id) { - $page = $this->pageRepo->getById($id, []); + $page = $this->queries->findVisibleByIdOrFail($id); return response()->json($page->forJsonDisplay()); } @@ -113,7 +115,7 @@ class PageApiController extends ApiController { $requestData = $this->validate($request, $this->rules['update']); - $page = $this->pageRepo->getById($id, []); + $page = $this->queries->findVisibleByIdOrFail($id); $this->checkOwnablePermission('page-update', $page); $parent = null; @@ -148,7 +150,7 @@ class PageApiController extends ApiController */ public function delete(string $id) { - $page = $this->pageRepo->getById($id, []); + $page = $this->queries->findVisibleByIdOrFail($id); $this->checkOwnablePermission('page-delete', $page); $this->pageRepo->destroy($page); diff --git a/app/Entities/Controllers/PageController.php b/app/Entities/Controllers/PageController.php index f2cd729c6..3a5bdbd0b 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -31,7 +31,7 @@ class PageController extends Controller { public function __construct( protected PageRepo $pageRepo, - protected PageQueries $pageQueries, + protected PageQueries $queries, protected EntityQueries $entityQueries, protected ReferenceFetcher $referenceFetcher ) { @@ -44,7 +44,12 @@ class PageController extends Controller */ public function create(string $bookSlug, string $chapterSlug = null) { - $parent = $this->pageRepo->getParentFromSlugs($bookSlug, $chapterSlug); + if ($chapterSlug) { + $parent = $this->entityQueries->chapters->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); + } else { + $parent = $this->entityQueries->books->findVisibleBySlugOrFail($bookSlug); + } + $this->checkOwnablePermission('page-create', $parent); // Redirect to draft edit screen if signed in @@ -71,7 +76,12 @@ class PageController extends Controller 'name' => ['required', 'string', 'max:255'], ]); - $parent = $this->pageRepo->getParentFromSlugs($bookSlug, $chapterSlug); + if ($chapterSlug) { + $parent = $this->entityQueries->chapters->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); + } else { + $parent = $this->entityQueries->books->findVisibleBySlugOrFail($bookSlug); + } + $this->checkOwnablePermission('page-create', $parent); $page = $this->pageRepo->getNewDraftPage($parent); @@ -89,10 +99,10 @@ class PageController extends Controller */ public function editDraft(Request $request, string $bookSlug, int $pageId) { - $draft = $this->pageRepo->getById($pageId); + $draft = $this->queries->findVisibleByIdOrFail($pageId); $this->checkOwnablePermission('page-create', $draft->getParent()); - $editorData = new PageEditorData($draft, $this->pageRepo, $request->query('editor', '')); + $editorData = new PageEditorData($draft, $this->entityQueries, $request->query('editor', '')); $this->setPageTitle(trans('entities.pages_edit_draft')); return view('pages.edit', $editorData->getViewData()); @@ -109,7 +119,7 @@ class PageController extends Controller $this->validate($request, [ 'name' => ['required', 'string', 'max:255'], ]); - $draftPage = $this->pageRepo->getById($pageId); + $draftPage = $this->queries->findVisibleByIdOrFail($pageId); $this->checkOwnablePermission('page-create', $draftPage->getParent()); $page = $this->pageRepo->publishDraft($draftPage, $request->all()); @@ -126,11 +136,12 @@ class PageController extends Controller public function show(string $bookSlug, string $pageSlug) { try { - $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); } catch (NotFoundException $e) { - $page = $this->pageRepo->getByOldSlug($bookSlug, $pageSlug); + $revision = $this->entityQueries->revisions->findLatestVersionBySlugs($bookSlug, $pageSlug); + $page = $revision->page ?? null; - if ($page === null) { + if (is_null($page)) { throw $e; } @@ -171,7 +182,7 @@ class PageController extends Controller */ public function getPageAjax(int $pageId) { - $page = $this->pageRepo->getById($pageId); + $page = $this->queries->findVisibleByIdOrFail($pageId); $page->setHidden(array_diff($page->getHidden(), ['html', 'markdown'])); $page->makeHidden(['book']); @@ -185,10 +196,10 @@ class PageController extends Controller */ public function edit(Request $request, string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $this->checkOwnablePermission('page-update', $page); - $editorData = new PageEditorData($page, $this->pageRepo, $request->query('editor', '')); + $editorData = new PageEditorData($page, $this->entityQueries, $request->query('editor', '')); if ($editorData->getWarnings()) { $this->showWarningNotification(implode("\n", $editorData->getWarnings())); } @@ -209,7 +220,7 @@ class PageController extends Controller $this->validate($request, [ 'name' => ['required', 'string', 'max:255'], ]); - $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $this->checkOwnablePermission('page-update', $page); $this->pageRepo->update($page, $request->all()); @@ -224,7 +235,7 @@ class PageController extends Controller */ public function saveDraft(Request $request, int $pageId) { - $page = $this->pageRepo->getById($pageId); + $page = $this->queries->findVisibleByIdOrFail($pageId); $this->checkOwnablePermission('page-update', $page); if (!$this->isSignedIn()) { @@ -249,7 +260,7 @@ class PageController extends Controller */ public function redirectFromLink(int $pageId) { - $page = $this->pageRepo->getById($pageId); + $page = $this->queries->findVisibleByIdOrFail($pageId); return redirect($page->getUrl()); } @@ -261,7 +272,7 @@ class PageController extends Controller */ public function showDelete(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $this->checkOwnablePermission('page-delete', $page); $this->setPageTitle(trans('entities.pages_delete_named', ['pageName' => $page->getShortName()])); $usedAsTemplate = @@ -283,7 +294,7 @@ class PageController extends Controller */ public function showDeleteDraft(string $bookSlug, int $pageId) { - $page = $this->pageRepo->getById($pageId); + $page = $this->queries->findVisibleByIdOrFail($pageId); $this->checkOwnablePermission('page-update', $page); $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName' => $page->getShortName()])); $usedAsTemplate = @@ -306,7 +317,7 @@ class PageController extends Controller */ public function destroy(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $this->checkOwnablePermission('page-delete', $page); $parent = $page->getParent(); @@ -323,7 +334,7 @@ class PageController extends Controller */ public function destroyDraft(string $bookSlug, int $pageId) { - $page = $this->pageRepo->getById($pageId); + $page = $this->queries->findVisibleByIdOrFail($pageId); $book = $page->book; $chapter = $page->chapter; $this->checkOwnablePermission('page-update', $page); @@ -370,7 +381,7 @@ class PageController extends Controller */ public function showMove(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $this->checkOwnablePermission('page-update', $page); $this->checkOwnablePermission('page-delete', $page); @@ -388,7 +399,7 @@ class PageController extends Controller */ public function move(Request $request, string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $this->checkOwnablePermission('page-update', $page); $this->checkOwnablePermission('page-delete', $page); @@ -417,7 +428,7 @@ class PageController extends Controller */ public function showCopy(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $this->checkOwnablePermission('page-view', $page); session()->flashInput(['name' => $page->name]); @@ -435,7 +446,7 @@ class PageController extends Controller */ public function copy(Request $request, Cloner $cloner, string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $this->checkOwnablePermission('page-view', $page); $entitySelection = $request->get('entity_selection') ?: null; diff --git a/app/Entities/Controllers/PageExportController.php b/app/Entities/Controllers/PageExportController.php index 31862c8ac..be97f1930 100644 --- a/app/Entities/Controllers/PageExportController.php +++ b/app/Entities/Controllers/PageExportController.php @@ -2,7 +2,7 @@ namespace BookStack\Entities\Controllers; -use BookStack\Entities\Repos\PageRepo; +use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Tools\ExportFormatter; use BookStack\Entities\Tools\PageContent; use BookStack\Exceptions\NotFoundException; @@ -11,16 +11,10 @@ use Throwable; class PageExportController extends Controller { - protected $pageRepo; - protected $exportFormatter; - - /** - * PageExportController constructor. - */ - public function __construct(PageRepo $pageRepo, ExportFormatter $exportFormatter) - { - $this->pageRepo = $pageRepo; - $this->exportFormatter = $exportFormatter; + public function __construct( + protected PageQueries $queries, + protected ExportFormatter $exportFormatter, + ) { $this->middleware('can:content-export'); } @@ -33,7 +27,7 @@ class PageExportController extends Controller */ public function pdf(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $page->html = (new PageContent($page))->render(); $pdfContent = $this->exportFormatter->pageToPdf($page); @@ -48,7 +42,7 @@ class PageExportController extends Controller */ public function html(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $page->html = (new PageContent($page))->render(); $containedHtml = $this->exportFormatter->pageToContainedHtml($page); @@ -62,7 +56,7 @@ class PageExportController extends Controller */ public function plainText(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $pageText = $this->exportFormatter->pageToPlainText($page); return $this->download()->directly($pageText, $pageSlug . '.txt'); @@ -75,7 +69,7 @@ class PageExportController extends Controller */ public function markdown(string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $pageText = $this->exportFormatter->pageToMarkdown($page); return $this->download()->directly($pageText, $pageSlug . '.md'); diff --git a/app/Entities/Controllers/PageRevisionController.php b/app/Entities/Controllers/PageRevisionController.php index a3190a0fc..232d40668 100644 --- a/app/Entities/Controllers/PageRevisionController.php +++ b/app/Entities/Controllers/PageRevisionController.php @@ -4,6 +4,7 @@ namespace BookStack\Entities\Controllers; use BookStack\Activity\ActivityType; use BookStack\Entities\Models\PageRevision; +use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Repos\PageRepo; use BookStack\Entities\Repos\RevisionRepo; use BookStack\Entities\Tools\PageContent; @@ -18,6 +19,7 @@ class PageRevisionController extends Controller { public function __construct( protected PageRepo $pageRepo, + protected PageQueries $pageQueries, protected RevisionRepo $revisionRepo, ) { } @@ -29,7 +31,7 @@ class PageRevisionController extends Controller */ public function index(Request $request, string $bookSlug, string $pageSlug) { - $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $listOptions = SimpleListOptions::fromRequest($request, 'page_revisions', true)->withSortOptions([ 'id' => trans('entities.pages_revisions_sort_number') ]); @@ -60,7 +62,7 @@ class PageRevisionController extends Controller */ public function show(string $bookSlug, string $pageSlug, int $revisionId) { - $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); /** @var ?PageRevision $revision */ $revision = $page->revisions()->where('id', '=', $revisionId)->first(); if ($revision === null) { @@ -89,7 +91,7 @@ class PageRevisionController extends Controller */ public function changes(string $bookSlug, string $pageSlug, int $revisionId) { - $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); /** @var ?PageRevision $revision */ $revision = $page->revisions()->where('id', '=', $revisionId)->first(); if ($revision === null) { @@ -121,7 +123,7 @@ class PageRevisionController extends Controller */ public function restore(string $bookSlug, string $pageSlug, int $revisionId) { - $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $this->checkOwnablePermission('page-update', $page); $page = $this->pageRepo->restoreRevision($page, $revisionId); @@ -136,7 +138,7 @@ class PageRevisionController extends Controller */ public function destroy(string $bookSlug, string $pageSlug, int $revId) { - $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug); + $page = $this->pageQueries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $this->checkOwnablePermission('page-delete', $page); $revision = $page->revisions()->where('id', '=', $revId)->first(); @@ -162,7 +164,7 @@ class PageRevisionController extends Controller */ public function destroyUserDraft(string $pageId) { - $page = $this->pageRepo->getById($pageId); + $page = $this->pageQueries->findVisibleByIdOrFail($pageId); $this->revisionRepo->deleteDraftsForCurrentUser($page); return response('', 200); diff --git a/app/Entities/Controllers/PageTemplateController.php b/app/Entities/Controllers/PageTemplateController.php index e4e7b5680..c0b972148 100644 --- a/app/Entities/Controllers/PageTemplateController.php +++ b/app/Entities/Controllers/PageTemplateController.php @@ -2,6 +2,7 @@ namespace BookStack\Entities\Controllers; +use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Repos\PageRepo; use BookStack\Exceptions\NotFoundException; use BookStack\Http\Controller; @@ -9,14 +10,10 @@ use Illuminate\Http\Request; class PageTemplateController extends Controller { - protected $pageRepo; - - /** - * PageTemplateController constructor. - */ - public function __construct(PageRepo $pageRepo) - { - $this->pageRepo = $pageRepo; + public function __construct( + protected PageRepo $pageRepo, + protected PageQueries $pageQueries, + ) { } /** @@ -26,7 +23,19 @@ class PageTemplateController extends Controller { $page = $request->get('page', 1); $search = $request->get('search', ''); - $templates = $this->pageRepo->getTemplates(10, $page, $search); + $count = 10; + + $query = $this->pageQueries->visibleTemplates() + ->orderBy('name', 'asc') + ->skip(($page - 1) * $count) + ->take($count); + + if ($search) { + $query->where('name', 'like', '%' . $search . '%'); + } + + $templates = $query->paginate($count, ['*'], 'page', $page); + $templates->withPath('/templates'); if ($search) { $templates->appends(['search' => $search]); @@ -44,7 +53,7 @@ class PageTemplateController extends Controller */ public function get(int $templateId) { - $page = $this->pageRepo->getById($templateId); + $page = $this->pageQueries->findVisibleByIdOrFail($templateId); if (!$page->template) { throw new NotFoundException(); diff --git a/app/Entities/Queries/BookQueries.php b/app/Entities/Queries/BookQueries.php index 5e85523cf..3d7474b3d 100644 --- a/app/Entities/Queries/BookQueries.php +++ b/app/Entities/Queries/BookQueries.php @@ -18,7 +18,7 @@ class BookQueries implements ProvidesEntityQueries return $this->start()->scopes('visible')->find($id); } - public function findVisibleBySlug(string $slug): Book + public function findVisibleBySlugOrFail(string $slug): Book { /** @var ?Book $book */ $book = $this->start() diff --git a/app/Entities/Queries/BookshelfQueries.php b/app/Entities/Queries/BookshelfQueries.php index 52e123087..d61607e7a 100644 --- a/app/Entities/Queries/BookshelfQueries.php +++ b/app/Entities/Queries/BookshelfQueries.php @@ -18,7 +18,7 @@ class BookshelfQueries implements ProvidesEntityQueries return $this->start()->scopes('visible')->find($id); } - public function findVisibleBySlug(string $slug): Bookshelf + public function findVisibleBySlugOrFail(string $slug): Bookshelf { /** @var ?Bookshelf $shelf */ $shelf = $this->start() diff --git a/app/Entities/Queries/ChapterQueries.php b/app/Entities/Queries/ChapterQueries.php index dcfc4aad3..200514932 100644 --- a/app/Entities/Queries/ChapterQueries.php +++ b/app/Entities/Queries/ChapterQueries.php @@ -24,7 +24,7 @@ class ChapterQueries implements ProvidesEntityQueries return $this->start()->scopes('visible')->find($id); } - public function findVisibleBySlugs(string $bookSlug, string $chapterSlug): Chapter + public function findVisibleBySlugsOrFail(string $bookSlug, string $chapterSlug): Chapter { /** @var ?Chapter $chapter */ $chapter = $this->start()->with('book') @@ -34,7 +34,7 @@ class ChapterQueries implements ProvidesEntityQueries ->where('slug', '=', $chapterSlug) ->first(); - if ($chapter === null) { + if (is_null($chapter)) { throw new NotFoundException(trans('errors.chapter_not_found')); } diff --git a/app/Entities/Queries/EntityQueries.php b/app/Entities/Queries/EntityQueries.php index 39a21c913..31e5b913a 100644 --- a/app/Entities/Queries/EntityQueries.php +++ b/app/Entities/Queries/EntityQueries.php @@ -11,6 +11,7 @@ class EntityQueries public BookQueries $books, public ChapterQueries $chapters, public PageQueries $pages, + public PageRevisionQueries $revisions, ) { } diff --git a/app/Entities/Queries/PageQueries.php b/app/Entities/Queries/PageQueries.php index f1991626f..1640dc2db 100644 --- a/app/Entities/Queries/PageQueries.php +++ b/app/Entities/Queries/PageQueries.php @@ -3,6 +3,7 @@ namespace BookStack\Entities\Queries; use BookStack\Entities\Models\Page; +use BookStack\Exceptions\NotFoundException; use Illuminate\Database\Eloquent\Builder; class PageQueries implements ProvidesEntityQueries @@ -17,6 +18,34 @@ class PageQueries implements ProvidesEntityQueries return $this->start()->scopes('visible')->find($id); } + public function findVisibleByIdOrFail(int $id): Page + { + $page = $this->findVisibleById($id); + + if (is_null($page)) { + throw new NotFoundException(trans('errors.page_not_found')); + } + + return $page; + } + + public function findVisibleBySlugsOrFail(string $bookSlug, string $pageSlug): Page + { + /** @var ?Page $page */ + $page = $this->start()->with('book') + ->whereHas('book', function (Builder $query) use ($bookSlug) { + $query->where('slug', '=', $bookSlug); + }) + ->where('slug', '=', $pageSlug) + ->first(); + + if (is_null($page)) { + throw new NotFoundException(trans('errors.chapter_not_found')); + } + + return $page; + } + public function visibleForList(): Builder { return $this->start() @@ -33,4 +62,10 @@ class PageQueries implements ProvidesEntityQueries ->where('draft', '=', true) ->where('created_by', '=', user()->id); } + + public function visibleTemplates(): Builder + { + return $this->visibleForList() + ->where('template', '=', true); + } } diff --git a/app/Entities/Queries/PageRevisionQueries.php b/app/Entities/Queries/PageRevisionQueries.php new file mode 100644 index 000000000..2dcd428f5 --- /dev/null +++ b/app/Entities/Queries/PageRevisionQueries.php @@ -0,0 +1,41 @@ +whereHas('page', function (Builder $query) { + $query->scopes('visible'); + }) + ->where('slug', '=', $pageSlug) + ->where('type', '=', 'version') + ->where('book_slug', '=', $bookSlug) + ->orderBy('created_at', 'desc') + ->first(); + } + + public function findLatestCurrentUserDraftsForPageId(int $pageId): ?PageRevision + { + return $this->latestCurrentUserDraftsForPageId($pageId)->first(); + } + + public function latestCurrentUserDraftsForPageId(int $pageId): Builder + { + return $this->start() + ->where('created_by', '=', user()->id) + ->where('type', 'update_draft') + ->where('page_id', '=', $pageId) + ->orderBy('created_at', 'desc'); + } +} diff --git a/app/Entities/Queries/ProvidesEntityQueries.php b/app/Entities/Queries/ProvidesEntityQueries.php index 5c37b02e4..ea83d6cdd 100644 --- a/app/Entities/Queries/ProvidesEntityQueries.php +++ b/app/Entities/Queries/ProvidesEntityQueries.php @@ -2,9 +2,18 @@ namespace BookStack\Entities\Queries; -use BookStack\Entities\Models\Entity; +use BookStack\App\Model; use Illuminate\Database\Eloquent\Builder; +/** + * Interface for our classes which provide common queries for our + * entity objects. Ideally all queries for entities should run through + * these classes. + * Any added methods should return a builder instances to allow extension + * via building on the query, unless the method starts with 'find' + * in which case an entity object should be returned. + * (nullable unless it's a *OrFail method). + */ interface ProvidesEntityQueries { public function start(): Builder; diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index 929de528d..74aed072a 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -14,13 +14,11 @@ use BookStack\Entities\Tools\PageContent; use BookStack\Entities\Tools\PageEditorData; use BookStack\Entities\Tools\TrashCan; use BookStack\Exceptions\MoveOperationException; -use BookStack\Exceptions\NotFoundException; use BookStack\Exceptions\PermissionsException; use BookStack\Facades\Activity; use BookStack\References\ReferenceStore; use BookStack\References\ReferenceUpdater; use Exception; -use Illuminate\Pagination\LengthAwarePaginator; class PageRepo { @@ -33,91 +31,6 @@ class PageRepo ) { } - /** - * Get a page by ID. - * - * @throws NotFoundException - */ - public function getById(int $id, array $relations = ['book']): Page - { - /** @var Page $page */ - $page = Page::visible()->with($relations)->find($id); - - if (!$page) { - throw new NotFoundException(trans('errors.page_not_found')); - } - - return $page; - } - - /** - * Get a page its book and own slug. - * - * @throws NotFoundException - */ - public function getBySlug(string $bookSlug, string $pageSlug): Page - { - $page = Page::visible()->whereSlugs($bookSlug, $pageSlug)->first(); - - if (!$page) { - throw new NotFoundException(trans('errors.page_not_found')); - } - - return $page; - } - - /** - * Get a page by its old slug but checking the revisions table - * for the last revision that matched the given page and book slug. - */ - public function getByOldSlug(string $bookSlug, string $pageSlug): ?Page - { - $revision = $this->revisionRepo->getBySlugs($bookSlug, $pageSlug); - - return $revision->page ?? null; - } - - /** - * Get pages that have been marked as a template. - */ - public function getTemplates(int $count = 10, int $page = 1, string $search = ''): LengthAwarePaginator - { - $query = Page::visible() - ->where('template', '=', true) - ->orderBy('name', 'asc') - ->skip(($page - 1) * $count) - ->take($count); - - if ($search) { - $query->where('name', 'like', '%' . $search . '%'); - } - - $paginator = $query->paginate($count, ['*'], 'page', $page); - $paginator->withPath('/templates'); - - return $paginator; - } - - /** - * Get a parent item via slugs. - */ - public function getParentFromSlugs(string $bookSlug, string $chapterSlug = null): Entity - { - if ($chapterSlug !== null) { - return Chapter::visible()->whereSlugs($bookSlug, $chapterSlug)->firstOrFail(); - } - - return Book::visible()->where('slug', '=', $bookSlug)->firstOrFail(); - } - - /** - * Get the draft copy of the given page for the current user. - */ - public function getUserDraft(Page $page): ?PageRevision - { - return $this->revisionRepo->getLatestDraftForCurrentUser($page); - } - /** * Get a new draft page belonging to the given parent entity. */ diff --git a/app/Entities/Tools/PageEditorData.php b/app/Entities/Tools/PageEditorData.php index 3c7c9e2ea..20bf19eb2 100644 --- a/app/Entities/Tools/PageEditorData.php +++ b/app/Entities/Tools/PageEditorData.php @@ -4,7 +4,7 @@ namespace BookStack\Entities\Tools; use BookStack\Activity\Tools\CommentTree; use BookStack\Entities\Models\Page; -use BookStack\Entities\Repos\PageRepo; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Entities\Tools\Markdown\HtmlToMarkdown; use BookStack\Entities\Tools\Markdown\MarkdownToHtml; @@ -15,7 +15,7 @@ class PageEditorData public function __construct( protected Page $page, - protected PageRepo $pageRepo, + protected EntityQueries $queries, protected string $requestedEditor ) { $this->viewData = $this->build(); @@ -35,7 +35,11 @@ class PageEditorData { $page = clone $this->page; $isDraft = boolval($this->page->draft); - $templates = $this->pageRepo->getTemplates(10); + $templates = $this->queries->pages->visibleTemplates() + ->orderBy('name', 'asc') + ->take(10) + ->get(); + $draftsEnabled = auth()->check(); $isDraftRevision = false; @@ -47,8 +51,8 @@ class PageEditorData } // Check for a current draft version for this user - $userDraft = $this->pageRepo->getUserDraft($page); - if ($userDraft !== null) { + $userDraft = $this->queries->revisions->findLatestCurrentUserDraftsForPageId($page->id)->first(); + if (!is_null($userDraft)) { $page->forceFill($userDraft->only(['name', 'html', 'markdown'])); $isDraftRevision = true; $this->warnings[] = $editActivity->getEditingActiveDraftMessage($userDraft); diff --git a/app/Uploads/Controllers/AttachmentController.php b/app/Uploads/Controllers/AttachmentController.php index e61c10338..809cdfa58 100644 --- a/app/Uploads/Controllers/AttachmentController.php +++ b/app/Uploads/Controllers/AttachmentController.php @@ -2,6 +2,7 @@ namespace BookStack\Uploads\Controllers; +use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Repos\PageRepo; use BookStack\Exceptions\FileUploadException; use BookStack\Exceptions\NotFoundException; @@ -18,6 +19,7 @@ class AttachmentController extends Controller { public function __construct( protected AttachmentService $attachmentService, + protected PageQueries $pageQueries, protected PageRepo $pageRepo ) { } @@ -36,7 +38,7 @@ class AttachmentController extends Controller ]); $pageId = $request->get('uploaded_to'); - $page = $this->pageRepo->getById($pageId); + $page = $this->pageQueries->findVisibleByIdOrFail($pageId); $this->checkPermission('attachment-create-all'); $this->checkOwnablePermission('page-update', $page); @@ -152,7 +154,7 @@ class AttachmentController extends Controller ]), 422); } - $page = $this->pageRepo->getById($pageId); + $page = $this->pageQueries->findVisibleByIdOrFail($pageId); $this->checkPermission('attachment-create-all'); $this->checkOwnablePermission('page-update', $page); @@ -173,7 +175,7 @@ class AttachmentController extends Controller */ public function listForPage(int $pageId) { - $page = $this->pageRepo->getById($pageId); + $page = $this->pageQueries->findVisibleByIdOrFail($pageId); $this->checkOwnablePermission('page-view', $page); return view('attachments.manager-list', [ @@ -192,7 +194,7 @@ class AttachmentController extends Controller $this->validate($request, [ 'order' => ['required', 'array'], ]); - $page = $this->pageRepo->getById($pageId); + $page = $this->pageQueries->findVisibleByIdOrFail($pageId); $this->checkOwnablePermission('page-update', $page); $attachmentOrder = $request->get('order'); @@ -213,7 +215,7 @@ class AttachmentController extends Controller $attachment = Attachment::query()->findOrFail($attachmentId); try { - $page = $this->pageRepo->getById($attachment->uploaded_to); + $page = $this->pageQueries->findVisibleByIdOrFail($attachment->uploaded_to); } catch (NotFoundException $exception) { throw new NotFoundException(trans('errors.attachment_not_found')); } From c95f4ca40fecf0584eccb5f89d49b245d4ec7369 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 7 Feb 2024 15:09:16 +0000 Subject: [PATCH 062/124] Queries: Migrated revision repo queries to new class --- app/Entities/Queries/PageRevisionQueries.php | 5 +- .../Queries/ProvidesEntityQueries.php | 2 +- app/Entities/Repos/RevisionRepo.php | 50 +++---------------- 3 files changed, 11 insertions(+), 46 deletions(-) diff --git a/app/Entities/Queries/PageRevisionQueries.php b/app/Entities/Queries/PageRevisionQueries.php index 2dcd428f5..6e017a742 100644 --- a/app/Entities/Queries/PageRevisionQueries.php +++ b/app/Entities/Queries/PageRevisionQueries.php @@ -27,7 +27,10 @@ class PageRevisionQueries public function findLatestCurrentUserDraftsForPageId(int $pageId): ?PageRevision { - return $this->latestCurrentUserDraftsForPageId($pageId)->first(); + /** @var ?PageRevision $revision */ + $revision = $this->latestCurrentUserDraftsForPageId($pageId)->first(); + + return $revision; } public function latestCurrentUserDraftsForPageId(int $pageId): Builder diff --git a/app/Entities/Queries/ProvidesEntityQueries.php b/app/Entities/Queries/ProvidesEntityQueries.php index ea83d6cdd..103352244 100644 --- a/app/Entities/Queries/ProvidesEntityQueries.php +++ b/app/Entities/Queries/ProvidesEntityQueries.php @@ -2,7 +2,7 @@ namespace BookStack\Entities\Queries; -use BookStack\App\Model; +use BookStack\Entities\Models\Entity; use Illuminate\Database\Eloquent\Builder; /** diff --git a/app/Entities/Repos/RevisionRepo.php b/app/Entities/Repos/RevisionRepo.php index 064327ee9..daf55777c 100644 --- a/app/Entities/Repos/RevisionRepo.php +++ b/app/Entities/Repos/RevisionRepo.php @@ -4,39 +4,13 @@ namespace BookStack\Entities\Repos; use BookStack\Entities\Models\Page; use BookStack\Entities\Models\PageRevision; -use Illuminate\Database\Eloquent\Builder; +use BookStack\Entities\Queries\PageRevisionQueries; class RevisionRepo { - /** - * Get a revision by its stored book and page slug values. - */ - public function getBySlugs(string $bookSlug, string $pageSlug): ?PageRevision - { - /** @var ?PageRevision $revision */ - $revision = PageRevision::query() - ->whereHas('page', function (Builder $query) { - $query->scopes('visible'); - }) - ->where('slug', '=', $pageSlug) - ->where('type', '=', 'version') - ->where('book_slug', '=', $bookSlug) - ->orderBy('created_at', 'desc') - ->with('page') - ->first(); - - return $revision; - } - - /** - * Get the latest draft revision, for the given page, belonging to the current user. - */ - public function getLatestDraftForCurrentUser(Page $page): ?PageRevision - { - /** @var ?PageRevision $revision */ - $revision = $this->queryForCurrentUserDraft($page->id)->first(); - - return $revision; + public function __construct( + protected PageRevisionQueries $queries, + ) { } /** @@ -44,7 +18,7 @@ class RevisionRepo */ public function deleteDraftsForCurrentUser(Page $page): void { - $this->queryForCurrentUserDraft($page->id)->delete(); + $this->queries->latestCurrentUserDraftsForPageId($page->id)->delete(); } /** @@ -53,7 +27,7 @@ class RevisionRepo */ public function getNewDraftForCurrentUser(Page $page): PageRevision { - $draft = $this->getLatestDraftForCurrentUser($page); + $draft = $this->queries->findLatestCurrentUserDraftsForPageId($page->id); if ($draft) { return $draft; @@ -116,16 +90,4 @@ class RevisionRepo PageRevision::query()->whereIn('id', $revisionsToDelete->pluck('id'))->delete(); } } - - /** - * Query update draft revisions for the current user. - */ - protected function queryForCurrentUserDraft(int $pageId): Builder - { - return PageRevision::query() - ->where('created_by', '=', user()->id) - ->where('type', 'update_draft') - ->where('page_id', '=', $pageId) - ->orderBy('created_at', 'desc'); - } } From 483410749bdfb03d1e3fb98db82c092583a88449 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 7 Feb 2024 16:37:36 +0000 Subject: [PATCH 063/124] Queries: Updated all app book static query uses --- app/App/HomeController.php | 2 +- .../Controllers/BookApiController.php | 12 +++--- .../Controllers/BookExportApiController.php | 19 +++++---- .../Controllers/BookshelfController.php | 10 +++-- .../Controllers/ChapterApiController.php | 17 ++++---- .../Controllers/PageApiController.php | 15 ++++--- app/Entities/Controllers/PageController.php | 8 ++-- .../Controllers/RecycleBinController.php | 4 +- app/Entities/Models/Book.php | 11 +---- app/Entities/Queries/BookQueries.php | 5 +++ app/Entities/Queries/ChapterQueries.php | 19 ++++++++- app/Entities/Queries/PageQueries.php | 22 ++++++++++ app/Entities/Repos/BookRepo.php | 8 ++-- app/Entities/Repos/BookshelfRepo.php | 11 ++--- app/Entities/Repos/ChapterRepo.php | 6 +-- app/Entities/Repos/PageRepo.php | 8 ++-- app/Entities/Tools/BookContents.php | 38 ++++++++++-------- app/Entities/Tools/Cloner.php | 2 +- app/Entities/Tools/SiblingFetcher.php | 12 ++++-- app/Entities/Tools/TrashCan.php | 12 +++++- app/Permissions/JointPermissionBuilder.php | 13 ++++-- app/Permissions/PermissionsController.php | 40 +++++++++---------- app/References/CrossLinkParser.php | 13 +++--- .../ModelResolvers/BookLinkModelResolver.php | 8 +++- .../BookshelfLinkModelResolver.php | 7 +++- .../ChapterLinkModelResolver.php | 8 +++- .../ModelResolvers/PageLinkModelResolver.php | 8 +++- .../PagePermalinkModelResolver.php | 8 +++- app/References/ReferenceController.php | 12 +++--- app/Search/SearchController.php | 4 +- app/Settings/MaintenanceController.php | 4 +- app/Uploads/ImageService.php | 8 ++-- .../Controllers/UserProfileController.php | 19 ++++++--- app/Users/Queries/UserContentCounts.php | 19 +++++---- .../Queries/UserRecentlyCreatedContent.php | 18 +++++---- tests/Entity/BookTest.php | 6 +-- tests/Entity/EntitySearchTest.php | 4 +- 37 files changed, 278 insertions(+), 162 deletions(-) diff --git a/app/App/HomeController.php b/app/App/HomeController.php index f5a3c7ed6..6a4cb0176 100644 --- a/app/App/HomeController.php +++ b/app/App/HomeController.php @@ -40,7 +40,7 @@ class HomeController extends Controller $recentFactor = count($draftPages) > 0 ? 0.5 : 1; $recents = $this->isSignedIn() ? (new RecentlyViewed())->run(12 * $recentFactor, 1) - : Book::visible()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get(); + : $this->queries->books->visibleForList()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get(); $favourites = (new TopFavourites())->run(6); $recentlyUpdatedPages = $this->queries->pages->visibleForList() ->where('draft', false) diff --git a/app/Entities/Controllers/BookApiController.php b/app/Entities/Controllers/BookApiController.php index aa21aea47..955bd707b 100644 --- a/app/Entities/Controllers/BookApiController.php +++ b/app/Entities/Controllers/BookApiController.php @@ -6,6 +6,7 @@ use BookStack\Api\ApiEntityListFormatter; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; +use BookStack\Entities\Queries\BookQueries; use BookStack\Entities\Repos\BookRepo; use BookStack\Entities\Tools\BookContents; use BookStack\Http\ApiController; @@ -15,7 +16,8 @@ use Illuminate\Validation\ValidationException; class BookApiController extends ApiController { public function __construct( - protected BookRepo $bookRepo + protected BookRepo $bookRepo, + protected BookQueries $queries, ) { } @@ -24,7 +26,7 @@ class BookApiController extends ApiController */ public function list() { - $books = Book::visible(); + $books = $this->queries->visibleForList(); return $this->apiListingResponse($books, [ 'id', 'name', 'slug', 'description', 'created_at', 'updated_at', 'created_by', 'updated_by', 'owned_by', @@ -56,7 +58,7 @@ class BookApiController extends ApiController */ public function read(string $id) { - $book = Book::visible()->findOrFail($id); + $book = $this->queries->findVisibleByIdOrFail(intval($id)); $book = $this->forJsonDisplay($book); $book->load(['createdBy', 'updatedBy', 'ownedBy']); @@ -83,7 +85,7 @@ class BookApiController extends ApiController */ public function update(Request $request, string $id) { - $book = Book::visible()->findOrFail($id); + $book = $this->queries->findVisibleByIdOrFail(intval($id)); $this->checkOwnablePermission('book-update', $book); $requestData = $this->validate($request, $this->rules()['update']); @@ -100,7 +102,7 @@ class BookApiController extends ApiController */ public function delete(string $id) { - $book = Book::visible()->findOrFail($id); + $book = $this->queries->findVisibleByIdOrFail(intval($id)); $this->checkOwnablePermission('book-delete', $book); $this->bookRepo->destroy($book); diff --git a/app/Entities/Controllers/BookExportApiController.php b/app/Entities/Controllers/BookExportApiController.php index 5b6826c19..1161ddb88 100644 --- a/app/Entities/Controllers/BookExportApiController.php +++ b/app/Entities/Controllers/BookExportApiController.php @@ -2,18 +2,17 @@ namespace BookStack\Entities\Controllers; -use BookStack\Entities\Models\Book; +use BookStack\Entities\Queries\BookQueries; use BookStack\Entities\Tools\ExportFormatter; use BookStack\Http\ApiController; use Throwable; class BookExportApiController extends ApiController { - protected $exportFormatter; - - public function __construct(ExportFormatter $exportFormatter) - { - $this->exportFormatter = $exportFormatter; + public function __construct( + protected ExportFormatter $exportFormatter, + protected BookQueries $queries, + ) { $this->middleware('can:content-export'); } @@ -24,7 +23,7 @@ class BookExportApiController extends ApiController */ public function exportPdf(int $id) { - $book = Book::visible()->findOrFail($id); + $book = $this->queries->findVisibleByIdOrFail($id); $pdfContent = $this->exportFormatter->bookToPdf($book); return $this->download()->directly($pdfContent, $book->slug . '.pdf'); @@ -37,7 +36,7 @@ class BookExportApiController extends ApiController */ public function exportHtml(int $id) { - $book = Book::visible()->findOrFail($id); + $book = $this->queries->findVisibleByIdOrFail($id); $htmlContent = $this->exportFormatter->bookToContainedHtml($book); return $this->download()->directly($htmlContent, $book->slug . '.html'); @@ -48,7 +47,7 @@ class BookExportApiController extends ApiController */ public function exportPlainText(int $id) { - $book = Book::visible()->findOrFail($id); + $book = $this->queries->findVisibleByIdOrFail($id); $textContent = $this->exportFormatter->bookToPlainText($book); return $this->download()->directly($textContent, $book->slug . '.txt'); @@ -59,7 +58,7 @@ class BookExportApiController extends ApiController */ public function exportMarkdown(int $id) { - $book = Book::visible()->findOrFail($id); + $book = $this->queries->findVisibleByIdOrFail($id); $markdown = $this->exportFormatter->bookToMarkdown($book); return $this->download()->directly($markdown, $book->slug . '.md'); diff --git a/app/Entities/Controllers/BookshelfController.php b/app/Entities/Controllers/BookshelfController.php index 3118325da..6cedd23e7 100644 --- a/app/Entities/Controllers/BookshelfController.php +++ b/app/Entities/Controllers/BookshelfController.php @@ -4,7 +4,7 @@ namespace BookStack\Entities\Controllers; use BookStack\Activity\ActivityQueries; use BookStack\Activity\Models\View; -use BookStack\Entities\Models\Book; +use BookStack\Entities\Queries\BookQueries; use BookStack\Entities\Queries\BookshelfQueries; use BookStack\Entities\Repos\BookshelfRepo; use BookStack\Entities\Tools\ShelfContext; @@ -22,6 +22,7 @@ class BookshelfController extends Controller public function __construct( protected BookshelfRepo $shelfRepo, protected BookshelfQueries $queries, + protected BookQueries $bookQueries, protected ShelfContext $shelfContext, protected ReferenceFetcher $referenceFetcher, ) { @@ -68,7 +69,7 @@ class BookshelfController extends Controller public function create() { $this->checkPermission('bookshelf-create-all'); - $books = Book::visible()->orderBy('name')->get(['name', 'id', 'slug', 'created_at', 'updated_at']); + $books = $this->bookQueries->visibleForList()->orderBy('name')->get(['name', 'id', 'slug', 'created_at', 'updated_at']); $this->setPageTitle(trans('entities.shelves_create')); return view('shelves.create', ['books' => $books]); @@ -145,7 +146,10 @@ class BookshelfController extends Controller $this->checkOwnablePermission('bookshelf-update', $shelf); $shelfBookIds = $shelf->books()->get(['id'])->pluck('id'); - $books = Book::visible()->whereNotIn('id', $shelfBookIds)->orderBy('name')->get(['name', 'id', 'slug', 'created_at', 'updated_at']); + $books = $this->bookQueries->visibleForList() + ->whereNotIn('id', $shelfBookIds) + ->orderBy('name') + ->get(['name', 'id', 'slug', 'created_at', 'updated_at']); $this->setPageTitle(trans('entities.shelves_edit_named', ['name' => $shelf->getShortName()])); diff --git a/app/Entities/Controllers/ChapterApiController.php b/app/Entities/Controllers/ChapterApiController.php index 3fbe85222..fb484b85d 100644 --- a/app/Entities/Controllers/ChapterApiController.php +++ b/app/Entities/Controllers/ChapterApiController.php @@ -2,8 +2,9 @@ namespace BookStack\Entities\Controllers; -use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Chapter; +use BookStack\Entities\Queries\BookQueries; +use BookStack\Entities\Queries\ChapterQueries; use BookStack\Entities\Repos\ChapterRepo; use BookStack\Exceptions\PermissionsException; use BookStack\Http\ApiController; @@ -35,7 +36,9 @@ class ChapterApiController extends ApiController ]; public function __construct( - protected ChapterRepo $chapterRepo + protected ChapterRepo $chapterRepo, + protected ChapterQueries $queries, + protected BookQueries $bookQueries, ) { } @@ -44,7 +47,7 @@ class ChapterApiController extends ApiController */ public function list() { - $chapters = Chapter::visible(); + $chapters = $this->queries->visibleForList(); return $this->apiListingResponse($chapters, [ 'id', 'book_id', 'name', 'slug', 'description', 'priority', @@ -60,7 +63,7 @@ class ChapterApiController extends ApiController $requestData = $this->validate($request, $this->rules['create']); $bookId = $request->get('book_id'); - $book = Book::visible()->findOrFail($bookId); + $book = $this->bookQueries->findVisibleByIdOrFail(intval($bookId)); $this->checkOwnablePermission('chapter-create', $book); $chapter = $this->chapterRepo->create($requestData, $book); @@ -73,7 +76,7 @@ class ChapterApiController extends ApiController */ public function read(string $id) { - $chapter = Chapter::visible()->findOrFail($id); + $chapter = $this->queries->findVisibleByIdOrFail(intval($id)); $chapter = $this->forJsonDisplay($chapter); $chapter->load([ @@ -94,7 +97,7 @@ class ChapterApiController extends ApiController public function update(Request $request, string $id) { $requestData = $this->validate($request, $this->rules()['update']); - $chapter = Chapter::visible()->findOrFail($id); + $chapter = $this->queries->findVisibleByIdOrFail(intval($id)); $this->checkOwnablePermission('chapter-update', $chapter); if ($request->has('book_id') && $chapter->book_id !== intval($requestData['book_id'])) { @@ -122,7 +125,7 @@ class ChapterApiController extends ApiController */ public function delete(string $id) { - $chapter = Chapter::visible()->findOrFail($id); + $chapter = $this->queries->findVisibleByIdOrFail(intval($id)); $this->checkOwnablePermission('chapter-delete', $chapter); $this->chapterRepo->destroy($chapter); diff --git a/app/Entities/Controllers/PageApiController.php b/app/Entities/Controllers/PageApiController.php index 6e3880aed..d2a5a3ee3 100644 --- a/app/Entities/Controllers/PageApiController.php +++ b/app/Entities/Controllers/PageApiController.php @@ -2,9 +2,7 @@ namespace BookStack\Entities\Controllers; -use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Repos\PageRepo; use BookStack\Exceptions\PermissionsException; @@ -38,6 +36,7 @@ class PageApiController extends ApiController public function __construct( protected PageRepo $pageRepo, protected PageQueries $queries, + protected EntityQueries $entityQueries, ) { } @@ -46,7 +45,7 @@ class PageApiController extends ApiController */ public function list() { - $pages = Page::visible(); + $pages = $this->queries->visibleForList(); return $this->apiListingResponse($pages, [ 'id', 'book_id', 'chapter_id', 'name', 'slug', 'priority', @@ -72,9 +71,9 @@ class PageApiController extends ApiController $this->validate($request, $this->rules['create']); if ($request->has('chapter_id')) { - $parent = Chapter::visible()->findOrFail($request->get('chapter_id')); + $parent = $this->entityQueries->chapters->findVisibleByIdOrFail(intval($request->get('chapter_id'))); } else { - $parent = Book::visible()->findOrFail($request->get('book_id')); + $parent = $this->entityQueries->books->findVisibleByIdOrFail(intval($request->get('book_id'))); } $this->checkOwnablePermission('page-create', $parent); @@ -120,9 +119,9 @@ class PageApiController extends ApiController $parent = null; if ($request->has('chapter_id')) { - $parent = Chapter::visible()->findOrFail($request->get('chapter_id')); + $parent = $this->entityQueries->chapters->findVisibleByIdOrFail(intval($request->get('chapter_id'))); } elseif ($request->has('book_id')) { - $parent = Book::visible()->findOrFail($request->get('book_id')); + $parent = $this->entityQueries->books->findVisibleByIdOrFail(intval($request->get('book_id'))); } if ($parent && !$parent->matches($page->getParent())) { diff --git a/app/Entities/Controllers/PageController.php b/app/Entities/Controllers/PageController.php index 3a5bdbd0b..471df8184 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -276,8 +276,8 @@ class PageController extends Controller $this->checkOwnablePermission('page-delete', $page); $this->setPageTitle(trans('entities.pages_delete_named', ['pageName' => $page->getShortName()])); $usedAsTemplate = - Book::query()->where('default_template_id', '=', $page->id)->count() > 0 || - Chapter::query()->where('default_template_id', '=', $page->id)->count() > 0; + $this->entityQueries->books->start()->where('default_template_id', '=', $page->id)->count() > 0 || + $this->entityQueries->chapters->start()->where('default_template_id', '=', $page->id)->count() > 0; return view('pages.delete', [ 'book' => $page->book, @@ -298,8 +298,8 @@ class PageController extends Controller $this->checkOwnablePermission('page-update', $page); $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName' => $page->getShortName()])); $usedAsTemplate = - Book::query()->where('default_template_id', '=', $page->id)->count() > 0 || - Chapter::query()->where('default_template_id', '=', $page->id)->count() > 0; + $this->entityQueries->books->start()->where('default_template_id', '=', $page->id)->count() > 0 || + $this->entityQueries->chapters->start()->where('default_template_id', '=', $page->id)->count() > 0; return view('pages.delete', [ 'book' => $page->book, diff --git a/app/Entities/Controllers/RecycleBinController.php b/app/Entities/Controllers/RecycleBinController.php index 78f86a5ae..d11dde4dd 100644 --- a/app/Entities/Controllers/RecycleBinController.php +++ b/app/Entities/Controllers/RecycleBinController.php @@ -116,9 +116,9 @@ class RecycleBinController extends Controller * * @throws \Exception */ - public function empty() + public function empty(TrashCan $trash) { - $deleteCount = (new TrashCan())->empty(); + $deleteCount = $trash->empty(); $this->logActivity(ActivityType::RECYCLE_BIN_EMPTY); $this->showSuccessNotification(trans('settings.recycle_bin_destroy_notification', ['count' => $deleteCount])); diff --git a/app/Entities/Models/Book.php b/app/Entities/Models/Book.php index 14cb790c5..c1644dcf5 100644 --- a/app/Entities/Models/Book.php +++ b/app/Entities/Models/Book.php @@ -117,20 +117,11 @@ class Book extends Entity implements HasCoverImage /** * Get the direct child items within this book. */ - public function getDirectChildren(): Collection + public function getDirectVisibleChildren(): Collection { $pages = $this->directPages()->scopes('visible')->get(); $chapters = $this->chapters()->scopes('visible')->get(); return $pages->concat($chapters)->sortBy('priority')->sortByDesc('draft'); } - - /** - * Get a visible book by its slug. - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException - */ - public static function getBySlug(string $slug): self - { - return static::visible()->where('slug', '=', $slug)->firstOrFail(); - } } diff --git a/app/Entities/Queries/BookQueries.php b/app/Entities/Queries/BookQueries.php index 3d7474b3d..2ffff5eca 100644 --- a/app/Entities/Queries/BookQueries.php +++ b/app/Entities/Queries/BookQueries.php @@ -18,6 +18,11 @@ class BookQueries implements ProvidesEntityQueries return $this->start()->scopes('visible')->find($id); } + public function findVisibleByIdOrFail(int $id): Book + { + return $this->start()->scopes('visible')->findOrFail($id); + } + public function findVisibleBySlugOrFail(string $slug): Book { /** @var ?Book $book */ diff --git a/app/Entities/Queries/ChapterQueries.php b/app/Entities/Queries/ChapterQueries.php index 200514932..31193c7ea 100644 --- a/app/Entities/Queries/ChapterQueries.php +++ b/app/Entities/Queries/ChapterQueries.php @@ -24,10 +24,17 @@ class ChapterQueries implements ProvidesEntityQueries return $this->start()->scopes('visible')->find($id); } + public function findVisibleByIdOrFail(int $id): Chapter + { + return $this->start()->scopes('visible')->findOrFail($id); + } + public function findVisibleBySlugsOrFail(string $bookSlug, string $chapterSlug): Chapter { /** @var ?Chapter $chapter */ - $chapter = $this->start()->with('book') + $chapter = $this->start() + ->scopes('visible') + ->with('book') ->whereHas('book', function (Builder $query) use ($bookSlug) { $query->where('slug', '=', $bookSlug); }) @@ -41,9 +48,19 @@ class ChapterQueries implements ProvidesEntityQueries return $chapter; } + public function usingSlugs(string $bookSlug, string $chapterSlug): Builder + { + return $this->start() + ->where('slug', '=', $chapterSlug) + ->whereHas('book', function (Builder $query) use ($bookSlug) { + $query->where('slug', '=', $bookSlug); + }); + } + public function visibleForList(): Builder { return $this->start() + ->scopes('visible') ->select(array_merge(static::$listAttributes, ['book_slug' => function ($builder) { $builder->select('slug') ->from('books') diff --git a/app/Entities/Queries/PageQueries.php b/app/Entities/Queries/PageQueries.php index 1640dc2db..e22769c3a 100644 --- a/app/Entities/Queries/PageQueries.php +++ b/app/Entities/Queries/PageQueries.php @@ -33,6 +33,7 @@ class PageQueries implements ProvidesEntityQueries { /** @var ?Page $page */ $page = $this->start()->with('book') + ->scopes('visible') ->whereHas('book', function (Builder $query) use ($bookSlug) { $query->where('slug', '=', $bookSlug); }) @@ -46,9 +47,19 @@ class PageQueries implements ProvidesEntityQueries return $page; } + public function usingSlugs(string $bookSlug, string $pageSlug): Builder + { + return $this->start() + ->where('slug', '=', $pageSlug) + ->whereHas('book', function (Builder $query) use ($bookSlug) { + $query->where('slug', '=', $bookSlug); + }); + } + public function visibleForList(): Builder { return $this->start() + ->scopes('visible') ->select(array_merge(Page::$listAttributes, ['book_slug' => function ($builder) { $builder->select('slug') ->from('books') @@ -56,6 +67,17 @@ class PageQueries implements ProvidesEntityQueries }])); } + public function visibleWithContents(): Builder + { + return $this->start() + ->scopes('visible') + ->select(array_merge(Page::$contentAttributes, ['book_slug' => function ($builder) { + $builder->select('slug') + ->from('books') + ->whereColumn('books.id', '=', 'pages.book_id'); + }])); + } + public function currentUserDraftsForList(): Builder { return $this->visibleForList() diff --git a/app/Entities/Repos/BookRepo.php b/app/Entities/Repos/BookRepo.php index 26b9414fb..19d159eb1 100644 --- a/app/Entities/Repos/BookRepo.php +++ b/app/Entities/Repos/BookRepo.php @@ -17,7 +17,8 @@ class BookRepo public function __construct( protected BaseRepo $baseRepo, protected TagRepo $tagRepo, - protected ImageRepo $imageRepo + protected ImageRepo $imageRepo, + protected TrashCan $trashCan, ) { } @@ -73,10 +74,9 @@ class BookRepo */ public function destroy(Book $book) { - $trashCan = new TrashCan(); - $trashCan->softDestroyBook($book); + $this->trashCan->softDestroyBook($book); Activity::add(ActivityType::BOOK_DELETE, $book); - $trashCan->autoClearOld(); + $this->trashCan->autoClearOld(); } } diff --git a/app/Entities/Repos/BookshelfRepo.php b/app/Entities/Repos/BookshelfRepo.php index 479e6178f..a00349ef1 100644 --- a/app/Entities/Repos/BookshelfRepo.php +++ b/app/Entities/Repos/BookshelfRepo.php @@ -3,8 +3,8 @@ namespace BookStack\Entities\Repos; use BookStack\Activity\ActivityType; -use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; +use BookStack\Entities\Queries\BookQueries; use BookStack\Entities\Tools\TrashCan; use BookStack\Facades\Activity; use Exception; @@ -13,6 +13,8 @@ class BookshelfRepo { public function __construct( protected BaseRepo $baseRepo, + protected BookQueries $bookQueries, + protected TrashCan $trashCan, ) { } @@ -60,7 +62,7 @@ class BookshelfRepo return intval($id); }); - $syncData = Book::visible() + $syncData = $this->bookQueries->visibleForList() ->whereIn('id', $bookIds) ->pluck('id') ->mapWithKeys(function ($bookId) use ($numericIDs) { @@ -77,9 +79,8 @@ class BookshelfRepo */ public function destroy(Bookshelf $shelf) { - $trashCan = new TrashCan(); - $trashCan->softDestroyShelf($shelf); + $this->trashCan->softDestroyShelf($shelf); Activity::add(ActivityType::BOOKSHELF_DELETE, $shelf); - $trashCan->autoClearOld(); + $this->trashCan->autoClearOld(); } } diff --git a/app/Entities/Repos/ChapterRepo.php b/app/Entities/Repos/ChapterRepo.php index cacca93f6..17cbccd41 100644 --- a/app/Entities/Repos/ChapterRepo.php +++ b/app/Entities/Repos/ChapterRepo.php @@ -18,6 +18,7 @@ class ChapterRepo public function __construct( protected BaseRepo $baseRepo, protected EntityQueries $entityQueries, + protected TrashCan $trashCan, ) { } @@ -59,10 +60,9 @@ class ChapterRepo */ public function destroy(Chapter $chapter) { - $trashCan = new TrashCan(); - $trashCan->softDestroyChapter($chapter); + $this->trashCan->softDestroyChapter($chapter); Activity::add(ActivityType::CHAPTER_DELETE, $chapter); - $trashCan->autoClearOld(); + $this->trashCan->autoClearOld(); } /** diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index 74aed072a..2526b6c44 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -27,7 +27,8 @@ class PageRepo protected RevisionRepo $revisionRepo, protected EntityQueries $entityQueries, protected ReferenceStore $referenceStore, - protected ReferenceUpdater $referenceUpdater + protected ReferenceUpdater $referenceUpdater, + protected TrashCan $trashCan, ) { } @@ -184,10 +185,9 @@ class PageRepo */ public function destroy(Page $page) { - $trashCan = new TrashCan(); - $trashCan->softDestroyPage($page); + $this->trashCan->softDestroyPage($page); Activity::add(ActivityType::PAGE_DELETE, $page); - $trashCan->autoClearOld(); + $this->trashCan->autoClearOld(); } /** diff --git a/app/Entities/Tools/BookContents.php b/app/Entities/Tools/BookContents.php index f45bdfcc1..7fa2134b7 100644 --- a/app/Entities/Tools/BookContents.php +++ b/app/Entities/Tools/BookContents.php @@ -7,15 +7,17 @@ use BookStack\Entities\Models\BookChild; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use Illuminate\Support\Collection; class BookContents { - protected Book $book; + protected EntityQueries $queries; - public function __construct(Book $book) - { - $this->book = $book; + public function __construct( + protected Book $book, + ) { + $this->queries = app()->make(EntityQueries::class); } /** @@ -23,10 +25,12 @@ class BookContents */ public function getLastPriority(): int { - $maxPage = Page::visible()->where('book_id', '=', $this->book->id) + $maxPage = $this->book->pages() ->where('draft', '=', false) - ->where('chapter_id', '=', 0)->max('priority'); - $maxChapter = Chapter::visible()->where('book_id', '=', $this->book->id) + ->where('chapter_id', '=', 0) + ->max('priority'); + + $maxChapter = $this->book->chapters() ->max('priority'); return max($maxChapter, $maxPage, 1); @@ -38,7 +42,7 @@ class BookContents public function getTree(bool $showDrafts = false, bool $renderPages = false): Collection { $pages = $this->getPages($showDrafts, $renderPages); - $chapters = Chapter::visible()->where('book_id', '=', $this->book->id)->get(); + $chapters = $this->book->chapters()->scopes('visible')->get(); $all = collect()->concat($pages)->concat($chapters); $chapterMap = $chapters->keyBy('id'); $lonePages = collect(); @@ -87,15 +91,17 @@ class BookContents */ protected function getPages(bool $showDrafts = false, bool $getPageContent = false): Collection { - $query = Page::visible() - ->select($getPageContent ? Page::$contentAttributes : Page::$listAttributes) - ->where('book_id', '=', $this->book->id); + if ($getPageContent) { + $query = $this->queries->pages->visibleWithContents(); + } else { + $query = $this->queries->pages->visibleForList(); + } if (!$showDrafts) { $query->where('draft', '=', false); } - return $query->get(); + return $query->where('book_id', '=', $this->book->id)->get(); } /** @@ -126,7 +132,7 @@ class BookContents /** @var Book[] $booksInvolved */ $booksInvolved = array_values(array_filter($modelMap, function (string $key) { - return strpos($key, 'book:') === 0; + return str_starts_with($key, 'book:'); }, ARRAY_FILTER_USE_KEY)); // Update permissions of books involved @@ -279,7 +285,7 @@ class BookContents } } - $pages = Page::visible()->whereIn('id', array_unique($ids['page']))->get(Page::$listAttributes); + $pages = $this->queries->pages->visibleForList()->whereIn('id', array_unique($ids['page']))->get(); /** @var Page $page */ foreach ($pages as $page) { $modelMap['page:' . $page->id] = $page; @@ -289,14 +295,14 @@ class BookContents } } - $chapters = Chapter::visible()->whereIn('id', array_unique($ids['chapter']))->get(); + $chapters = $this->queries->chapters->visibleForList()->whereIn('id', array_unique($ids['chapter']))->get(); /** @var Chapter $chapter */ foreach ($chapters as $chapter) { $modelMap['chapter:' . $chapter->id] = $chapter; $ids['book'][] = $chapter->book_id; } - $books = Book::visible()->whereIn('id', array_unique($ids['book']))->get(); + $books = $this->queries->books->visibleForList()->whereIn('id', array_unique($ids['book']))->get(); /** @var Book $book */ foreach ($books as $book) { $modelMap['book:' . $book->id] = $book; diff --git a/app/Entities/Tools/Cloner.php b/app/Entities/Tools/Cloner.php index f7ed4b72d..2030b050c 100644 --- a/app/Entities/Tools/Cloner.php +++ b/app/Entities/Tools/Cloner.php @@ -77,7 +77,7 @@ class Cloner $copyBook = $this->bookRepo->create($bookDetails); // Clone contents - $directChildren = $original->getDirectChildren(); + $directChildren = $original->getDirectVisibleChildren(); foreach ($directChildren as $child) { if ($child instanceof Chapter && userCan('chapter-create', $copyBook)) { $this->cloneChapter($child, $copyBook, $child->name); diff --git a/app/Entities/Tools/SiblingFetcher.php b/app/Entities/Tools/SiblingFetcher.php index 617ef4a62..7b8ac37ad 100644 --- a/app/Entities/Tools/SiblingFetcher.php +++ b/app/Entities/Tools/SiblingFetcher.php @@ -7,10 +7,16 @@ use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use Illuminate\Support\Collection; class SiblingFetcher { + public function __construct( + protected EntityQueries $queries, + ) { + } + /** * Search among the siblings of the entity of given type and id. */ @@ -26,7 +32,7 @@ class SiblingFetcher // Page in book or chapter if (($entity instanceof Page && !$entity->chapter) || $entity instanceof Chapter) { - $entities = $entity->book->getDirectChildren(); + $entities = $entity->book->getDirectVisibleChildren(); } // Book @@ -36,13 +42,13 @@ class SiblingFetcher if ($contextShelf) { $entities = $contextShelf->visibleBooks()->get(); } else { - $entities = Book::visible()->get(); + $entities = $this->queries->books->visibleForList()->get(); } } // Shelf if ($entity instanceof Bookshelf) { - $entities = Bookshelf::visible()->get(); + $entities = $this->queries->shelves->visibleForList()->get(); } return $entities; diff --git a/app/Entities/Tools/TrashCan.php b/app/Entities/Tools/TrashCan.php index 8e9f010df..39c982cdc 100644 --- a/app/Entities/Tools/TrashCan.php +++ b/app/Entities/Tools/TrashCan.php @@ -10,6 +10,7 @@ use BookStack\Entities\Models\Deletion; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\HasCoverImage; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Exceptions\NotifyException; use BookStack\Facades\Activity; use BookStack\Uploads\AttachmentService; @@ -20,6 +21,11 @@ use Illuminate\Support\Carbon; class TrashCan { + public function __construct( + protected EntityQueries $queries, + ) { + } + /** * Send a shelf to the recycle bin. * @@ -203,11 +209,13 @@ class TrashCan } // Remove book template usages - Book::query()->where('default_template_id', '=', $page->id) + $this->queries->books->start() + ->where('default_template_id', '=', $page->id) ->update(['default_template_id' => null]); // Remove chapter template usages - Chapter::query()->where('default_template_id', '=', $page->id) + $this->queries->chapters->start() + ->where('default_template_id', '=', $page->id) ->update(['default_template_id' => null]); $page->forceDelete(); diff --git a/app/Permissions/JointPermissionBuilder.php b/app/Permissions/JointPermissionBuilder.php index 8c961fb13..49eaf0774 100644 --- a/app/Permissions/JointPermissionBuilder.php +++ b/app/Permissions/JointPermissionBuilder.php @@ -8,6 +8,7 @@ use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Permissions\Models\JointPermission; use BookStack\Users\Models\Role; use Illuminate\Database\Eloquent\Builder; @@ -20,6 +21,12 @@ use Illuminate\Support\Facades\DB; */ class JointPermissionBuilder { + public function __construct( + protected EntityQueries $queries, + ) { + } + + /** * Re-generate all entity permission from scratch. */ @@ -36,7 +43,7 @@ class JointPermissionBuilder }); // Chunk through all bookshelves - Bookshelf::query()->withTrashed()->select(['id', 'owned_by']) + $this->queries->shelves->start()->withTrashed()->select(['id', 'owned_by']) ->chunk(50, function (EloquentCollection $shelves) use ($roles) { $this->createManyJointPermissions($shelves->all(), $roles); }); @@ -88,7 +95,7 @@ class JointPermissionBuilder }); // Chunk through all bookshelves - Bookshelf::query()->select(['id', 'owned_by']) + $this->queries->shelves->start()->select(['id', 'owned_by']) ->chunk(100, function ($shelves) use ($roles) { $this->createManyJointPermissions($shelves->all(), $roles); }); @@ -99,7 +106,7 @@ class JointPermissionBuilder */ protected function bookFetchQuery(): Builder { - return Book::query()->withTrashed() + return $this->queries->books->start()->withTrashed() ->select(['id', 'owned_by'])->with([ 'chapters' => function ($query) { $query->withTrashed()->select(['id', 'owned_by', 'book_id']); diff --git a/app/Permissions/PermissionsController.php b/app/Permissions/PermissionsController.php index f2014ea73..5d2035870 100644 --- a/app/Permissions/PermissionsController.php +++ b/app/Permissions/PermissionsController.php @@ -2,10 +2,7 @@ namespace BookStack\Permissions; -use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Bookshelf; -use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Entities\Tools\PermissionsUpdater; use BookStack\Http\Controller; use BookStack\Permissions\Models\EntityPermission; @@ -14,19 +11,18 @@ use Illuminate\Http\Request; class PermissionsController extends Controller { - protected PermissionsUpdater $permissionsUpdater; - - public function __construct(PermissionsUpdater $permissionsUpdater) - { - $this->permissionsUpdater = $permissionsUpdater; + public function __construct( + protected PermissionsUpdater $permissionsUpdater, + protected EntityQueries $queries, + ) { } /** - * Show the Permissions view for a page. + * Show the permissions view for a page. */ public function showForPage(string $bookSlug, string $pageSlug) { - $page = Page::getBySlugs($bookSlug, $pageSlug); + $page = $this->queries->pages->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $this->checkOwnablePermission('restrictions-manage', $page); $this->setPageTitle(trans('entities.pages_permissions')); @@ -41,7 +37,7 @@ class PermissionsController extends Controller */ public function updateForPage(Request $request, string $bookSlug, string $pageSlug) { - $page = Page::getBySlugs($bookSlug, $pageSlug); + $page = $this->queries->pages->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $this->checkOwnablePermission('restrictions-manage', $page); $this->permissionsUpdater->updateFromPermissionsForm($page, $request); @@ -52,11 +48,11 @@ class PermissionsController extends Controller } /** - * Show the Restrictions view for a chapter. + * Show the permissions view for a chapter. */ public function showForChapter(string $bookSlug, string $chapterSlug) { - $chapter = Chapter::getBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->chapters->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $this->checkOwnablePermission('restrictions-manage', $chapter); $this->setPageTitle(trans('entities.chapters_permissions')); @@ -67,11 +63,11 @@ class PermissionsController extends Controller } /** - * Set the restrictions for a chapter. + * Set the permissions for a chapter. */ public function updateForChapter(Request $request, string $bookSlug, string $chapterSlug) { - $chapter = Chapter::getBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->chapters->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $this->checkOwnablePermission('restrictions-manage', $chapter); $this->permissionsUpdater->updateFromPermissionsForm($chapter, $request); @@ -86,7 +82,7 @@ class PermissionsController extends Controller */ public function showForBook(string $slug) { - $book = Book::getBySlug($slug); + $book = $this->queries->books->findVisibleBySlugOrFail($slug); $this->checkOwnablePermission('restrictions-manage', $book); $this->setPageTitle(trans('entities.books_permissions')); @@ -97,11 +93,11 @@ class PermissionsController extends Controller } /** - * Set the restrictions for a book. + * Set the permissions for a book. */ public function updateForBook(Request $request, string $slug) { - $book = Book::getBySlug($slug); + $book = $this->queries->books->findVisibleBySlugOrFail($slug); $this->checkOwnablePermission('restrictions-manage', $book); $this->permissionsUpdater->updateFromPermissionsForm($book, $request); @@ -116,7 +112,7 @@ class PermissionsController extends Controller */ public function showForShelf(string $slug) { - $shelf = Bookshelf::getBySlug($slug); + $shelf = $this->queries->shelves->findVisibleBySlugOrFail($slug); $this->checkOwnablePermission('restrictions-manage', $shelf); $this->setPageTitle(trans('entities.shelves_permissions')); @@ -131,7 +127,7 @@ class PermissionsController extends Controller */ public function updateForShelf(Request $request, string $slug) { - $shelf = Bookshelf::getBySlug($slug); + $shelf = $this->queries->shelves->findVisibleBySlugOrFail($slug); $this->checkOwnablePermission('restrictions-manage', $shelf); $this->permissionsUpdater->updateFromPermissionsForm($shelf, $request); @@ -146,7 +142,7 @@ class PermissionsController extends Controller */ public function copyShelfPermissionsToBooks(string $slug) { - $shelf = Bookshelf::getBySlug($slug); + $shelf = $this->queries->shelves->findVisibleBySlugOrFail($slug); $this->checkOwnablePermission('restrictions-manage', $shelf); $updateCount = $this->permissionsUpdater->updateBookPermissionsFromShelf($shelf); diff --git a/app/References/CrossLinkParser.php b/app/References/CrossLinkParser.php index b9c3ad205..1fd4c1b3e 100644 --- a/app/References/CrossLinkParser.php +++ b/app/References/CrossLinkParser.php @@ -3,6 +3,7 @@ namespace BookStack\References; use BookStack\App\Model; +use BookStack\Entities\Queries\EntityQueries; use BookStack\References\ModelResolvers\BookLinkModelResolver; use BookStack\References\ModelResolvers\BookshelfLinkModelResolver; use BookStack\References\ModelResolvers\ChapterLinkModelResolver; @@ -85,12 +86,14 @@ class CrossLinkParser */ public static function createWithEntityResolvers(): self { + $queries = app()->make(EntityQueries::class); + return new self([ - new PagePermalinkModelResolver(), - new PageLinkModelResolver(), - new ChapterLinkModelResolver(), - new BookLinkModelResolver(), - new BookshelfLinkModelResolver(), + new PagePermalinkModelResolver($queries->pages), + new PageLinkModelResolver($queries->pages), + new ChapterLinkModelResolver($queries->chapters), + new BookLinkModelResolver($queries->books), + new BookshelfLinkModelResolver($queries->shelves), ]); } } diff --git a/app/References/ModelResolvers/BookLinkModelResolver.php b/app/References/ModelResolvers/BookLinkModelResolver.php index d404fe2fd..a671fbf57 100644 --- a/app/References/ModelResolvers/BookLinkModelResolver.php +++ b/app/References/ModelResolvers/BookLinkModelResolver.php @@ -4,9 +4,15 @@ namespace BookStack\References\ModelResolvers; use BookStack\App\Model; use BookStack\Entities\Models\Book; +use BookStack\Entities\Queries\BookQueries; class BookLinkModelResolver implements CrossLinkModelResolver { + public function __construct( + protected BookQueries $queries + ) { + } + public function resolve(string $link): ?Model { $pattern = '/^' . preg_quote(url('/books'), '/') . '\/([\w-]+)' . '([#?\/]|$)/'; @@ -19,7 +25,7 @@ class BookLinkModelResolver implements CrossLinkModelResolver $bookSlug = $matches[1]; /** @var ?Book $model */ - $model = Book::query()->where('slug', '=', $bookSlug)->first(['id']); + $model = $this->queries->start()->where('slug', '=', $bookSlug)->first(['id']); return $model; } diff --git a/app/References/ModelResolvers/BookshelfLinkModelResolver.php b/app/References/ModelResolvers/BookshelfLinkModelResolver.php index cc9df034e..d79c760e0 100644 --- a/app/References/ModelResolvers/BookshelfLinkModelResolver.php +++ b/app/References/ModelResolvers/BookshelfLinkModelResolver.php @@ -4,9 +4,14 @@ namespace BookStack\References\ModelResolvers; use BookStack\App\Model; use BookStack\Entities\Models\Bookshelf; +use BookStack\Entities\Queries\BookshelfQueries; class BookshelfLinkModelResolver implements CrossLinkModelResolver { + public function __construct( + protected BookshelfQueries $queries + ) { + } public function resolve(string $link): ?Model { $pattern = '/^' . preg_quote(url('/shelves'), '/') . '\/([\w-]+)' . '([#?\/]|$)/'; @@ -19,7 +24,7 @@ class BookshelfLinkModelResolver implements CrossLinkModelResolver $shelfSlug = $matches[1]; /** @var ?Bookshelf $model */ - $model = Bookshelf::query()->where('slug', '=', $shelfSlug)->first(['id']); + $model = $this->queries->start()->where('slug', '=', $shelfSlug)->first(['id']); return $model; } diff --git a/app/References/ModelResolvers/ChapterLinkModelResolver.php b/app/References/ModelResolvers/ChapterLinkModelResolver.php index 4733c68ee..318b5ed35 100644 --- a/app/References/ModelResolvers/ChapterLinkModelResolver.php +++ b/app/References/ModelResolvers/ChapterLinkModelResolver.php @@ -4,9 +4,15 @@ namespace BookStack\References\ModelResolvers; use BookStack\App\Model; use BookStack\Entities\Models\Chapter; +use BookStack\Entities\Queries\ChapterQueries; class ChapterLinkModelResolver implements CrossLinkModelResolver { + public function __construct( + protected ChapterQueries $queries + ) { + } + public function resolve(string $link): ?Model { $pattern = '/^' . preg_quote(url('/books'), '/') . '\/([\w-]+)' . '\/chapter\/' . '([\w-]+)' . '([#?\/]|$)/'; @@ -20,7 +26,7 @@ class ChapterLinkModelResolver implements CrossLinkModelResolver $chapterSlug = $matches[2]; /** @var ?Chapter $model */ - $model = Chapter::query()->whereSlugs($bookSlug, $chapterSlug)->first(['id']); + $model = $this->queries->usingSlugs($bookSlug, $chapterSlug)->first(['id']); return $model; } diff --git a/app/References/ModelResolvers/PageLinkModelResolver.php b/app/References/ModelResolvers/PageLinkModelResolver.php index 736382bec..9a01416a4 100644 --- a/app/References/ModelResolvers/PageLinkModelResolver.php +++ b/app/References/ModelResolvers/PageLinkModelResolver.php @@ -4,9 +4,15 @@ namespace BookStack\References\ModelResolvers; use BookStack\App\Model; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\PageQueries; class PageLinkModelResolver implements CrossLinkModelResolver { + public function __construct( + protected PageQueries $queries + ) { + } + public function resolve(string $link): ?Model { $pattern = '/^' . preg_quote(url('/books'), '/') . '\/([\w-]+)' . '\/page\/' . '([\w-]+)' . '([#?\/]|$)/'; @@ -20,7 +26,7 @@ class PageLinkModelResolver implements CrossLinkModelResolver $pageSlug = $matches[2]; /** @var ?Page $model */ - $model = Page::query()->whereSlugs($bookSlug, $pageSlug)->first(['id']); + $model = $this->queries->usingSlugs($bookSlug, $pageSlug)->first(['id']); return $model; } diff --git a/app/References/ModelResolvers/PagePermalinkModelResolver.php b/app/References/ModelResolvers/PagePermalinkModelResolver.php index 9ed00b36d..59d509f33 100644 --- a/app/References/ModelResolvers/PagePermalinkModelResolver.php +++ b/app/References/ModelResolvers/PagePermalinkModelResolver.php @@ -4,9 +4,15 @@ namespace BookStack\References\ModelResolvers; use BookStack\App\Model; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\PageQueries; class PagePermalinkModelResolver implements CrossLinkModelResolver { + public function __construct( + protected PageQueries $queries + ) { + } + public function resolve(string $link): ?Model { $pattern = '/^' . preg_quote(url('/link'), '/') . '\/(\d+)/'; @@ -18,7 +24,7 @@ class PagePermalinkModelResolver implements CrossLinkModelResolver $id = intval($matches[1]); /** @var ?Page $model */ - $model = Page::query()->find($id, ['id']); + $model = $this->queries->start()->find($id, ['id']); return $model; } diff --git a/app/References/ReferenceController.php b/app/References/ReferenceController.php index 991f47225..57dbcce3c 100644 --- a/app/References/ReferenceController.php +++ b/app/References/ReferenceController.php @@ -6,12 +6,14 @@ use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Http\Controller; class ReferenceController extends Controller { public function __construct( - protected ReferenceFetcher $referenceFetcher + protected ReferenceFetcher $referenceFetcher, + protected EntityQueries $queries, ) { } @@ -20,7 +22,7 @@ class ReferenceController extends Controller */ public function page(string $bookSlug, string $pageSlug) { - $page = Page::getBySlugs($bookSlug, $pageSlug); + $page = $this->queries->pages->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $references = $this->referenceFetcher->getReferencesToEntity($page); return view('pages.references', [ @@ -34,7 +36,7 @@ class ReferenceController extends Controller */ public function chapter(string $bookSlug, string $chapterSlug) { - $chapter = Chapter::getBySlugs($bookSlug, $chapterSlug); + $chapter = $this->queries->chapters->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $references = $this->referenceFetcher->getReferencesToEntity($chapter); return view('chapters.references', [ @@ -48,7 +50,7 @@ class ReferenceController extends Controller */ public function book(string $slug) { - $book = Book::getBySlug($slug); + $book = $this->queries->books->findVisibleBySlugOrFail($slug); $references = $this->referenceFetcher->getReferencesToEntity($book); return view('books.references', [ @@ -62,7 +64,7 @@ class ReferenceController extends Controller */ public function shelf(string $slug) { - $shelf = Bookshelf::getBySlug($slug); + $shelf = $this->queries->shelves->findVisibleBySlugOrFail($slug); $references = $this->referenceFetcher->getReferencesToEntity($shelf); return view('shelves.references', [ diff --git a/app/Search/SearchController.php b/app/Search/SearchController.php index 6cf12a579..bc3f2ddb4 100644 --- a/app/Search/SearchController.php +++ b/app/Search/SearchController.php @@ -130,12 +130,12 @@ class SearchController extends Controller /** * Search siblings items in the system. */ - public function searchSiblings(Request $request) + public function searchSiblings(Request $request, SiblingFetcher $siblingFetcher) { $type = $request->get('entity_type', null); $id = $request->get('entity_id', null); - $entities = (new SiblingFetcher())->fetch($type, $id); + $entities = $siblingFetcher->fetch($type, $id); return view('entities.list-basic', ['entities' => $entities, 'style' => 'compact']); } diff --git a/app/Settings/MaintenanceController.php b/app/Settings/MaintenanceController.php index 62eeecf39..0382ae08a 100644 --- a/app/Settings/MaintenanceController.php +++ b/app/Settings/MaintenanceController.php @@ -14,7 +14,7 @@ class MaintenanceController extends Controller /** * Show the page for application maintenance. */ - public function index() + public function index(TrashCan $trashCan) { $this->checkPermission('settings-manage'); $this->setPageTitle(trans('settings.maint')); @@ -23,7 +23,7 @@ class MaintenanceController extends Controller $version = trim(file_get_contents(base_path('version'))); // Recycle bin details - $recycleStats = (new TrashCan())->getTrashedCounts(); + $recycleStats = $trashCan->getTrashedCounts(); return view('settings.maintenance', [ 'version' => $version, diff --git a/app/Uploads/ImageService.php b/app/Uploads/ImageService.php index 1655a4cc3..ee2cb8a35 100644 --- a/app/Uploads/ImageService.php +++ b/app/Uploads/ImageService.php @@ -5,6 +5,7 @@ namespace BookStack\Uploads; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Exceptions\ImageUploadException; use Exception; use Illuminate\Support\Facades\DB; @@ -20,6 +21,7 @@ class ImageService public function __construct( protected ImageStorage $storage, protected ImageResizer $resizer, + protected EntityQueries $queries, ) { } @@ -278,15 +280,15 @@ class ImageService } if ($imageType === 'gallery' || $imageType === 'drawio') { - return Page::visible()->where('id', '=', $image->uploaded_to)->exists(); + return $this->queries->pages->visibleForList()->where('id', '=', $image->uploaded_to)->exists(); } if ($imageType === 'cover_book') { - return Book::visible()->where('id', '=', $image->uploaded_to)->exists(); + return $this->queries->books->visibleForList()->where('id', '=', $image->uploaded_to)->exists(); } if ($imageType === 'cover_bookshelf') { - return Bookshelf::visible()->where('id', '=', $image->uploaded_to)->exists(); + return $this->queries->shelves->visibleForList()->where('id', '=', $image->uploaded_to)->exists(); } return false; diff --git a/app/Users/Controllers/UserProfileController.php b/app/Users/Controllers/UserProfileController.php index bdf268260..963d69a62 100644 --- a/app/Users/Controllers/UserProfileController.php +++ b/app/Users/Controllers/UserProfileController.php @@ -10,16 +10,25 @@ use BookStack\Users\UserRepo; class UserProfileController extends Controller { + public function __construct( + protected UserRepo $userRepo, + protected ActivityQueries $activityQueries, + protected UserContentCounts $contentCounts, + protected UserRecentlyCreatedContent $recentlyCreatedContent + ) { + } + + /** * Show the user profile page. */ - public function show(UserRepo $repo, ActivityQueries $activities, string $slug) + public function show(string $slug) { - $user = $repo->getBySlug($slug); + $user = $this->userRepo->getBySlug($slug); - $userActivity = $activities->userActivity($user); - $recentlyCreated = (new UserRecentlyCreatedContent())->run($user, 5); - $assetCounts = (new UserContentCounts())->run($user); + $userActivity = $this->activityQueries->userActivity($user); + $recentlyCreated = $this->recentlyCreatedContent->run($user, 5); + $assetCounts = $this->contentCounts->run($user); $this->setPageTitle($user->name); diff --git a/app/Users/Queries/UserContentCounts.php b/app/Users/Queries/UserContentCounts.php index 178d8536b..af38bfa7e 100644 --- a/app/Users/Queries/UserContentCounts.php +++ b/app/Users/Queries/UserContentCounts.php @@ -2,10 +2,7 @@ namespace BookStack\Users\Queries; -use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Bookshelf; -use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Users\Models\User; /** @@ -13,6 +10,12 @@ use BookStack\Users\Models\User; */ class UserContentCounts { + public function __construct( + protected EntityQueries $queries, + ) { + } + + /** * @return array{pages: int, chapters: int, books: int, shelves: int} */ @@ -21,10 +24,10 @@ class UserContentCounts $createdBy = ['created_by' => $user->id]; return [ - 'pages' => Page::visible()->where($createdBy)->count(), - 'chapters' => Chapter::visible()->where($createdBy)->count(), - 'books' => Book::visible()->where($createdBy)->count(), - 'shelves' => Bookshelf::visible()->where($createdBy)->count(), + 'pages' => $this->queries->pages->visibleForList()->where($createdBy)->count(), + 'chapters' => $this->queries->chapters->visibleForList()->where($createdBy)->count(), + 'books' => $this->queries->books->visibleForList()->where($createdBy)->count(), + 'shelves' => $this->queries->shelves->visibleForList()->where($createdBy)->count(), ]; } } diff --git a/app/Users/Queries/UserRecentlyCreatedContent.php b/app/Users/Queries/UserRecentlyCreatedContent.php index 23db2c1f1..23850e072 100644 --- a/app/Users/Queries/UserRecentlyCreatedContent.php +++ b/app/Users/Queries/UserRecentlyCreatedContent.php @@ -2,10 +2,7 @@ namespace BookStack\Users\Queries; -use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Bookshelf; -use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Users\Models\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; @@ -15,6 +12,11 @@ use Illuminate\Database\Eloquent\Collection; */ class UserRecentlyCreatedContent { + public function __construct( + protected EntityQueries $queries, + ) { + } + /** * @return array{pages: Collection, chapters: Collection, books: Collection, shelves: Collection} */ @@ -28,10 +30,10 @@ class UserRecentlyCreatedContent }; return [ - 'pages' => $query(Page::visible()->where('draft', '=', false)), - 'chapters' => $query(Chapter::visible()), - 'books' => $query(Book::visible()), - 'shelves' => $query(Bookshelf::visible()), + 'pages' => $query($this->queries->pages->visibleForList()->where('draft', '=', false)), + 'chapters' => $query($this->queries->chapters->visibleForList()), + 'books' => $query($this->queries->books->visibleForList()), + 'shelves' => $query($this->queries->shelves->visibleForList()), ]; } } diff --git a/tests/Entity/BookTest.php b/tests/Entity/BookTest.php index 374089246..04dff293f 100644 --- a/tests/Entity/BookTest.php +++ b/tests/Entity/BookTest.php @@ -317,7 +317,7 @@ class BookTest extends TestCase $copy = Book::query()->where('name', '=', 'My copy book')->first(); $resp->assertRedirect($copy->getUrl()); - $this->assertEquals($book->getDirectChildren()->count(), $copy->getDirectChildren()->count()); + $this->assertEquals($book->getDirectVisibleChildren()->count(), $copy->getDirectVisibleChildren()->count()); $this->get($copy->getUrl())->assertSee($book->description_html, false); } @@ -329,7 +329,7 @@ class BookTest extends TestCase // Hide child content /** @var BookChild $page */ - foreach ($book->getDirectChildren() as $child) { + foreach ($book->getDirectVisibleChildren() as $child) { $this->permissions->setEntityPermissions($child, [], []); } @@ -337,7 +337,7 @@ class BookTest extends TestCase /** @var Book $copy */ $copy = Book::query()->where('name', '=', 'My copy book')->first(); - $this->assertEquals(0, $copy->getDirectChildren()->count()); + $this->assertEquals(0, $copy->getDirectVisibleChildren()->count()); } public function test_copy_does_not_copy_pages_or_chapters_if_user_cant_create() diff --git a/tests/Entity/EntitySearchTest.php b/tests/Entity/EntitySearchTest.php index 9b77a32ab..dcc062044 100644 --- a/tests/Entity/EntitySearchTest.php +++ b/tests/Entity/EntitySearchTest.php @@ -303,7 +303,7 @@ class EntitySearchTest extends TestCase public function test_sibling_search_for_pages_without_chapter() { $page = $this->entities->pageNotWithinChapter(); - $bookChildren = $page->book->getDirectChildren(); + $bookChildren = $page->book->getDirectVisibleChildren(); $this->assertGreaterThan(2, count($bookChildren), 'Ensure we\'re testing with at least 1 sibling'); $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$page->id}&entity_type=page"); @@ -318,7 +318,7 @@ class EntitySearchTest extends TestCase public function test_sibling_search_for_chapters() { $chapter = $this->entities->chapter(); - $bookChildren = $chapter->book->getDirectChildren(); + $bookChildren = $chapter->book->getDirectVisibleChildren(); $this->assertGreaterThan(2, count($bookChildren), 'Ensure we\'re testing with at least 1 sibling'); $search = $this->actingAs($this->users->viewer())->get("/search/entity/siblings?entity_id={$chapter->id}&entity_type=chapter"); From 546cfb0dcc801c4fa6560ca0e6d18b4f1edd830f Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 7 Feb 2024 21:58:27 +0000 Subject: [PATCH 064/124] Queries: Extracted static page,chapter,shelf queries to classes --- .../Controllers/CommentController.php | 6 ++++-- .../Commands/CopyShelfPermissionsCommand.php | 7 ++++--- app/Entities/Controllers/BookController.php | 6 ++++-- .../Controllers/BookshelfApiController.php | 12 ++++++----- .../ChapterExportApiController.php | 21 ++++++++----------- app/Entities/Controllers/PageController.php | 4 +++- .../Controllers/PageExportApiController.php | 18 ++++++++-------- app/Entities/Queries/BookshelfQueries.php | 11 ++++++++++ app/Entities/Repos/BaseRepo.php | 5 +++-- app/Entities/Tools/PageContent.php | 6 +++++- app/Entities/Tools/ShelfContext.php | 15 ++++++++----- app/Search/SearchController.php | 9 ++++---- .../Controllers/AttachmentApiController.php | 8 ++++--- .../Controllers/ImageGalleryApiController.php | 6 ++++-- app/Uploads/ImageRepo.php | 19 +++++++++-------- 15 files changed, 93 insertions(+), 60 deletions(-) diff --git a/app/Activity/Controllers/CommentController.php b/app/Activity/Controllers/CommentController.php index 340524cd0..0e8db9eaf 100644 --- a/app/Activity/Controllers/CommentController.php +++ b/app/Activity/Controllers/CommentController.php @@ -4,6 +4,7 @@ namespace BookStack\Activity\Controllers; use BookStack\Activity\CommentRepo; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\PageQueries; use BookStack\Http\Controller; use Illuminate\Http\Request; use Illuminate\Validation\ValidationException; @@ -11,7 +12,8 @@ use Illuminate\Validation\ValidationException; class CommentController extends Controller { public function __construct( - protected CommentRepo $commentRepo + protected CommentRepo $commentRepo, + protected PageQueries $pageQueries, ) { } @@ -27,7 +29,7 @@ class CommentController extends Controller 'parent_id' => ['nullable', 'integer'], ]); - $page = Page::visible()->find($pageId); + $page = $this->pageQueries->findVisibleById($pageId); if ($page === null) { return response('Not found', 404); } diff --git a/app/Console/Commands/CopyShelfPermissionsCommand.php b/app/Console/Commands/CopyShelfPermissionsCommand.php index fc11484bd..8463ccd03 100644 --- a/app/Console/Commands/CopyShelfPermissionsCommand.php +++ b/app/Console/Commands/CopyShelfPermissionsCommand.php @@ -3,6 +3,7 @@ namespace BookStack\Console\Commands; use BookStack\Entities\Models\Bookshelf; +use BookStack\Entities\Queries\BookshelfQueries; use BookStack\Entities\Tools\PermissionsUpdater; use Illuminate\Console\Command; @@ -28,7 +29,7 @@ class CopyShelfPermissionsCommand extends Command /** * Execute the console command. */ - public function handle(PermissionsUpdater $permissionsUpdater): int + public function handle(PermissionsUpdater $permissionsUpdater, BookshelfQueries $queries): int { $shelfSlug = $this->option('slug'); $cascadeAll = $this->option('all'); @@ -51,11 +52,11 @@ class CopyShelfPermissionsCommand extends Command return 0; } - $shelves = Bookshelf::query()->get(['id']); + $shelves = $queries->start()->get(['id']); } if ($shelfSlug) { - $shelves = Bookshelf::query()->where('slug', '=', $shelfSlug)->get(['id']); + $shelves = $queries->start()->where('slug', '=', $shelfSlug)->get(['id']); if ($shelves->count() === 0) { $this->info('No shelves found with the given slug.'); } diff --git a/app/Entities/Controllers/BookController.php b/app/Entities/Controllers/BookController.php index 0e9346dbd..a0b98636f 100644 --- a/app/Entities/Controllers/BookController.php +++ b/app/Entities/Controllers/BookController.php @@ -8,6 +8,7 @@ use BookStack\Activity\Models\View; use BookStack\Activity\Tools\UserEntityWatchOptions; use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Queries\BookQueries; +use BookStack\Entities\Queries\BookshelfQueries; use BookStack\Entities\Repos\BookRepo; use BookStack\Entities\Tools\BookContents; use BookStack\Entities\Tools\Cloner; @@ -29,6 +30,7 @@ class BookController extends Controller protected ShelfContext $shelfContext, protected BookRepo $bookRepo, protected BookQueries $queries, + protected BookshelfQueries $shelfQueries, protected ReferenceFetcher $referenceFetcher, ) { } @@ -75,7 +77,7 @@ class BookController extends Controller $bookshelf = null; if ($shelfSlug !== null) { - $bookshelf = Bookshelf::visible()->where('slug', '=', $shelfSlug)->firstOrFail(); + $bookshelf = $this->shelfQueries->findVisibleBySlugOrFail($shelfSlug); $this->checkOwnablePermission('bookshelf-update', $bookshelf); } @@ -105,7 +107,7 @@ class BookController extends Controller $bookshelf = null; if ($shelfSlug !== null) { - $bookshelf = Bookshelf::visible()->where('slug', '=', $shelfSlug)->firstOrFail(); + $bookshelf = $this->shelfQueries->findVisibleBySlugOrFail($shelfSlug); $this->checkOwnablePermission('bookshelf-update', $bookshelf); } diff --git a/app/Entities/Controllers/BookshelfApiController.php b/app/Entities/Controllers/BookshelfApiController.php index a12dc90ac..9170226a5 100644 --- a/app/Entities/Controllers/BookshelfApiController.php +++ b/app/Entities/Controllers/BookshelfApiController.php @@ -3,6 +3,7 @@ namespace BookStack\Entities\Controllers; use BookStack\Entities\Models\Bookshelf; +use BookStack\Entities\Queries\BookshelfQueries; use BookStack\Entities\Repos\BookshelfRepo; use BookStack\Http\ApiController; use Exception; @@ -13,7 +14,8 @@ use Illuminate\Validation\ValidationException; class BookshelfApiController extends ApiController { public function __construct( - protected BookshelfRepo $bookshelfRepo + protected BookshelfRepo $bookshelfRepo, + protected BookshelfQueries $queries, ) { } @@ -22,7 +24,7 @@ class BookshelfApiController extends ApiController */ public function list() { - $shelves = Bookshelf::visible(); + $shelves = $this->queries->visibleForList(); return $this->apiListingResponse($shelves, [ 'id', 'name', 'slug', 'description', 'created_at', 'updated_at', 'created_by', 'updated_by', 'owned_by', @@ -54,7 +56,7 @@ class BookshelfApiController extends ApiController */ public function read(string $id) { - $shelf = Bookshelf::visible()->findOrFail($id); + $shelf = $this->queries->findVisibleByIdOrFail(intval($id)); $shelf = $this->forJsonDisplay($shelf); $shelf->load([ 'createdBy', 'updatedBy', 'ownedBy', @@ -78,7 +80,7 @@ class BookshelfApiController extends ApiController */ public function update(Request $request, string $id) { - $shelf = Bookshelf::visible()->findOrFail($id); + $shelf = $this->queries->findVisibleByIdOrFail(intval($id)); $this->checkOwnablePermission('bookshelf-update', $shelf); $requestData = $this->validate($request, $this->rules()['update']); @@ -97,7 +99,7 @@ class BookshelfApiController extends ApiController */ public function delete(string $id) { - $shelf = Bookshelf::visible()->findOrFail($id); + $shelf = $this->queries->findVisibleByIdOrFail(intval($id)); $this->checkOwnablePermission('bookshelf-delete', $shelf); $this->bookshelfRepo->destroy($shelf); diff --git a/app/Entities/Controllers/ChapterExportApiController.php b/app/Entities/Controllers/ChapterExportApiController.php index d1523e665..08aa959b3 100644 --- a/app/Entities/Controllers/ChapterExportApiController.php +++ b/app/Entities/Controllers/ChapterExportApiController.php @@ -3,20 +3,17 @@ namespace BookStack\Entities\Controllers; use BookStack\Entities\Models\Chapter; +use BookStack\Entities\Queries\ChapterQueries; use BookStack\Entities\Tools\ExportFormatter; use BookStack\Http\ApiController; use Throwable; class ChapterExportApiController extends ApiController { - protected $exportFormatter; - - /** - * ChapterExportController constructor. - */ - public function __construct(ExportFormatter $exportFormatter) - { - $this->exportFormatter = $exportFormatter; + public function __construct( + protected ExportFormatter $exportFormatter, + protected ChapterQueries $queries, + ) { $this->middleware('can:content-export'); } @@ -27,7 +24,7 @@ class ChapterExportApiController extends ApiController */ public function exportPdf(int $id) { - $chapter = Chapter::visible()->findOrFail($id); + $chapter = $this->queries->findVisibleByIdOrFail($id); $pdfContent = $this->exportFormatter->chapterToPdf($chapter); return $this->download()->directly($pdfContent, $chapter->slug . '.pdf'); @@ -40,7 +37,7 @@ class ChapterExportApiController extends ApiController */ public function exportHtml(int $id) { - $chapter = Chapter::visible()->findOrFail($id); + $chapter = $this->queries->findVisibleByIdOrFail($id); $htmlContent = $this->exportFormatter->chapterToContainedHtml($chapter); return $this->download()->directly($htmlContent, $chapter->slug . '.html'); @@ -51,7 +48,7 @@ class ChapterExportApiController extends ApiController */ public function exportPlainText(int $id) { - $chapter = Chapter::visible()->findOrFail($id); + $chapter = $this->queries->findVisibleByIdOrFail($id); $textContent = $this->exportFormatter->chapterToPlainText($chapter); return $this->download()->directly($textContent, $chapter->slug . '.txt'); @@ -62,7 +59,7 @@ class ChapterExportApiController extends ApiController */ public function exportMarkdown(int $id) { - $chapter = Chapter::visible()->findOrFail($id); + $chapter = $this->queries->findVisibleByIdOrFail($id); $markdown = $this->exportFormatter->chapterToMarkdown($chapter); return $this->download()->directly($markdown, $chapter->slug . '.md'); diff --git a/app/Entities/Controllers/PageController.php b/app/Entities/Controllers/PageController.php index 471df8184..44ba999e8 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -359,7 +359,9 @@ class PageController extends Controller $query->scopes('visible'); }; - $pages = Page::visible()->with(['updatedBy', 'book' => $visibleBelongsScope, 'chapter' => $visibleBelongsScope]) + $pages = $this->queries->visibleForList() + ->addSelect('updated_by') + ->with(['updatedBy', 'book' => $visibleBelongsScope, 'chapter' => $visibleBelongsScope]) ->orderBy('updated_at', 'desc') ->paginate(20) ->setPath(url('/pages/recently-updated')); diff --git a/app/Entities/Controllers/PageExportApiController.php b/app/Entities/Controllers/PageExportApiController.php index d936a0de2..8737f2f3e 100644 --- a/app/Entities/Controllers/PageExportApiController.php +++ b/app/Entities/Controllers/PageExportApiController.php @@ -3,17 +3,17 @@ namespace BookStack\Entities\Controllers; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Tools\ExportFormatter; use BookStack\Http\ApiController; use Throwable; class PageExportApiController extends ApiController { - protected $exportFormatter; - - public function __construct(ExportFormatter $exportFormatter) - { - $this->exportFormatter = $exportFormatter; + public function __construct( + protected ExportFormatter $exportFormatter, + protected PageQueries $queries, + ) { $this->middleware('can:content-export'); } @@ -24,7 +24,7 @@ class PageExportApiController extends ApiController */ public function exportPdf(int $id) { - $page = Page::visible()->findOrFail($id); + $page = $this->queries->findVisibleByIdOrFail($id); $pdfContent = $this->exportFormatter->pageToPdf($page); return $this->download()->directly($pdfContent, $page->slug . '.pdf'); @@ -37,7 +37,7 @@ class PageExportApiController extends ApiController */ public function exportHtml(int $id) { - $page = Page::visible()->findOrFail($id); + $page = $this->queries->findVisibleByIdOrFail($id); $htmlContent = $this->exportFormatter->pageToContainedHtml($page); return $this->download()->directly($htmlContent, $page->slug . '.html'); @@ -48,7 +48,7 @@ class PageExportApiController extends ApiController */ public function exportPlainText(int $id) { - $page = Page::visible()->findOrFail($id); + $page = $this->queries->findVisibleByIdOrFail($id); $textContent = $this->exportFormatter->pageToPlainText($page); return $this->download()->directly($textContent, $page->slug . '.txt'); @@ -59,7 +59,7 @@ class PageExportApiController extends ApiController */ public function exportMarkdown(int $id) { - $page = Page::visible()->findOrFail($id); + $page = $this->queries->findVisibleByIdOrFail($id); $markdown = $this->exportFormatter->pageToMarkdown($page); return $this->download()->directly($markdown, $page->slug . '.md'); diff --git a/app/Entities/Queries/BookshelfQueries.php b/app/Entities/Queries/BookshelfQueries.php index d61607e7a..f2fe50531 100644 --- a/app/Entities/Queries/BookshelfQueries.php +++ b/app/Entities/Queries/BookshelfQueries.php @@ -18,6 +18,17 @@ class BookshelfQueries implements ProvidesEntityQueries return $this->start()->scopes('visible')->find($id); } + public function findVisibleByIdOrFail(int $id): Bookshelf + { + $shelf = $this->findVisibleById($id); + + if (is_null($shelf)) { + throw new NotFoundException(trans('errors.bookshelf_not_found')); + } + + return $shelf; + } + public function findVisibleBySlugOrFail(string $slug): Bookshelf { /** @var ?Bookshelf $shelf */ diff --git a/app/Entities/Repos/BaseRepo.php b/app/Entities/Repos/BaseRepo.php index 17208ae03..6674f559a 100644 --- a/app/Entities/Repos/BaseRepo.php +++ b/app/Entities/Repos/BaseRepo.php @@ -9,6 +9,7 @@ use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\HasCoverImage; use BookStack\Entities\Models\HasHtmlDescription; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\PageQueries; use BookStack\Exceptions\ImageUploadException; use BookStack\References\ReferenceStore; use BookStack\References\ReferenceUpdater; @@ -23,6 +24,7 @@ class BaseRepo protected ImageRepo $imageRepo, protected ReferenceUpdater $referenceUpdater, protected ReferenceStore $referenceStore, + protected PageQueries $pageQueries, ) { } @@ -125,8 +127,7 @@ class BaseRepo return; } - $templateExists = Page::query()->visible() - ->where('template', '=', true) + $templateExists = $this->pageQueries->visibleTemplates() ->where('id', '=', $templateId) ->exists(); diff --git a/app/Entities/Tools/PageContent.php b/app/Entities/Tools/PageContent.php index 6a89ff626..88987f054 100644 --- a/app/Entities/Tools/PageContent.php +++ b/app/Entities/Tools/PageContent.php @@ -3,6 +3,7 @@ namespace BookStack\Entities\Tools; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Tools\Markdown\MarkdownToHtml; use BookStack\Exceptions\ImageUploadException; use BookStack\Facades\Theme; @@ -21,9 +22,12 @@ use Illuminate\Support\Str; class PageContent { + protected PageQueries $pageQueries; + public function __construct( protected Page $page ) { + $this->pageQueries = app()->make(PageQueries::class); } /** @@ -331,7 +335,7 @@ class PageContent return PageIncludeContent::fromHtmlAndTag('', $tag); } - $matchedPage = Page::visible()->find($tag->getPageId()); + $matchedPage = $this->pageQueries->findVisibleById($tag->getPageId()); $content = PageIncludeContent::fromHtmlAndTag($matchedPage->html ?? '', $tag); if (Theme::hasListeners(ThemeEvents::PAGE_INCLUDE_PARSE)) { diff --git a/app/Entities/Tools/ShelfContext.php b/app/Entities/Tools/ShelfContext.php index 50c7047d9..5ed334878 100644 --- a/app/Entities/Tools/ShelfContext.php +++ b/app/Entities/Tools/ShelfContext.php @@ -4,10 +4,16 @@ namespace BookStack\Entities\Tools; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; +use BookStack\Entities\Queries\BookshelfQueries; class ShelfContext { - protected $KEY_SHELF_CONTEXT_ID = 'context_bookshelf_id'; + protected string $KEY_SHELF_CONTEXT_ID = 'context_bookshelf_id'; + + public function __construct( + protected BookshelfQueries $shelfQueries, + ) { + } /** * Get the current bookshelf context for the given book. @@ -20,8 +26,7 @@ class ShelfContext return null; } - /** @var Bookshelf $shelf */ - $shelf = Bookshelf::visible()->find($contextBookshelfId); + $shelf = $this->shelfQueries->findVisibleById($contextBookshelfId); $shelfContainsBook = $shelf && $shelf->contains($book); return $shelfContainsBook ? $shelf : null; @@ -30,7 +35,7 @@ class ShelfContext /** * Store the current contextual shelf ID. */ - public function setShelfContext(int $shelfId) + public function setShelfContext(int $shelfId): void { session()->put($this->KEY_SHELF_CONTEXT_ID, $shelfId); } @@ -38,7 +43,7 @@ class ShelfContext /** * Clear the session stored shelf context id. */ - public function clearShelfContext() + public function clearShelfContext(): void { session()->forget($this->KEY_SHELF_CONTEXT_ID); } diff --git a/app/Search/SearchController.php b/app/Search/SearchController.php index bc3f2ddb4..08f0826dd 100644 --- a/app/Search/SearchController.php +++ b/app/Search/SearchController.php @@ -3,6 +3,7 @@ namespace BookStack\Search; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Queries\Popular; use BookStack\Entities\Tools\SiblingFetcher; use BookStack\Http\Controller; @@ -11,7 +12,8 @@ use Illuminate\Http\Request; class SearchController extends Controller { public function __construct( - protected SearchRunner $searchRunner + protected SearchRunner $searchRunner, + protected PageQueries $pageQueries, ) { } @@ -95,12 +97,11 @@ class SearchController extends Controller $searchOptions->setFilter('is_template'); $entities = $this->searchRunner->searchEntities($searchOptions, 'page', 1, 20)['results']; } else { - $entities = Page::visible() - ->where('template', '=', true) + $entities = $this->pageQueries->visibleTemplates() ->where('draft', '=', false) ->orderBy('updated_at', 'desc') ->take(20) - ->get(Page::$listAttributes); + ->get(); } return view('search.parts.entity-selector-list', [ diff --git a/app/Uploads/Controllers/AttachmentApiController.php b/app/Uploads/Controllers/AttachmentApiController.php index 2e6d16205..2832fa8e1 100644 --- a/app/Uploads/Controllers/AttachmentApiController.php +++ b/app/Uploads/Controllers/AttachmentApiController.php @@ -3,6 +3,7 @@ namespace BookStack\Uploads\Controllers; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\PageQueries; use BookStack\Exceptions\FileUploadException; use BookStack\Http\ApiController; use BookStack\Uploads\Attachment; @@ -15,7 +16,8 @@ use Illuminate\Validation\ValidationException; class AttachmentApiController extends ApiController { public function __construct( - protected AttachmentService $attachmentService + protected AttachmentService $attachmentService, + protected PageQueries $pageQueries, ) { } @@ -48,7 +50,7 @@ class AttachmentApiController extends ApiController $requestData = $this->validate($request, $this->rules()['create']); $pageId = $request->get('uploaded_to'); - $page = Page::visible()->findOrFail($pageId); + $page = $this->pageQueries->findVisibleByIdOrFail($pageId); $this->checkOwnablePermission('page-update', $page); if ($request->hasFile('file')) { @@ -132,7 +134,7 @@ class AttachmentApiController extends ApiController $page = $attachment->page; if ($requestData['uploaded_to'] ?? false) { $pageId = $request->get('uploaded_to'); - $page = Page::visible()->findOrFail($pageId); + $page = $this->pageQueries->findVisibleByIdOrFail($pageId); $attachment->uploaded_to = $requestData['uploaded_to']; } diff --git a/app/Uploads/Controllers/ImageGalleryApiController.php b/app/Uploads/Controllers/ImageGalleryApiController.php index ec96e4593..cec47feb0 100644 --- a/app/Uploads/Controllers/ImageGalleryApiController.php +++ b/app/Uploads/Controllers/ImageGalleryApiController.php @@ -3,6 +3,7 @@ namespace BookStack\Uploads\Controllers; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\PageQueries; use BookStack\Http\ApiController; use BookStack\Uploads\Image; use BookStack\Uploads\ImageRepo; @@ -18,6 +19,7 @@ class ImageGalleryApiController extends ApiController public function __construct( protected ImageRepo $imageRepo, protected ImageResizer $imageResizer, + protected PageQueries $pageQueries, ) { } @@ -66,9 +68,9 @@ class ImageGalleryApiController extends ApiController { $this->checkPermission('image-create-all'); $data = $this->validate($request, $this->rules()['create']); - Page::visible()->findOrFail($data['uploaded_to']); + $page = $this->pageQueries->findVisibleByIdOrFail($data['uploaded_to']); - $image = $this->imageRepo->saveNew($data['image'], $data['type'], $data['uploaded_to']); + $image = $this->imageRepo->saveNew($data['image'], $data['type'], $page->id); if (isset($data['name'])) { $image->refresh(); diff --git a/app/Uploads/ImageRepo.php b/app/Uploads/ImageRepo.php index 0e312d883..160a02fa1 100644 --- a/app/Uploads/ImageRepo.php +++ b/app/Uploads/ImageRepo.php @@ -3,6 +3,7 @@ namespace BookStack\Uploads; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\PageQueries; use BookStack\Exceptions\ImageUploadException; use BookStack\Permissions\PermissionApplicator; use Exception; @@ -15,6 +16,7 @@ class ImageRepo protected ImageService $imageService, protected PermissionApplicator $permissions, protected ImageResizer $imageResizer, + protected PageQueries $pageQueries, ) { } @@ -77,14 +79,13 @@ class ImageRepo */ public function getEntityFiltered( string $type, - string $filterType = null, - int $page = 0, - int $pageSize = 24, - int $uploadedTo = null, - string $search = null + ?string $filterType, + int $page, + int $pageSize, + int $uploadedTo, + ?string $search ): array { - /** @var Page $contextPage */ - $contextPage = Page::visible()->findOrFail($uploadedTo); + $contextPage = $this->pageQueries->findVisibleByIdOrFail($uploadedTo); $parentFilter = null; if ($filterType === 'book' || $filterType === 'page') { @@ -225,9 +226,9 @@ class ImageRepo */ public function getPagesUsingImage(Image $image): array { - $pages = Page::visible() + $pages = $this->pageQueries->visibleForList() ->where('html', 'like', '%' . $image->url . '%') - ->get(['id', 'name', 'slug', 'book_id']); + ->get(); foreach ($pages as $page) { $page->setAttribute('url', $page->getUrl()); From b77ab6f3af996f924bd2ddbac6e88f61a8bf6cf9 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Wed, 7 Feb 2024 22:41:45 +0000 Subject: [PATCH 065/124] Queries: Moved out or removed some class-level items Also ran auto-removal of unused imports across app folder. --- .../Controllers/CommentController.php | 1 - app/Api/ApiDocsGenerator.php | 1 - app/App/HomeController.php | 1 - app/App/Providers/ThemeServiceProvider.php | 1 - .../Commands/CopyShelfPermissionsCommand.php | 1 - app/Entities/Controllers/BookController.php | 1 - .../ChapterExportApiController.php | 1 - app/Entities/Controllers/PageController.php | 1 - .../Controllers/PageExportApiController.php | 1 - app/Entities/Models/BookChild.php | 15 ------ app/Entities/Models/Chapter.php | 9 ---- app/Entities/Models/Page.php | 12 ----- app/Entities/Queries/BookQueries.php | 7 ++- app/Entities/Queries/BookshelfQueries.php | 6 ++- app/Entities/Queries/ChapterQueries.php | 3 +- app/Entities/Queries/EntityQueries.php | 24 ++++++++-- app/Entities/Queries/PageQueries.php | 30 ++++++++---- .../Queries/ProvidesEntityQueries.php | 13 +++++ app/Entities/Repos/BaseRepo.php | 1 - app/Permissions/JointPermissionBuilder.php | 1 - app/References/ReferenceController.php | 4 -- app/Search/SearchController.php | 1 - app/Search/SearchRunner.php | 48 ++++++++----------- app/Uploads/AttachmentService.php | 1 - .../Controllers/AttachmentApiController.php | 1 - .../Controllers/GalleryImageController.php | 2 - .../Controllers/ImageGalleryApiController.php | 1 - app/Uploads/ImageRepo.php | 1 - app/Uploads/ImageService.php | 3 -- 29 files changed, 85 insertions(+), 107 deletions(-) diff --git a/app/Activity/Controllers/CommentController.php b/app/Activity/Controllers/CommentController.php index 0e8db9eaf..52ccc8238 100644 --- a/app/Activity/Controllers/CommentController.php +++ b/app/Activity/Controllers/CommentController.php @@ -3,7 +3,6 @@ namespace BookStack\Activity\Controllers; use BookStack\Activity\CommentRepo; -use BookStack\Entities\Models\Page; use BookStack\Entities\Queries\PageQueries; use BookStack\Http\Controller; use Illuminate\Http\Request; diff --git a/app/Api/ApiDocsGenerator.php b/app/Api/ApiDocsGenerator.php index bffc38623..9cae80617 100644 --- a/app/Api/ApiDocsGenerator.php +++ b/app/Api/ApiDocsGenerator.php @@ -7,7 +7,6 @@ use Exception; use Illuminate\Contracts\Container\BindingResolutionException; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; -use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Route; use Illuminate\Support\Str; use Illuminate\Validation\Rules\Password; diff --git a/app/App/HomeController.php b/app/App/HomeController.php index 6a4cb0176..56f3d81a8 100644 --- a/app/App/HomeController.php +++ b/app/App/HomeController.php @@ -3,7 +3,6 @@ namespace BookStack\App; use BookStack\Activity\ActivityQueries; -use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Page; use BookStack\Entities\Queries\EntityQueries; use BookStack\Entities\Queries\RecentlyViewed; diff --git a/app/App/Providers/ThemeServiceProvider.php b/app/App/Providers/ThemeServiceProvider.php index c15b43a6b..4c657d912 100644 --- a/app/App/Providers/ThemeServiceProvider.php +++ b/app/App/Providers/ThemeServiceProvider.php @@ -4,7 +4,6 @@ namespace BookStack\App\Providers; use BookStack\Theming\ThemeEvents; use BookStack\Theming\ThemeService; -use Illuminate\Support\Facades\Route; use Illuminate\Support\ServiceProvider; class ThemeServiceProvider extends ServiceProvider diff --git a/app/Console/Commands/CopyShelfPermissionsCommand.php b/app/Console/Commands/CopyShelfPermissionsCommand.php index 8463ccd03..c5e2d504e 100644 --- a/app/Console/Commands/CopyShelfPermissionsCommand.php +++ b/app/Console/Commands/CopyShelfPermissionsCommand.php @@ -2,7 +2,6 @@ namespace BookStack\Console\Commands; -use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Queries\BookshelfQueries; use BookStack\Entities\Tools\PermissionsUpdater; use Illuminate\Console\Command; diff --git a/app/Entities/Controllers/BookController.php b/app/Entities/Controllers/BookController.php index a0b98636f..a1c586f47 100644 --- a/app/Entities/Controllers/BookController.php +++ b/app/Entities/Controllers/BookController.php @@ -6,7 +6,6 @@ use BookStack\Activity\ActivityQueries; use BookStack\Activity\ActivityType; use BookStack\Activity\Models\View; use BookStack\Activity\Tools\UserEntityWatchOptions; -use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Queries\BookQueries; use BookStack\Entities\Queries\BookshelfQueries; use BookStack\Entities\Repos\BookRepo; diff --git a/app/Entities/Controllers/ChapterExportApiController.php b/app/Entities/Controllers/ChapterExportApiController.php index 08aa959b3..ceb2522b2 100644 --- a/app/Entities/Controllers/ChapterExportApiController.php +++ b/app/Entities/Controllers/ChapterExportApiController.php @@ -2,7 +2,6 @@ namespace BookStack\Entities\Controllers; -use BookStack\Entities\Models\Chapter; use BookStack\Entities\Queries\ChapterQueries; use BookStack\Entities\Tools\ExportFormatter; use BookStack\Http\ApiController; diff --git a/app/Entities/Controllers/PageController.php b/app/Entities/Controllers/PageController.php index 44ba999e8..eab53bb25 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -7,7 +7,6 @@ use BookStack\Activity\Tools\CommentTree; use BookStack\Activity\Tools\UserEntityWatchOptions; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Page; use BookStack\Entities\Queries\EntityQueries; use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Repos\PageRepo; diff --git a/app/Entities/Controllers/PageExportApiController.php b/app/Entities/Controllers/PageExportApiController.php index 8737f2f3e..693760bc8 100644 --- a/app/Entities/Controllers/PageExportApiController.php +++ b/app/Entities/Controllers/PageExportApiController.php @@ -2,7 +2,6 @@ namespace BookStack\Entities\Controllers; -use BookStack\Entities\Models\Page; use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Tools\ExportFormatter; use BookStack\Http\ApiController; diff --git a/app/Entities/Models/BookChild.php b/app/Entities/Models/BookChild.php index d19a2466a..ad54fb926 100644 --- a/app/Entities/Models/BookChild.php +++ b/app/Entities/Models/BookChild.php @@ -13,24 +13,9 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property int $priority * @property string $book_slug * @property Book $book - * - * @method Builder whereSlugs(string $bookSlug, string $childSlug) */ abstract class BookChild extends Entity { - /** - * Scope a query to find items where the child has the given childSlug - * where its parent has the bookSlug. - */ - public function scopeWhereSlugs(Builder $query, string $bookSlug, string $childSlug) - { - return $query->with('book') - ->whereHas('book', function (Builder $query) use ($bookSlug) { - $query->where('slug', '=', $bookSlug); - }) - ->where('slug', '=', $childSlug); - } - /** * Get the book this page sits in. */ diff --git a/app/Entities/Models/Chapter.php b/app/Entities/Models/Chapter.php index 422c82442..c926aaa64 100644 --- a/app/Entities/Models/Chapter.php +++ b/app/Entities/Models/Chapter.php @@ -69,13 +69,4 @@ class Chapter extends BookChild ->orderBy('priority', 'asc') ->get(); } - - /** - * Get a visible chapter by its book and page slugs. - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException - */ - public static function getBySlugs(string $bookSlug, string $chapterSlug): self - { - return static::visible()->whereSlugs($bookSlug, $chapterSlug)->firstOrFail(); - } } diff --git a/app/Entities/Models/Page.php b/app/Entities/Models/Page.php index 17d6f9a01..3a433338b 100644 --- a/app/Entities/Models/Page.php +++ b/app/Entities/Models/Page.php @@ -32,9 +32,6 @@ class Page extends BookChild { use HasFactory; - public static $listAttributes = ['name', 'id', 'slug', 'book_id', 'chapter_id', 'draft', 'template', 'text', 'created_at', 'updated_at', 'priority']; - public static $contentAttributes = ['name', 'id', 'slug', 'book_id', 'chapter_id', 'draft', 'template', 'html', 'text', 'created_at', 'updated_at', 'priority']; - protected $fillable = ['name', 'priority']; public string $textField = 'text'; @@ -145,13 +142,4 @@ class Page extends BookChild return $refreshed; } - - /** - * Get a visible page by its book and page slugs. - * @throws \Illuminate\Database\Eloquent\ModelNotFoundException - */ - public static function getBySlugs(string $bookSlug, string $pageSlug): self - { - return static::visible()->whereSlugs($bookSlug, $pageSlug)->firstOrFail(); - } } diff --git a/app/Entities/Queries/BookQueries.php b/app/Entities/Queries/BookQueries.php index 2ffff5eca..8f62e513c 100644 --- a/app/Entities/Queries/BookQueries.php +++ b/app/Entities/Queries/BookQueries.php @@ -8,6 +8,10 @@ use Illuminate\Database\Eloquent\Builder; class BookQueries implements ProvidesEntityQueries { + protected static array $listAttributes = [ + 'id', 'slug', 'name', 'description', 'created_at', 'updated_at', 'image_id' + ]; + public function start(): Builder { return Book::query(); @@ -40,7 +44,8 @@ class BookQueries implements ProvidesEntityQueries public function visibleForList(): Builder { - return $this->start()->scopes('visible'); + return $this->start()->scopes('visible') + ->select(static::$listAttributes); } public function visibleForListWithCover(): Builder diff --git a/app/Entities/Queries/BookshelfQueries.php b/app/Entities/Queries/BookshelfQueries.php index f2fe50531..47ff068a9 100644 --- a/app/Entities/Queries/BookshelfQueries.php +++ b/app/Entities/Queries/BookshelfQueries.php @@ -8,6 +8,10 @@ use Illuminate\Database\Eloquent\Builder; class BookshelfQueries implements ProvidesEntityQueries { + protected static array $listAttributes = [ + 'id', 'slug', 'name', 'description', 'created_at', 'updated_at', 'image_id' + ]; + public function start(): Builder { return Bookshelf::query(); @@ -46,7 +50,7 @@ class BookshelfQueries implements ProvidesEntityQueries public function visibleForList(): Builder { - return $this->start()->scopes('visible'); + return $this->start()->scopes('visible')->select(static::$listAttributes); } public function visibleForListWithCover(): Builder diff --git a/app/Entities/Queries/ChapterQueries.php b/app/Entities/Queries/ChapterQueries.php index 31193c7ea..34f762a15 100644 --- a/app/Entities/Queries/ChapterQueries.php +++ b/app/Entities/Queries/ChapterQueries.php @@ -10,8 +10,7 @@ class ChapterQueries implements ProvidesEntityQueries { protected static array $listAttributes = [ 'id', 'slug', 'name', 'description', 'priority', - 'created_at', 'updated_at', - 'created_by', 'updated_by', 'owned_by', + 'created_at', 'updated_at' ]; public function start(): Builder diff --git a/app/Entities/Queries/EntityQueries.php b/app/Entities/Queries/EntityQueries.php index 31e5b913a..36dc6c0bc 100644 --- a/app/Entities/Queries/EntityQueries.php +++ b/app/Entities/Queries/EntityQueries.php @@ -3,6 +3,8 @@ namespace BookStack\Entities\Queries; use BookStack\Entities\Models\Entity; +use Illuminate\Database\Eloquent\Builder; +use InvalidArgumentException; class EntityQueries { @@ -25,9 +27,25 @@ class EntityQueries $explodedId = explode(':', $identifier); $entityType = $explodedId[0]; $entityId = intval($explodedId[1]); + $queries = $this->getQueriesForType($entityType); + return $queries->findVisibleById($entityId); + } + + /** + * Start a query of visible entities of the given type, + * suitable for listing display. + */ + public function visibleForList(string $entityType): Builder + { + $queries = $this->getQueriesForType($entityType); + return $queries->visibleForList(); + } + + protected function getQueriesForType(string $type): ProvidesEntityQueries + { /** @var ?ProvidesEntityQueries $queries */ - $queries = match ($entityType) { + $queries = match ($type) { 'page' => $this->pages, 'chapter' => $this->chapters, 'book' => $this->books, @@ -36,9 +54,9 @@ class EntityQueries }; if (is_null($queries)) { - return null; + throw new InvalidArgumentException("No entity query class configured for {$type}"); } - return $queries->findVisibleById($entityId); + return $queries; } } diff --git a/app/Entities/Queries/PageQueries.php b/app/Entities/Queries/PageQueries.php index e22769c3a..ec27ba4aa 100644 --- a/app/Entities/Queries/PageQueries.php +++ b/app/Entities/Queries/PageQueries.php @@ -8,6 +8,15 @@ use Illuminate\Database\Eloquent\Builder; class PageQueries implements ProvidesEntityQueries { + protected static array $contentAttributes = [ + 'name', 'id', 'slug', 'book_id', 'chapter_id', 'draft', + 'template', 'html', 'text', 'created_at', 'updated_at', 'priority' + ]; + protected static array $listAttributes = [ + 'name', 'id', 'slug', 'book_id', 'chapter_id', 'draft', + 'template', 'text', 'created_at', 'updated_at', 'priority' + ]; + public function start(): Builder { return Page::query(); @@ -60,22 +69,14 @@ class PageQueries implements ProvidesEntityQueries { return $this->start() ->scopes('visible') - ->select(array_merge(Page::$listAttributes, ['book_slug' => function ($builder) { - $builder->select('slug') - ->from('books') - ->whereColumn('books.id', '=', 'pages.book_id'); - }])); + ->select($this->mergeBookSlugForSelect(static::$listAttributes)); } public function visibleWithContents(): Builder { return $this->start() ->scopes('visible') - ->select(array_merge(Page::$contentAttributes, ['book_slug' => function ($builder) { - $builder->select('slug') - ->from('books') - ->whereColumn('books.id', '=', 'pages.book_id'); - }])); + ->select($this->mergeBookSlugForSelect(static::$contentAttributes)); } public function currentUserDraftsForList(): Builder @@ -90,4 +91,13 @@ class PageQueries implements ProvidesEntityQueries return $this->visibleForList() ->where('template', '=', true); } + + protected function mergeBookSlugForSelect(array $columns): array + { + return array_merge($columns, ['book_slug' => function ($builder) { + $builder->select('slug') + ->from('books') + ->whereColumn('books.id', '=', 'pages.book_id'); + }]); + } } diff --git a/app/Entities/Queries/ProvidesEntityQueries.php b/app/Entities/Queries/ProvidesEntityQueries.php index 103352244..611d0ae52 100644 --- a/app/Entities/Queries/ProvidesEntityQueries.php +++ b/app/Entities/Queries/ProvidesEntityQueries.php @@ -16,6 +16,19 @@ use Illuminate\Database\Eloquent\Builder; */ interface ProvidesEntityQueries { + /** + * Start a new query for this entity type. + */ public function start(): Builder; + + /** + * Find the entity of the given ID, or return null if not found. + */ public function findVisibleById(int $id): ?Entity; + + /** + * Start a query for items that are visible, with selection + * configured for list display of this item. + */ + public function visibleForList(): Builder; } diff --git a/app/Entities/Repos/BaseRepo.php b/app/Entities/Repos/BaseRepo.php index 6674f559a..033350743 100644 --- a/app/Entities/Repos/BaseRepo.php +++ b/app/Entities/Repos/BaseRepo.php @@ -8,7 +8,6 @@ use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\HasCoverImage; use BookStack\Entities\Models\HasHtmlDescription; -use BookStack\Entities\Models\Page; use BookStack\Entities\Queries\PageQueries; use BookStack\Exceptions\ImageUploadException; use BookStack\References\ReferenceStore; diff --git a/app/Permissions/JointPermissionBuilder.php b/app/Permissions/JointPermissionBuilder.php index 49eaf0774..c2922cdc9 100644 --- a/app/Permissions/JointPermissionBuilder.php +++ b/app/Permissions/JointPermissionBuilder.php @@ -4,7 +4,6 @@ namespace BookStack\Permissions; use BookStack\Entities\Models\Book; use BookStack\Entities\Models\BookChild; -use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; diff --git a/app/References/ReferenceController.php b/app/References/ReferenceController.php index 57dbcce3c..2fe86fd59 100644 --- a/app/References/ReferenceController.php +++ b/app/References/ReferenceController.php @@ -2,10 +2,6 @@ namespace BookStack\References; -use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Bookshelf; -use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Models\Page; use BookStack\Entities\Queries\EntityQueries; use BookStack\Http\Controller; diff --git a/app/Search/SearchController.php b/app/Search/SearchController.php index 08f0826dd..b94ec1ec7 100644 --- a/app/Search/SearchController.php +++ b/app/Search/SearchController.php @@ -2,7 +2,6 @@ namespace BookStack\Search; -use BookStack\Entities\Models\Page; use BookStack\Entities\Queries\PageQueries; use BookStack\Entities\Queries\Popular; use BookStack\Entities\Tools\SiblingFetcher; diff --git a/app/Search/SearchRunner.php b/app/Search/SearchRunner.php index aac9d1000..94518dbf7 100644 --- a/app/Search/SearchRunner.php +++ b/app/Search/SearchRunner.php @@ -3,9 +3,9 @@ namespace BookStack\Search; use BookStack\Entities\EntityProvider; -use BookStack\Entities\Models\BookChild; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Permissions\PermissionApplicator; use BookStack\Users\Models\User; use Illuminate\Database\Connection; @@ -20,9 +20,6 @@ use SplObjectStorage; class SearchRunner { - protected EntityProvider $entityProvider; - protected PermissionApplicator $permissions; - /** * Acceptable operators to be used in a query. * @@ -38,10 +35,11 @@ class SearchRunner */ protected $termAdjustmentCache; - public function __construct(EntityProvider $entityProvider, PermissionApplicator $permissions) - { - $this->entityProvider = $entityProvider; - $this->permissions = $permissions; + public function __construct( + protected EntityProvider $entityProvider, + protected PermissionApplicator $permissions, + protected EntityQueries $entityQueries, + ) { $this->termAdjustmentCache = new SplObjectStorage(); } @@ -72,10 +70,9 @@ class SearchRunner continue; } - $entityModelInstance = $this->entityProvider->get($entityType); - $searchQuery = $this->buildQuery($searchOpts, $entityModelInstance); + $searchQuery = $this->buildQuery($searchOpts, $entityType); $entityTotal = $searchQuery->count(); - $searchResults = $this->getPageOfDataFromQuery($searchQuery, $entityModelInstance, $page, $count); + $searchResults = $this->getPageOfDataFromQuery($searchQuery, $entityType, $page, $count); if ($entityTotal > ($page * $count)) { $hasMore = true; @@ -108,8 +105,7 @@ class SearchRunner continue; } - $entityModelInstance = $this->entityProvider->get($entityType); - $search = $this->buildQuery($opts, $entityModelInstance)->where('book_id', '=', $bookId)->take(20)->get(); + $search = $this->buildQuery($opts, $entityType)->where('book_id', '=', $bookId)->take(20)->get(); $results = $results->merge($search); } @@ -122,8 +118,7 @@ class SearchRunner public function searchChapter(int $chapterId, string $searchString): Collection { $opts = SearchOptions::fromString($searchString); - $entityModelInstance = $this->entityProvider->get('page'); - $pages = $this->buildQuery($opts, $entityModelInstance)->where('chapter_id', '=', $chapterId)->take(20)->get(); + $pages = $this->buildQuery($opts, 'page')->where('chapter_id', '=', $chapterId)->take(20)->get(); return $pages->sortByDesc('score'); } @@ -131,17 +126,17 @@ class SearchRunner /** * Get a page of result data from the given query based on the provided page parameters. */ - protected function getPageOfDataFromQuery(EloquentBuilder $query, Entity $entityModelInstance, int $page = 1, int $count = 20): EloquentCollection + protected function getPageOfDataFromQuery(EloquentBuilder $query, string $entityType, int $page = 1, int $count = 20): EloquentCollection { $relations = ['tags']; - if ($entityModelInstance instanceof BookChild) { + if ($entityType === 'page' || $entityType === 'chapter') { $relations['book'] = function (BelongsTo $query) { $query->scopes('visible'); }; } - if ($entityModelInstance instanceof Page) { + if ($entityType === 'page') { $relations['chapter'] = function (BelongsTo $query) { $query->scopes('visible'); }; @@ -157,18 +152,13 @@ class SearchRunner /** * Create a search query for an entity. */ - protected function buildQuery(SearchOptions $searchOpts, Entity $entityModelInstance): EloquentBuilder + protected function buildQuery(SearchOptions $searchOpts, string $entityType): EloquentBuilder { - $entityQuery = $entityModelInstance->newQuery()->scopes('visible'); - - if ($entityModelInstance instanceof Page) { - $entityQuery->select(array_merge($entityModelInstance::$listAttributes, ['owned_by'])); - } else { - $entityQuery->select(['*']); - } + $entityModelInstance = $this->entityProvider->get($entityType); + $entityQuery = $this->entityQueries->visibleForList($entityType); // Handle normal search terms - $this->applyTermSearch($entityQuery, $searchOpts, $entityModelInstance); + $this->applyTermSearch($entityQuery, $searchOpts, $entityType); // Handle exact term matching foreach ($searchOpts->exacts as $inputTerm) { @@ -198,7 +188,7 @@ class SearchRunner /** * For the given search query, apply the queries for handling the regular search terms. */ - protected function applyTermSearch(EloquentBuilder $entityQuery, SearchOptions $options, Entity $entity): void + protected function applyTermSearch(EloquentBuilder $entityQuery, SearchOptions $options, string $entityType): void { $terms = $options->searches; if (count($terms) === 0) { @@ -216,7 +206,7 @@ class SearchRunner $subQuery->addBinding($scoreSelect['bindings'], 'select'); - $subQuery->where('entity_type', '=', $entity->getMorphClass()); + $subQuery->where('entity_type', '=', $entityType); $subQuery->where(function (Builder $query) use ($terms) { foreach ($terms as $inputTerm) { $inputTerm = str_replace('\\', '\\\\', $inputTerm); diff --git a/app/Uploads/AttachmentService.php b/app/Uploads/AttachmentService.php index 72f78e347..bd319fbd7 100644 --- a/app/Uploads/AttachmentService.php +++ b/app/Uploads/AttachmentService.php @@ -4,7 +4,6 @@ namespace BookStack\Uploads; use BookStack\Exceptions\FileUploadException; use Exception; -use Illuminate\Contracts\Filesystem\FileNotFoundException; use Illuminate\Contracts\Filesystem\Filesystem as Storage; use Illuminate\Filesystem\FilesystemManager; use Illuminate\Support\Facades\Log; diff --git a/app/Uploads/Controllers/AttachmentApiController.php b/app/Uploads/Controllers/AttachmentApiController.php index 2832fa8e1..9040ba6d3 100644 --- a/app/Uploads/Controllers/AttachmentApiController.php +++ b/app/Uploads/Controllers/AttachmentApiController.php @@ -2,7 +2,6 @@ namespace BookStack\Uploads\Controllers; -use BookStack\Entities\Models\Page; use BookStack\Entities\Queries\PageQueries; use BookStack\Exceptions\FileUploadException; use BookStack\Http\ApiController; diff --git a/app/Uploads/Controllers/GalleryImageController.php b/app/Uploads/Controllers/GalleryImageController.php index 258f2bef6..1bc9da2d7 100644 --- a/app/Uploads/Controllers/GalleryImageController.php +++ b/app/Uploads/Controllers/GalleryImageController.php @@ -8,8 +8,6 @@ use BookStack\Uploads\ImageRepo; use BookStack\Uploads\ImageResizer; use BookStack\Util\OutOfMemoryHandler; use Illuminate\Http\Request; -use Illuminate\Support\Facades\App; -use Illuminate\Support\Facades\Log; use Illuminate\Validation\ValidationException; class GalleryImageController extends Controller diff --git a/app/Uploads/Controllers/ImageGalleryApiController.php b/app/Uploads/Controllers/ImageGalleryApiController.php index cec47feb0..6d4657a7a 100644 --- a/app/Uploads/Controllers/ImageGalleryApiController.php +++ b/app/Uploads/Controllers/ImageGalleryApiController.php @@ -2,7 +2,6 @@ namespace BookStack\Uploads\Controllers; -use BookStack\Entities\Models\Page; use BookStack\Entities\Queries\PageQueries; use BookStack\Http\ApiController; use BookStack\Uploads\Image; diff --git a/app/Uploads/ImageRepo.php b/app/Uploads/ImageRepo.php index 160a02fa1..1e58816a4 100644 --- a/app/Uploads/ImageRepo.php +++ b/app/Uploads/ImageRepo.php @@ -2,7 +2,6 @@ namespace BookStack\Uploads; -use BookStack\Entities\Models\Page; use BookStack\Entities\Queries\PageQueries; use BookStack\Exceptions\ImageUploadException; use BookStack\Permissions\PermissionApplicator; diff --git a/app/Uploads/ImageService.php b/app/Uploads/ImageService.php index ee2cb8a35..8d8da61ec 100644 --- a/app/Uploads/ImageService.php +++ b/app/Uploads/ImageService.php @@ -2,9 +2,6 @@ namespace BookStack\Uploads; -use BookStack\Entities\Models\Book; -use BookStack\Entities\Models\Bookshelf; -use BookStack\Entities\Models\Page; use BookStack\Entities\Queries\EntityQueries; use BookStack\Exceptions\ImageUploadException; use Exception; From ed21a6d798a5a7b4148bf388051c016a2b3315e4 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 8 Feb 2024 16:39:59 +0000 Subject: [PATCH 066/124] Queries: Updated old use-specific entity query classes - Updated name to align, and differentate from new 'XQueries' clases. - Removed old sketchy base class with app resolving workarounds, to a proper injection-based approach. - Also fixed wrong translation text used in PageQueries. --- .../Controllers/FavouriteController.php | 4 +-- app/App/HomeController.php | 16 +++++---- app/Entities/Queries/EntityQuery.php | 25 ------------- app/Entities/Queries/PageQueries.php | 2 +- .../Queries/{Popular.php => QueryPopular.php} | 16 ++++++--- ...ntlyViewed.php => QueryRecentlyViewed.php} | 14 ++++++-- ...pFavourites.php => QueryTopFavourites.php} | 14 ++++++-- app/Entities/Tools/MixedEntityListLoader.php | 35 ++----------------- app/Search/SearchController.php | 6 ++-- resources/views/errors/404.blade.php | 8 ++--- 10 files changed, 57 insertions(+), 83 deletions(-) delete mode 100644 app/Entities/Queries/EntityQuery.php rename app/Entities/Queries/{Popular.php => QueryPopular.php} (80%) rename app/Entities/Queries/{RecentlyViewed.php => QueryRecentlyViewed.php} (61%) rename app/Entities/Queries/{TopFavourites.php => QueryTopFavourites.php} (71%) diff --git a/app/Activity/Controllers/FavouriteController.php b/app/Activity/Controllers/FavouriteController.php index b3aff1cef..3125a25ad 100644 --- a/app/Activity/Controllers/FavouriteController.php +++ b/app/Activity/Controllers/FavouriteController.php @@ -2,7 +2,7 @@ namespace BookStack\Activity\Controllers; -use BookStack\Entities\Queries\TopFavourites; +use BookStack\Entities\Queries\QueryTopFavourites; use BookStack\Entities\Tools\MixedEntityRequestHelper; use BookStack\Http\Controller; use Illuminate\Http\Request; @@ -21,7 +21,7 @@ class FavouriteController extends Controller { $viewCount = 20; $page = intval($request->get('page', 1)); - $favourites = (new TopFavourites())->run($viewCount + 1, (($page - 1) * $viewCount)); + $favourites = (new QueryTopFavourites())->run($viewCount + 1, (($page - 1) * $viewCount)); $hasMoreLink = ($favourites->count() > $viewCount) ? url('/favourites?page=' . ($page + 1)) : null; diff --git a/app/App/HomeController.php b/app/App/HomeController.php index 56f3d81a8..116f5c8a4 100644 --- a/app/App/HomeController.php +++ b/app/App/HomeController.php @@ -5,8 +5,8 @@ namespace BookStack\App; use BookStack\Activity\ActivityQueries; use BookStack\Entities\Models\Page; use BookStack\Entities\Queries\EntityQueries; -use BookStack\Entities\Queries\RecentlyViewed; -use BookStack\Entities\Queries\TopFavourites; +use BookStack\Entities\Queries\QueryRecentlyViewed; +use BookStack\Entities\Queries\QueryTopFavourites; use BookStack\Entities\Tools\PageContent; use BookStack\Http\Controller; use BookStack\Uploads\FaviconHandler; @@ -23,8 +23,12 @@ class HomeController extends Controller /** * Display the homepage. */ - public function index(Request $request, ActivityQueries $activities) - { + public function index( + Request $request, + ActivityQueries $activities, + QueryRecentlyViewed $recentlyViewed, + QueryTopFavourites $topFavourites, + ) { $activity = $activities->latest(10); $draftPages = []; @@ -38,9 +42,9 @@ class HomeController extends Controller $recentFactor = count($draftPages) > 0 ? 0.5 : 1; $recents = $this->isSignedIn() ? - (new RecentlyViewed())->run(12 * $recentFactor, 1) + $recentlyViewed->run(12 * $recentFactor, 1) : $this->queries->books->visibleForList()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get(); - $favourites = (new TopFavourites())->run(6); + $favourites = $topFavourites->run(6); $recentlyUpdatedPages = $this->queries->pages->visibleForList() ->where('draft', false) ->orderBy('updated_at', 'desc') diff --git a/app/Entities/Queries/EntityQuery.php b/app/Entities/Queries/EntityQuery.php deleted file mode 100644 index bd7a98b5e..000000000 --- a/app/Entities/Queries/EntityQuery.php +++ /dev/null @@ -1,25 +0,0 @@ -make(MixedEntityListLoader::class); - } - - protected function permissionService(): PermissionApplicator - { - return app()->make(PermissionApplicator::class); - } - - protected function entityProvider(): EntityProvider - { - return app()->make(EntityProvider::class); - } -} diff --git a/app/Entities/Queries/PageQueries.php b/app/Entities/Queries/PageQueries.php index ec27ba4aa..8fb02075a 100644 --- a/app/Entities/Queries/PageQueries.php +++ b/app/Entities/Queries/PageQueries.php @@ -50,7 +50,7 @@ class PageQueries implements ProvidesEntityQueries ->first(); if (is_null($page)) { - throw new NotFoundException(trans('errors.chapter_not_found')); + throw new NotFoundException(trans('errors.page_not_found')); } return $page; diff --git a/app/Entities/Queries/Popular.php b/app/Entities/Queries/QueryPopular.php similarity index 80% rename from app/Entities/Queries/Popular.php rename to app/Entities/Queries/QueryPopular.php index a934f346b..b2ca565eb 100644 --- a/app/Entities/Queries/Popular.php +++ b/app/Entities/Queries/QueryPopular.php @@ -3,24 +3,32 @@ namespace BookStack\Entities\Queries; use BookStack\Activity\Models\View; +use BookStack\Entities\EntityProvider; use BookStack\Entities\Models\BookChild; use BookStack\Entities\Models\Entity; +use BookStack\Permissions\PermissionApplicator; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Support\Collection; use Illuminate\Support\Facades\DB; -class Popular extends EntityQuery +class QueryPopular { + public function __construct( + protected PermissionApplicator $permissions, + protected EntityProvider $entityProvider, + ) { + } + public function run(int $count, int $page, array $filterModels = null) { - $query = $this->permissionService() + $query = $this->permissions ->restrictEntityRelationQuery(View::query(), 'views', 'viewable_id', 'viewable_type') ->select('*', 'viewable_id', 'viewable_type', DB::raw('SUM(views) as view_count')) ->groupBy('viewable_id', 'viewable_type') ->orderBy('view_count', 'desc'); if ($filterModels) { - $query->whereIn('viewable_type', $this->entityProvider()->getMorphClasses($filterModels)); + $query->whereIn('viewable_type', $this->entityProvider->getMorphClasses($filterModels)); } $entities = $query->with('viewable') @@ -35,7 +43,7 @@ class Popular extends EntityQuery return $entities; } - protected function loadBooksForChildren(Collection $entities) + protected function loadBooksForChildren(Collection $entities): void { $bookChildren = $entities->filter(fn(Entity $entity) => $entity instanceof BookChild); $eloquent = (new \Illuminate\Database\Eloquent\Collection($bookChildren)); diff --git a/app/Entities/Queries/RecentlyViewed.php b/app/Entities/Queries/QueryRecentlyViewed.php similarity index 61% rename from app/Entities/Queries/RecentlyViewed.php rename to app/Entities/Queries/QueryRecentlyViewed.php index fed15ca5a..f28b8f865 100644 --- a/app/Entities/Queries/RecentlyViewed.php +++ b/app/Entities/Queries/QueryRecentlyViewed.php @@ -3,10 +3,18 @@ namespace BookStack\Entities\Queries; use BookStack\Activity\Models\View; +use BookStack\Entities\Tools\MixedEntityListLoader; +use BookStack\Permissions\PermissionApplicator; use Illuminate\Support\Collection; -class RecentlyViewed extends EntityQuery +class QueryRecentlyViewed { + public function __construct( + protected PermissionApplicator $permissions, + protected MixedEntityListLoader $listLoader, + ) { + } + public function run(int $count, int $page): Collection { $user = user(); @@ -14,7 +22,7 @@ class RecentlyViewed extends EntityQuery return collect(); } - $query = $this->permissionService()->restrictEntityRelationQuery( + $query = $this->permissions->restrictEntityRelationQuery( View::query(), 'views', 'viewable_id', @@ -28,7 +36,7 @@ class RecentlyViewed extends EntityQuery ->take($count) ->get(); - $this->mixedEntityListLoader()->loadIntoRelations($views->all(), 'viewable', false); + $this->listLoader->loadIntoRelations($views->all(), 'viewable', false); return $views->pluck('viewable')->filter(); } diff --git a/app/Entities/Queries/TopFavourites.php b/app/Entities/Queries/QueryTopFavourites.php similarity index 71% rename from app/Entities/Queries/TopFavourites.php rename to app/Entities/Queries/QueryTopFavourites.php index 47d4b77f7..6340e35ef 100644 --- a/app/Entities/Queries/TopFavourites.php +++ b/app/Entities/Queries/QueryTopFavourites.php @@ -3,10 +3,18 @@ namespace BookStack\Entities\Queries; use BookStack\Activity\Models\Favourite; +use BookStack\Entities\Tools\MixedEntityListLoader; +use BookStack\Permissions\PermissionApplicator; use Illuminate\Database\Query\JoinClause; -class TopFavourites extends EntityQuery +class QueryTopFavourites { + public function __construct( + protected PermissionApplicator $permissions, + protected MixedEntityListLoader $listLoader, + ) { + } + public function run(int $count, int $skip = 0) { $user = user(); @@ -14,7 +22,7 @@ class TopFavourites extends EntityQuery return collect(); } - $query = $this->permissionService() + $query = $this->permissions ->restrictEntityRelationQuery(Favourite::query(), 'favourites', 'favouritable_id', 'favouritable_type') ->select('favourites.*') ->leftJoin('views', function (JoinClause $join) { @@ -30,7 +38,7 @@ class TopFavourites extends EntityQuery ->take($count) ->get(); - $this->mixedEntityListLoader()->loadIntoRelations($favourites->all(), 'favouritable', false); + $this->listLoader->loadIntoRelations($favourites->all(), 'favouritable', false); return $favourites->pluck('favouritable')->filter(); } diff --git a/app/Entities/Tools/MixedEntityListLoader.php b/app/Entities/Tools/MixedEntityListLoader.php index a0df791db..f9a940b98 100644 --- a/app/Entities/Tools/MixedEntityListLoader.php +++ b/app/Entities/Tools/MixedEntityListLoader.php @@ -3,20 +3,13 @@ namespace BookStack\Entities\Tools; use BookStack\App\Model; -use BookStack\Entities\EntityProvider; +use BookStack\Entities\Queries\EntityQueries; use Illuminate\Database\Eloquent\Relations\Relation; class MixedEntityListLoader { - protected array $listAttributes = [ - 'page' => ['id', 'name', 'slug', 'book_id', 'chapter_id', 'text', 'draft'], - 'chapter' => ['id', 'name', 'slug', 'book_id', 'description'], - 'book' => ['id', 'name', 'slug', 'description'], - 'bookshelf' => ['id', 'name', 'slug', 'description'], - ]; - public function __construct( - protected EntityProvider $entityProvider + protected EntityQueries $queries, ) { } @@ -61,14 +54,7 @@ class MixedEntityListLoader $modelMap = []; foreach ($idsByType as $type => $ids) { - if (!isset($this->listAttributes[$type])) { - continue; - } - - $instance = $this->entityProvider->get($type); - $models = $instance->newQuery() - ->select(array_merge($this->listAttributes[$type], $this->getSubSelectsForQuery($type))) - ->scopes('visible') + $models = $this->queries->visibleForList($type) ->whereIn('id', $ids) ->with($eagerLoadParents ? $this->getRelationsToEagerLoad($type) : []) ->get(); @@ -100,19 +86,4 @@ class MixedEntityListLoader return $toLoad; } - - protected function getSubSelectsForQuery(string $type): array - { - $subSelects = []; - - if ($type === 'chapter' || $type === 'page') { - $subSelects['book_slug'] = function ($builder) { - $builder->select('slug') - ->from('books') - ->whereColumn('books.id', '=', 'book_id'); - }; - } - - return $subSelects; - } } diff --git a/app/Search/SearchController.php b/app/Search/SearchController.php index b94ec1ec7..2fce6a3d5 100644 --- a/app/Search/SearchController.php +++ b/app/Search/SearchController.php @@ -3,7 +3,7 @@ namespace BookStack\Search; use BookStack\Entities\Queries\PageQueries; -use BookStack\Entities\Queries\Popular; +use BookStack\Entities\Queries\QueryPopular; use BookStack\Entities\Tools\SiblingFetcher; use BookStack\Http\Controller; use Illuminate\Http\Request; @@ -67,7 +67,7 @@ class SearchController extends Controller * Search for a list of entities and return a partial HTML response of matching entities. * Returns the most popular entities if no search is provided. */ - public function searchForSelector(Request $request) + public function searchForSelector(Request $request, QueryPopular $queryPopular) { $entityTypes = $request->filled('types') ? explode(',', $request->get('types')) : ['page', 'chapter', 'book']; $searchTerm = $request->get('term', false); @@ -78,7 +78,7 @@ class SearchController extends Controller $searchTerm .= ' {type:' . implode('|', $entityTypes) . '}'; $entities = $this->searchRunner->searchEntities(SearchOptions::fromString($searchTerm), 'all', 1, 20)['results']; } else { - $entities = (new Popular())->run(20, 0, $entityTypes); + $entities = $queryPopular->run(20, 0, $entityTypes); } return view('search.parts.entity-selector-list', ['entities' => $entities, 'permission' => $permission]); diff --git a/resources/views/errors/404.blade.php b/resources/views/errors/404.blade.php index 27d66b30b..dc24a558d 100644 --- a/resources/views/errors/404.blade.php +++ b/resources/views/errors/404.blade.php @@ -1,5 +1,5 @@ @extends('layouts.simple') - +@inject('popular', \BookStack\Entities\Queries\QueryPopular::class) @section('content')
@@ -28,7 +28,7 @@

{{ trans('entities.pages_popular') }}

- @include('entities.list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['page']), 'style' => 'compact']) + @include('entities.list', ['entities' => $popular->run(10, 0, ['page']), 'style' => 'compact'])
@@ -36,7 +36,7 @@

{{ trans('entities.books_popular') }}

- @include('entities.list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['book']), 'style' => 'compact']) + @include('entities.list', ['entities' => $popular->run(10, 0, ['book']), 'style' => 'compact'])
@@ -44,7 +44,7 @@

{{ trans('entities.chapters_popular') }}

- @include('entities.list', ['entities' => (new \BookStack\Entities\Queries\Popular)->run(10, 0, ['chapter']), 'style' => 'compact']) + @include('entities.list', ['entities' => $popular->run(10, 0, ['chapter']), 'style' => 'compact'])
From ed9c013f6e7ee7a7bfe1c67b4cb1a84ca198b7a6 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Thu, 8 Feb 2024 17:18:03 +0000 Subject: [PATCH 067/124] Queries: Addressed failing test cases from recent changes --- app/Activity/Controllers/FavouriteController.php | 4 ++-- app/Entities/Queries/BookQueries.php | 3 ++- app/Entities/Queries/BookshelfQueries.php | 3 ++- app/Entities/Queries/ChapterQueries.php | 2 +- app/Entities/Queries/PageQueries.php | 5 +++-- app/Entities/Tools/PageContent.php | 5 +++-- app/Entities/Tools/PageEditorData.php | 5 +++-- app/Entities/Tools/SiblingFetcher.php | 3 ++- 8 files changed, 18 insertions(+), 12 deletions(-) diff --git a/app/Activity/Controllers/FavouriteController.php b/app/Activity/Controllers/FavouriteController.php index 3125a25ad..deeb4b0af 100644 --- a/app/Activity/Controllers/FavouriteController.php +++ b/app/Activity/Controllers/FavouriteController.php @@ -17,11 +17,11 @@ class FavouriteController extends Controller /** * Show a listing of all favourite items for the current user. */ - public function index(Request $request) + public function index(Request $request, QueryTopFavourites $topFavourites) { $viewCount = 20; $page = intval($request->get('page', 1)); - $favourites = (new QueryTopFavourites())->run($viewCount + 1, (($page - 1) * $viewCount)); + $favourites = $topFavourites->run($viewCount + 1, (($page - 1) * $viewCount)); $hasMoreLink = ($favourites->count() > $viewCount) ? url('/favourites?page=' . ($page + 1)) : null; diff --git a/app/Entities/Queries/BookQueries.php b/app/Entities/Queries/BookQueries.php index 8f62e513c..534640621 100644 --- a/app/Entities/Queries/BookQueries.php +++ b/app/Entities/Queries/BookQueries.php @@ -9,7 +9,8 @@ use Illuminate\Database\Eloquent\Builder; class BookQueries implements ProvidesEntityQueries { protected static array $listAttributes = [ - 'id', 'slug', 'name', 'description', 'created_at', 'updated_at', 'image_id' + 'id', 'slug', 'name', 'description', + 'created_at', 'updated_at', 'image_id', 'owned_by', ]; public function start(): Builder diff --git a/app/Entities/Queries/BookshelfQueries.php b/app/Entities/Queries/BookshelfQueries.php index 47ff068a9..19717fb7c 100644 --- a/app/Entities/Queries/BookshelfQueries.php +++ b/app/Entities/Queries/BookshelfQueries.php @@ -9,7 +9,8 @@ use Illuminate\Database\Eloquent\Builder; class BookshelfQueries implements ProvidesEntityQueries { protected static array $listAttributes = [ - 'id', 'slug', 'name', 'description', 'created_at', 'updated_at', 'image_id' + 'id', 'slug', 'name', 'description', + 'created_at', 'updated_at', 'image_id', 'owned_by', ]; public function start(): Builder diff --git a/app/Entities/Queries/ChapterQueries.php b/app/Entities/Queries/ChapterQueries.php index 34f762a15..53c5bc9d8 100644 --- a/app/Entities/Queries/ChapterQueries.php +++ b/app/Entities/Queries/ChapterQueries.php @@ -10,7 +10,7 @@ class ChapterQueries implements ProvidesEntityQueries { protected static array $listAttributes = [ 'id', 'slug', 'name', 'description', 'priority', - 'created_at', 'updated_at' + 'book_id', 'created_at', 'updated_at', 'owned_by', ]; public function start(): Builder diff --git a/app/Entities/Queries/PageQueries.php b/app/Entities/Queries/PageQueries.php index 8fb02075a..a5938f754 100644 --- a/app/Entities/Queries/PageQueries.php +++ b/app/Entities/Queries/PageQueries.php @@ -10,11 +10,12 @@ class PageQueries implements ProvidesEntityQueries { protected static array $contentAttributes = [ 'name', 'id', 'slug', 'book_id', 'chapter_id', 'draft', - 'template', 'html', 'text', 'created_at', 'updated_at', 'priority' + 'template', 'html', 'text', 'created_at', 'updated_at', 'priority', + 'created_by', 'updated_by', 'owned_by', ]; protected static array $listAttributes = [ 'name', 'id', 'slug', 'book_id', 'chapter_id', 'draft', - 'template', 'text', 'created_at', 'updated_at', 'priority' + 'template', 'text', 'created_at', 'updated_at', 'priority', 'owned_by', ]; public function start(): Builder diff --git a/app/Entities/Tools/PageContent.php b/app/Entities/Tools/PageContent.php index 88987f054..4f68b828f 100644 --- a/app/Entities/Tools/PageContent.php +++ b/app/Entities/Tools/PageContent.php @@ -329,13 +329,14 @@ class PageContent protected function getContentProviderClosure(bool $blankIncludes): Closure { $contextPage = $this->page; + $queries = $this->pageQueries; - return function (PageIncludeTag $tag) use ($blankIncludes, $contextPage): PageIncludeContent { + return function (PageIncludeTag $tag) use ($blankIncludes, $contextPage, $queries): PageIncludeContent { if ($blankIncludes) { return PageIncludeContent::fromHtmlAndTag('', $tag); } - $matchedPage = $this->pageQueries->findVisibleById($tag->getPageId()); + $matchedPage = $queries->findVisibleById($tag->getPageId()); $content = PageIncludeContent::fromHtmlAndTag($matchedPage->html ?? '', $tag); if (Theme::hasListeners(ThemeEvents::PAGE_INCLUDE_PARSE)) { diff --git a/app/Entities/Tools/PageEditorData.php b/app/Entities/Tools/PageEditorData.php index 20bf19eb2..f0bd23589 100644 --- a/app/Entities/Tools/PageEditorData.php +++ b/app/Entities/Tools/PageEditorData.php @@ -38,7 +38,8 @@ class PageEditorData $templates = $this->queries->pages->visibleTemplates() ->orderBy('name', 'asc') ->take(10) - ->get(); + ->paginate() + ->withPath('/templates'); $draftsEnabled = auth()->check(); @@ -51,7 +52,7 @@ class PageEditorData } // Check for a current draft version for this user - $userDraft = $this->queries->revisions->findLatestCurrentUserDraftsForPageId($page->id)->first(); + $userDraft = $this->queries->revisions->findLatestCurrentUserDraftsForPageId($page->id); if (!is_null($userDraft)) { $page->forceFill($userDraft->only(['name', 'html', 'markdown'])); $isDraftRevision = true; diff --git a/app/Entities/Tools/SiblingFetcher.php b/app/Entities/Tools/SiblingFetcher.php index 7b8ac37ad..34d0fc6b0 100644 --- a/app/Entities/Tools/SiblingFetcher.php +++ b/app/Entities/Tools/SiblingFetcher.php @@ -14,6 +14,7 @@ class SiblingFetcher { public function __construct( protected EntityQueries $queries, + protected ShelfContext $shelfContext, ) { } @@ -38,7 +39,7 @@ class SiblingFetcher // Book // Gets just the books in a shelf if shelf is in context if ($entity instanceof Book) { - $contextShelf = (new ShelfContext())->getContextualShelfForBook($entity); + $contextShelf = $this->shelfContext->getContextualShelfForBook($entity); if ($contextShelf) { $entities = $contextShelf->visibleBooks()->get(); } else { From 1ea2ac864aaa7e4ee3995ec675fd92db7b2722cd Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Sun, 11 Feb 2024 15:42:37 +0000 Subject: [PATCH 068/124] Queries: Update API to align data with previous versions Ensures fields returned match API docs and previous versions of BookStack where we were accidentally returning more fields than expected. Updates tests to cover many of these. Also updated clockwork to ignore image requests for less noisy debugging. Also updated chapter page query to not be loading all page data, via new query in PageQueries. --- app/Config/clockwork.php | 2 ++ .../Controllers/BookApiController.php | 4 +++- .../Controllers/BookshelfApiController.php | 4 +++- .../Controllers/ChapterApiController.php | 23 +++++++++++-------- .../Controllers/ChapterController.php | 3 ++- .../Controllers/PageApiController.php | 3 ++- app/Entities/Queries/PageQueries.php | 8 +++++++ tests/Api/BooksApiTest.php | 3 +++ tests/Api/ChaptersApiTest.php | 13 +++++++++++ tests/Api/PagesApiTest.php | 4 ++++ tests/Api/ShelvesApiTest.php | 3 +++ 11 files changed, 56 insertions(+), 14 deletions(-) diff --git a/app/Config/clockwork.php b/app/Config/clockwork.php index 394af8451..bd59eaf71 100644 --- a/app/Config/clockwork.php +++ b/app/Config/clockwork.php @@ -173,6 +173,8 @@ return [ // List of URIs that should not be collected 'except' => [ + '/uploads/images/.*', // BookStack image requests + '/horizon/.*', // Laravel Horizon requests '/telescope/.*', // Laravel Telescope requests '/_debugbar/.*', // Laravel DebugBar requests diff --git a/app/Entities/Controllers/BookApiController.php b/app/Entities/Controllers/BookApiController.php index 955bd707b..15e67a0f7 100644 --- a/app/Entities/Controllers/BookApiController.php +++ b/app/Entities/Controllers/BookApiController.php @@ -26,7 +26,9 @@ class BookApiController extends ApiController */ public function list() { - $books = $this->queries->visibleForList(); + $books = $this->queries + ->visibleForList() + ->addSelect(['created_by', 'updated_by']); return $this->apiListingResponse($books, [ 'id', 'name', 'slug', 'description', 'created_at', 'updated_at', 'created_by', 'updated_by', 'owned_by', diff --git a/app/Entities/Controllers/BookshelfApiController.php b/app/Entities/Controllers/BookshelfApiController.php index 9170226a5..a665bcb6b 100644 --- a/app/Entities/Controllers/BookshelfApiController.php +++ b/app/Entities/Controllers/BookshelfApiController.php @@ -24,7 +24,9 @@ class BookshelfApiController extends ApiController */ public function list() { - $shelves = $this->queries->visibleForList(); + $shelves = $this->queries + ->visibleForList() + ->addSelect(['created_by', 'updated_by']); return $this->apiListingResponse($shelves, [ 'id', 'name', 'slug', 'description', 'created_at', 'updated_at', 'created_by', 'updated_by', 'owned_by', diff --git a/app/Entities/Controllers/ChapterApiController.php b/app/Entities/Controllers/ChapterApiController.php index fb484b85d..430654330 100644 --- a/app/Entities/Controllers/ChapterApiController.php +++ b/app/Entities/Controllers/ChapterApiController.php @@ -3,8 +3,8 @@ namespace BookStack\Entities\Controllers; use BookStack\Entities\Models\Chapter; -use BookStack\Entities\Queries\BookQueries; use BookStack\Entities\Queries\ChapterQueries; +use BookStack\Entities\Queries\EntityQueries; use BookStack\Entities\Repos\ChapterRepo; use BookStack\Exceptions\PermissionsException; use BookStack\Http\ApiController; @@ -38,7 +38,7 @@ class ChapterApiController extends ApiController public function __construct( protected ChapterRepo $chapterRepo, protected ChapterQueries $queries, - protected BookQueries $bookQueries, + protected EntityQueries $entityQueries, ) { } @@ -47,7 +47,8 @@ class ChapterApiController extends ApiController */ public function list() { - $chapters = $this->queries->visibleForList(); + $chapters = $this->queries->visibleForList() + ->addSelect(['created_by', 'updated_by']); return $this->apiListingResponse($chapters, [ 'id', 'book_id', 'name', 'slug', 'description', 'priority', @@ -63,7 +64,7 @@ class ChapterApiController extends ApiController $requestData = $this->validate($request, $this->rules['create']); $bookId = $request->get('book_id'); - $book = $this->bookQueries->findVisibleByIdOrFail(intval($bookId)); + $book = $this->entityQueries->books->findVisibleByIdOrFail(intval($bookId)); $this->checkOwnablePermission('chapter-create', $book); $chapter = $this->chapterRepo->create($requestData, $book); @@ -79,12 +80,14 @@ class ChapterApiController extends ApiController $chapter = $this->queries->findVisibleByIdOrFail(intval($id)); $chapter = $this->forJsonDisplay($chapter); - $chapter->load([ - 'createdBy', 'updatedBy', 'ownedBy', - 'pages' => function (HasMany $query) { - $query->scopes('visible')->get(['id', 'name', 'slug']); - } - ]); + $chapter->load(['createdBy', 'updatedBy', 'ownedBy']); + + // Note: More fields than usual here, for backwards compatibility, + // due to previously accidentally including more fields that desired. + $pages = $this->entityQueries->pages->visibleForChapterList($chapter->id) + ->addSelect(['created_by', 'updated_by', 'revision_count', 'editor']) + ->get(); + $chapter->setRelation('pages', $pages); return response()->json($chapter); } diff --git a/app/Entities/Controllers/ChapterController.php b/app/Entities/Controllers/ChapterController.php index 2e36a84b9..4274589e2 100644 --- a/app/Entities/Controllers/ChapterController.php +++ b/app/Entities/Controllers/ChapterController.php @@ -79,7 +79,8 @@ class ChapterController extends Controller $this->checkOwnablePermission('chapter-view', $chapter); $sidebarTree = (new BookContents($chapter->book))->getTree(); - $pages = $chapter->getVisiblePages(); + $pages = $this->entityQueries->pages->visibleForChapterList($chapter->id)->get(); + $nextPreviousLocator = new NextPreviousContentLocator($chapter, $sidebarTree); View::incrementFor($chapter); diff --git a/app/Entities/Controllers/PageApiController.php b/app/Entities/Controllers/PageApiController.php index d2a5a3ee3..40598e209 100644 --- a/app/Entities/Controllers/PageApiController.php +++ b/app/Entities/Controllers/PageApiController.php @@ -45,7 +45,8 @@ class PageApiController extends ApiController */ public function list() { - $pages = $this->queries->visibleForList(); + $pages = $this->queries->visibleForList() + ->addSelect(['created_by', 'updated_by', 'revision_count', 'editor']); return $this->apiListingResponse($pages, [ 'id', 'book_id', 'chapter_id', 'name', 'slug', 'priority', diff --git a/app/Entities/Queries/PageQueries.php b/app/Entities/Queries/PageQueries.php index a5938f754..06298f470 100644 --- a/app/Entities/Queries/PageQueries.php +++ b/app/Entities/Queries/PageQueries.php @@ -73,6 +73,14 @@ class PageQueries implements ProvidesEntityQueries ->select($this->mergeBookSlugForSelect(static::$listAttributes)); } + public function visibleForChapterList(int $chapterId): Builder + { + return $this->visibleForList() + ->where('chapter_id', '=', $chapterId) + ->orderBy('draft', 'desc') + ->orderBy('priority', 'asc'); + } + public function visibleWithContents(): Builder { return $this->start() diff --git a/tests/Api/BooksApiTest.php b/tests/Api/BooksApiTest.php index b31bd7d37..b8c2b6133 100644 --- a/tests/Api/BooksApiTest.php +++ b/tests/Api/BooksApiTest.php @@ -24,6 +24,9 @@ class BooksApiTest extends TestCase 'id' => $firstBook->id, 'name' => $firstBook->name, 'slug' => $firstBook->slug, + 'owned_by' => $firstBook->owned_by, + 'created_by' => $firstBook->created_by, + 'updated_by' => $firstBook->updated_by, ], ]]); } diff --git a/tests/Api/ChaptersApiTest.php b/tests/Api/ChaptersApiTest.php index e2d6cfc81..9698d4dd9 100644 --- a/tests/Api/ChaptersApiTest.php +++ b/tests/Api/ChaptersApiTest.php @@ -28,6 +28,9 @@ class ChaptersApiTest extends TestCase 'book_id' => $firstChapter->book->id, 'priority' => $firstChapter->priority, 'book_slug' => $firstChapter->book->slug, + 'owned_by' => $firstChapter->owned_by, + 'created_by' => $firstChapter->created_by, + 'updated_by' => $firstChapter->updated_by, ], ]]); } @@ -149,6 +152,16 @@ class ChaptersApiTest extends TestCase 'id' => $page->id, 'slug' => $page->slug, 'name' => $page->name, + 'owned_by' => $page->owned_by, + 'created_by' => $page->created_by, + 'updated_by' => $page->updated_by, + 'book_id' => $page->id, + 'chapter_id' => $chapter->id, + 'priority' => $page->priority, + 'book_slug' => $chapter->book->slug, + 'draft' => $page->draft, + 'template' => $page->template, + 'editor' => $page->editor, ], ], 'default_template_id' => null, diff --git a/tests/Api/PagesApiTest.php b/tests/Api/PagesApiTest.php index 0d084472d..22659d5bb 100644 --- a/tests/Api/PagesApiTest.php +++ b/tests/Api/PagesApiTest.php @@ -27,6 +27,10 @@ class PagesApiTest extends TestCase 'slug' => $firstPage->slug, 'book_id' => $firstPage->book->id, 'priority' => $firstPage->priority, + 'owned_by' => $firstPage->owned_by, + 'created_by' => $firstPage->created_by, + 'updated_by' => $firstPage->updated_by, + 'revision_count' => $firstPage->revision_count, ], ]]); } diff --git a/tests/Api/ShelvesApiTest.php b/tests/Api/ShelvesApiTest.php index f1b8ed985..be276e110 100644 --- a/tests/Api/ShelvesApiTest.php +++ b/tests/Api/ShelvesApiTest.php @@ -25,6 +25,9 @@ class ShelvesApiTest extends TestCase 'id' => $firstBookshelf->id, 'name' => $firstBookshelf->name, 'slug' => $firstBookshelf->slug, + 'owned_by' => $firstBookshelf->owned_by, + 'created_by' => $firstBookshelf->created_by, + 'updated_by' => $firstBookshelf->updated_by, ], ]]); } From 12daa1c2b982522d31bcc422477708a39aea9c92 Mon Sep 17 00:00:00 2001 From: Dan Brown Date: Tue, 13 Feb 2024 14:00:34 +0000 Subject: [PATCH 069/124] Header: Fixed mobile menu falling out of header Changed button to be within-DOM rather than absolute positioned. Also improves RTL handling by showing menu on the right side. Fixes #4841 --- resources/sass/_header.scss | 10 +--------- resources/views/layouts/parts/header.blade.php | 14 ++++++++------ 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/resources/sass/_header.scss b/resources/sass/_header.scss index e2daae437..d72b66729 100644 --- a/resources/sass/_header.scss +++ b/resources/sass/_header.scss @@ -205,9 +205,6 @@ header .search-box.search-active:focus-within { border: 2px solid rgba(255, 255, 255, 0.8); border-radius: 4px; padding: 0 $-xs; - position: absolute; - right: $-m; - top: 13px; line-height: 1; cursor: pointer; user-select: none; @@ -215,20 +212,15 @@ header .search-box.search-active:focus-within { margin: 0; bottom: -2px; } - @include rtl() { - left: $-m; - right: auto; - } } - @include smaller-than($l) { header .header-links { @include lightDark(background-color, #fff, #333); display: none; z-index: 10; - right: $-m; + inset-inline-end: $-m; border-radius: 4px; overflow: hidden; position: absolute; diff --git a/resources/views/layouts/parts/header.blade.php b/resources/views/layouts/parts/header.blade.php index 0e3ad4466..729b97504 100644 --- a/resources/views/layouts/parts/header.blade.php +++ b/resources/views/layouts/parts/header.blade.php @@ -1,11 +1,13 @@
- Cloudabove + + Diagrams.net - Practicali + + Cloudabove
- Stellar Hosted + + Practicali - Torutec + + Stellar Hosted